From 6fe3aed2e799d437841184915f3a8da0ef3e7d8b Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Wed, 13 Jan 2021 16:08:08 +0100 Subject: [PATCH 01/44] core updated to have new bison parser --- wrappers/realm-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappers/realm-core b/wrappers/realm-core index 057ca3286a..b410efff1a 160000 --- a/wrappers/realm-core +++ b/wrappers/realm-core @@ -1 +1 @@ -Subproject commit 057ca3286ae7b5d4c7a51c1970d2535764629840 +Subproject commit b410efff1a3171a2d0f9d7e423b852682aa36788 From b73b62d949d892f4dc5a526f9cea3c32d8490149 Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Thu, 14 Jan 2021 11:35:36 +0100 Subject: [PATCH 02/44] Wrong commit used for core --- wrappers/realm-core | 2 +- wrappers/src/results_cs.cpp | 19 ++++++++----------- wrappers/src/schema_cs.hpp | 3 +-- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/wrappers/realm-core b/wrappers/realm-core index b410efff1a..1228951f6d 160000 --- a/wrappers/realm-core +++ b/wrappers/realm-core @@ -1 +1 @@ -Subproject commit b410efff1a3171a2d0f9d7e423b852682aa36788 +Subproject commit 1228951f6d67c627f41c360c68712297862493c3 diff --git a/wrappers/src/results_cs.cpp b/wrappers/src/results_cs.cpp index 1df001a8f3..f95bb03eb5 100644 --- a/wrappers/src/results_cs.cpp +++ b/wrappers/src/results_cs.cpp @@ -16,8 +16,7 @@ // //////////////////////////////////////////////////////////////////////////// -#include -#include +#include #include #include @@ -124,20 +123,18 @@ REALM_EXPORT Results* results_get_filtered_results(const Results& results, uint1 { return handle_errors(ex, [&]() { Utf16StringAccessor query_string(query_buf, query_len); - auto query = results.get_query(); auto const &realm = results.get_realm(); + auto const& object_schema = results.get_object_schema(); - parser::ParserResult result = parser::parse(query_string.to_string()); - - parser::KeyPathMapping mapping; + query_parser::KeyPathMapping mapping; realm::populate_keypath_mapping(mapping, *realm); - query_builder::NoArguments no_args; - query_builder::apply_predicate(query, result.predicate, no_args, mapping); + auto table = realm->read_group().get_table(object_schema.table_key); - DescriptorOrdering ordering; - query_builder::apply_ordering(ordering, query.get_table(), result.ordering); - return new Results(realm, std::move(query), std::move(ordering)); + query_parser::NoArguments no_args; + auto query = table->query(query_string, no_args, mapping); + auto ordering = query.get_ordering(); + return new Results(realm, query, *ordering); }); } diff --git a/wrappers/src/schema_cs.hpp b/wrappers/src/schema_cs.hpp index 10f0576ebe..a4f3a2f809 100644 --- a/wrappers/src/schema_cs.hpp +++ b/wrappers/src/schema_cs.hpp @@ -23,8 +23,7 @@ #include #include #include -#include -#include +#include using namespace realm; From ca13f01cdcf49d1158c18c6c3a36910697d18fc1 Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Thu, 14 Jan 2021 12:41:15 +0100 Subject: [PATCH 03/44] New query was not applied on top of the previous one --- wrappers/src/results_cs.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/wrappers/src/results_cs.cpp b/wrappers/src/results_cs.cpp index f95bb03eb5..2d8f268e4b 100644 --- a/wrappers/src/results_cs.cpp +++ b/wrappers/src/results_cs.cpp @@ -124,17 +124,15 @@ REALM_EXPORT Results* results_get_filtered_results(const Results& results, uint1 return handle_errors(ex, [&]() { Utf16StringAccessor query_string(query_buf, query_len); auto const &realm = results.get_realm(); - auto const& object_schema = results.get_object_schema(); query_parser::KeyPathMapping mapping; realm::populate_keypath_mapping(mapping, *realm); - auto table = realm->read_group().get_table(object_schema.table_key); - + auto table = results.get_table(); query_parser::NoArguments no_args; auto query = table->query(query_string, no_args, mapping); auto ordering = query.get_ordering(); - return new Results(realm, query, *ordering); + return new Results(realm, results.get_query().and_query(query), *ordering); }); } From d3c4d481e0060f8f87bd5ca2640be74df8629ed7 Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Thu, 14 Jan 2021 14:24:18 +0100 Subject: [PATCH 04/44] PR feedback applied --- wrappers/src/results_cs.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/wrappers/src/results_cs.cpp b/wrappers/src/results_cs.cpp index 2d8f268e4b..1ff2819dac 100644 --- a/wrappers/src/results_cs.cpp +++ b/wrappers/src/results_cs.cpp @@ -128,9 +128,8 @@ REALM_EXPORT Results* results_get_filtered_results(const Results& results, uint1 query_parser::KeyPathMapping mapping; realm::populate_keypath_mapping(mapping, *realm); - auto table = results.get_table(); query_parser::NoArguments no_args; - auto query = table->query(query_string, no_args, mapping); + auto query = results.get_table()->query(query_string, no_args, mapping); auto ordering = query.get_ordering(); return new Results(realm, results.get_query().and_query(query), *ordering); }); From f30fad0a5a15743992180088d7e5ee717a4d028c Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Thu, 14 Jan 2021 14:35:29 +0100 Subject: [PATCH 05/44] Forgot a null check --- wrappers/src/results_cs.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/wrappers/src/results_cs.cpp b/wrappers/src/results_cs.cpp index 1ff2819dac..79fcd77bb0 100644 --- a/wrappers/src/results_cs.cpp +++ b/wrappers/src/results_cs.cpp @@ -131,7 +131,13 @@ REALM_EXPORT Results* results_get_filtered_results(const Results& results, uint1 query_parser::NoArguments no_args; auto query = results.get_table()->query(query_string, no_args, mapping); auto ordering = query.get_ordering(); - return new Results(realm, results.get_query().and_query(query), *ordering); + + if (ordering) { + return new Results(realm, results.get_query().and_query(query), *ordering); + } + else { + return new Results(realm, results.get_query().and_query(query)); + } }); } From 67dfc41db37500444a86b7b0c011451ebb676630 Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Thu, 14 Jan 2021 14:54:22 +0100 Subject: [PATCH 06/44] Updated unit test to match changed in error message --- Tests/Realm.Tests/Database/CollectionTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Realm.Tests/Database/CollectionTests.cs b/Tests/Realm.Tests/Database/CollectionTests.cs index 5c508e0fac..4b7a36e58d 100644 --- a/Tests/Realm.Tests/Database/CollectionTests.cs +++ b/Tests/Realm.Tests/Database/CollectionTests.cs @@ -611,7 +611,7 @@ public void Results_GetFiltered_WhenPredicateIsInvalid_Throws() { Assert.That( () => _realm.All().Filter("Foo == 5"), - Throws.TypeOf().And.Message.Contains("No property 'Foo' on object of type 'A'")); + Throws.TypeOf().And.Message.Contains("'class_A' has no property: 'Foo'")); } [Test] From 89ae7f5a69a1592eb11f58132c807b946e4d2728 Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Thu, 14 Jan 2021 14:56:59 +0100 Subject: [PATCH 07/44] Updated changelog --- CHANGELOG.md | 1 + wrappers/src/object-store | 1 + 2 files changed, 2 insertions(+) create mode 160000 wrappers/src/object-store diff --git a/CHANGELOG.md b/CHANGELOG.md index ddd9a0beaf..212524a04f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ### Internal * Using Core 10.3.0. +* Migrated to Bison parser ## 10.0.0-beta.3 (2020-12-10) ------------------ diff --git a/wrappers/src/object-store b/wrappers/src/object-store new file mode 160000 index 0000000000..d0ac41bee0 --- /dev/null +++ b/wrappers/src/object-store @@ -0,0 +1 @@ +Subproject commit d0ac41bee0ccab83eb887b9fff8f417875ecf771 From 6c827a598b0fb42f3c71df74cd8666f3a5dbbcb5 Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Thu, 14 Jan 2021 15:12:54 +0100 Subject: [PATCH 08/44] Revert "Updated changelog" This reverts commit 89ae7f5a69a1592eb11f58132c807b946e4d2728. --- CHANGELOG.md | 1 - wrappers/src/object-store | 1 - 2 files changed, 2 deletions(-) delete mode 160000 wrappers/src/object-store diff --git a/CHANGELOG.md b/CHANGELOG.md index 212524a04f..ddd9a0beaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,6 @@ ### Internal * Using Core 10.3.0. -* Migrated to Bison parser ## 10.0.0-beta.3 (2020-12-10) ------------------ diff --git a/wrappers/src/object-store b/wrappers/src/object-store deleted file mode 160000 index d0ac41bee0..0000000000 --- a/wrappers/src/object-store +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d0ac41bee0ccab83eb887b9fff8f417875ecf771 From 95339017568559a432b7bedca4b2b0fc190b5851 Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Thu, 14 Jan 2021 15:14:53 +0100 Subject: [PATCH 09/44] Updated changelog, again --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddd9a0beaf..97badd2a20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ### Internal * Using Core 10.3.0. +* Migrate to bison parser ## 10.0.0-beta.3 (2020-12-10) ------------------ From cfb927e10f279b920e92289ba7b204df63f7ec2f Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Thu, 14 Jan 2021 17:23:11 +0100 Subject: [PATCH 10/44] Fallback to original ordering if the new query doesn't have one --- wrappers/src/results_cs.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/wrappers/src/results_cs.cpp b/wrappers/src/results_cs.cpp index 79fcd77bb0..40c196597d 100644 --- a/wrappers/src/results_cs.cpp +++ b/wrappers/src/results_cs.cpp @@ -130,14 +130,9 @@ REALM_EXPORT Results* results_get_filtered_results(const Results& results, uint1 query_parser::NoArguments no_args; auto query = results.get_table()->query(query_string, no_args, mapping); - auto ordering = query.get_ordering(); + auto ordering = query.get_ordering() ? *query.get_ordering() : results.get_descriptor_ordering(); - if (ordering) { - return new Results(realm, results.get_query().and_query(query), *ordering); - } - else { - return new Results(realm, results.get_query().and_query(query)); - } + return new Results(realm, results.get_query().and_query(query), ordering); }); } From cbc60cb239b0b65eea5dae071f56f83cc835dc68 Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Fri, 15 Jan 2021 10:40:38 +0100 Subject: [PATCH 11/44] Fixed usage of query odering --- wrappers/src/results_cs.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/wrappers/src/results_cs.cpp b/wrappers/src/results_cs.cpp index 40c196597d..5a94866278 100644 --- a/wrappers/src/results_cs.cpp +++ b/wrappers/src/results_cs.cpp @@ -129,10 +129,13 @@ REALM_EXPORT Results* results_get_filtered_results(const Results& results, uint1 realm::populate_keypath_mapping(mapping, *realm); query_parser::NoArguments no_args; - auto query = results.get_table()->query(query_string, no_args, mapping); - auto ordering = query.get_ordering() ? *query.get_ordering() : results.get_descriptor_ordering(); + Query parsed_query = results.get_table()->query(query_string, no_args, mapping); + DescriptorOrdering new_order = results.get_descriptor_ordering(); + if (auto parsed_ordering = parsed_query.get_ordering()) { + new_order.append(*parsed_ordering); + } - return new Results(realm, results.get_query().and_query(query), ordering); + return new Results(realm, results.get_query().and_query(std::move(parsed_query)), new_order); }); } From 580699df08670df061c04eb14a1e4af3e9825af3 Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Fri, 15 Jan 2021 11:05:14 +0100 Subject: [PATCH 12/44] Updated unit test to use the user facing name of class --- Tests/Realm.Tests/Database/CollectionTests.cs | 2 +- wrappers/src/results_cs.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Realm.Tests/Database/CollectionTests.cs b/Tests/Realm.Tests/Database/CollectionTests.cs index 4b7a36e58d..547ab303af 100644 --- a/Tests/Realm.Tests/Database/CollectionTests.cs +++ b/Tests/Realm.Tests/Database/CollectionTests.cs @@ -611,7 +611,7 @@ public void Results_GetFiltered_WhenPredicateIsInvalid_Throws() { Assert.That( () => _realm.All().Filter("Foo == 5"), - Throws.TypeOf().And.Message.Contains("'class_A' has no property: 'Foo'")); + Throws.TypeOf().And.Message.Contains("'A' has no property: 'Foo'")); } [Test] diff --git a/wrappers/src/results_cs.cpp b/wrappers/src/results_cs.cpp index 5a94866278..a9ce1259c0 100644 --- a/wrappers/src/results_cs.cpp +++ b/wrappers/src/results_cs.cpp @@ -123,7 +123,7 @@ REALM_EXPORT Results* results_get_filtered_results(const Results& results, uint1 { return handle_errors(ex, [&]() { Utf16StringAccessor query_string(query_buf, query_len); - auto const &realm = results.get_realm(); + auto const& realm = results.get_realm(); query_parser::KeyPathMapping mapping; realm::populate_keypath_mapping(mapping, *realm); From b01db8cb671248c7ed4095df3f4447594cfe2817 Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Fri, 15 Jan 2021 15:30:46 +0100 Subject: [PATCH 13/44] Changed realm-core to have new changes --- wrappers/realm-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappers/realm-core b/wrappers/realm-core index 1228951f6d..592b151e14 160000 --- a/wrappers/realm-core +++ b/wrappers/realm-core @@ -1 +1 @@ -Subproject commit 1228951f6d67c627f41c360c68712297862493c3 +Subproject commit 592b151e145d25eb4b22d3ab7759012622168072 From 0f4923928e290279b092972f65ed85e0e042b24f Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Fri, 15 Jan 2021 15:43:18 +0100 Subject: [PATCH 14/44] Core points to new commit --- wrappers/realm-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappers/realm-core b/wrappers/realm-core index 592b151e14..9be774c892 160000 --- a/wrappers/realm-core +++ b/wrappers/realm-core @@ -1 +1 @@ -Subproject commit 592b151e145d25eb4b22d3ab7759012622168072 +Subproject commit 9be774c892d4ad9a34363e06aa4e9a3acb587397 From 0f0d18ec999cbefafebaf9d9b281cb0153138e8e Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Fri, 15 Jan 2021 16:38:13 +0100 Subject: [PATCH 15/44] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97badd2a20..b3789cfa99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### Enhancements * Add support for the `GUID` data type. It can be used as primary key and is indexable. (PR [#2120](https://github.com/realm/realm-dotnet/pull/2120)) +* Replaced the implementation of the string query parser (the one used for realm.All().Filter("some-string-query")). This results in ~5% reduction of the size of the native binary while keeping the query execution times on par with the old parser. ### Compatibility * Realm Studio: 10.0.0 or later. From fd43ddb7f1b5a87f070990a7e03aaca8c2858cc6 Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Fri, 15 Jan 2021 17:50:29 +0100 Subject: [PATCH 16/44] Update CHANGELOG.md Co-authored-by: Nikola Irinchev --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3789cfa99..afa86224f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ ### Enhancements * Add support for the `GUID` data type. It can be used as primary key and is indexable. (PR [#2120](https://github.com/realm/realm-dotnet/pull/2120)) -* Replaced the implementation of the string query parser (the one used for realm.All().Filter("some-string-query")). This results in ~5% reduction of the size of the native binary while keeping the query execution times on par with the old parser. +* Replaced the implementation of the string query parser (the one used for [`realm.All().Filter("some-string-query")`](https://docs.mongodb.com/realm-sdks/dotnet/10.0.0-beta.3/reference/Realms.CollectionExtensions.html#Realms_CollectionExtensions_Filter__1_System_Linq_IQueryable___0__System_String_)). This results in ~5% reduction of the size of the native binary while keeping the query execution times on par with the old parser. (PR [#2185](https://github.com/realm/realm-dotnet/pull/2185), Core upgrade) ### Compatibility * Realm Studio: 10.0.0 or later. From 256369ab77e134e658b37de4b477718fcdd1956b Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Mon, 18 Jan 2021 11:26:32 +0100 Subject: [PATCH 17/44] Update core submodule and added std::move --- wrappers/realm-core | 2 +- wrappers/src/results_cs.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wrappers/realm-core b/wrappers/realm-core index 9be774c892..b200d987d3 160000 --- a/wrappers/realm-core +++ b/wrappers/realm-core @@ -1 +1 @@ -Subproject commit 9be774c892d4ad9a34363e06aa4e9a3acb587397 +Subproject commit b200d987d3a60106fa1f77339bfb98e893935d51 diff --git a/wrappers/src/results_cs.cpp b/wrappers/src/results_cs.cpp index a9ce1259c0..904b525a34 100644 --- a/wrappers/src/results_cs.cpp +++ b/wrappers/src/results_cs.cpp @@ -135,7 +135,7 @@ REALM_EXPORT Results* results_get_filtered_results(const Results& results, uint1 new_order.append(*parsed_ordering); } - return new Results(realm, results.get_query().and_query(std::move(parsed_query)), new_order); + return new Results(realm, results.get_query().and_query(std::move(parsed_query)), std::move(new_order)); }); } From 37342c17b20b63a7c43c2f1102337dee7e55e37f Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Mon, 18 Jan 2021 17:13:15 +0100 Subject: [PATCH 18/44] Added value substitution in string based query --- .../DatabaseTypes/RealmCollectionBase.cs | 4 ++-- .../Realm/Extensions/CollectionExtensions.cs | 4 ++-- Realm/Realm/Handles/CollectionHandleBase.cs | 2 +- Realm/Realm/Handles/ListHandle.cs | 2 +- Realm/Realm/Handles/ResultsHandle.cs | 20 ++++++++++++++++--- Realm/Realm/Handles/SetHandle.cs | 2 +- wrappers/src/results_cs.cpp | 16 ++++++++++++--- 7 files changed, 37 insertions(+), 13 deletions(-) diff --git a/Realm/Realm/DatabaseTypes/RealmCollectionBase.cs b/Realm/Realm/DatabaseTypes/RealmCollectionBase.cs index 62889036db..e59bfe4765 100644 --- a/Realm/Realm/DatabaseTypes/RealmCollectionBase.cs +++ b/Realm/Realm/DatabaseTypes/RealmCollectionBase.cs @@ -164,9 +164,9 @@ public RealmCollectionBase Snapshot() return new RealmResults(Realm, handle, Metadata); } - internal RealmResults GetFilteredResults(string query) + internal RealmResults GetFilteredResults(string query, RealmValue[] arguments) { - var handle = Handle.Value.GetFilteredResults(query); + var handle = Handle.Value.GetFilteredResults(query, arguments); return new RealmResults(Realm, handle, Metadata); } diff --git a/Realm/Realm/Extensions/CollectionExtensions.cs b/Realm/Realm/Extensions/CollectionExtensions.cs index c88eb11b9e..74cc1f1c72 100644 --- a/Realm/Realm/Extensions/CollectionExtensions.cs +++ b/Realm/Realm/Extensions/CollectionExtensions.cs @@ -179,10 +179,10 @@ public static void Move(this IList list, int from, int to) /// Examples of the NSPredicate syntax /// /// NSPredicate Cheatsheet - public static IQueryable Filter(this IQueryable query, string predicate) + public static IQueryable Filter(this IQueryable query, string predicate, params RealmValue[] arguments) { var realmResults = Argument.EnsureType>(query, $"{nameof(query)} must be a query obtained by calling Realm.All.", nameof(query)); - return realmResults.GetFilteredResults(predicate); + return realmResults.GetFilteredResults(predicate, arguments); } } } diff --git a/Realm/Realm/Handles/CollectionHandleBase.cs b/Realm/Realm/Handles/CollectionHandleBase.cs index b3893191a3..a3aa8e6ffe 100644 --- a/Realm/Realm/Handles/CollectionHandleBase.cs +++ b/Realm/Realm/Handles/CollectionHandleBase.cs @@ -49,7 +49,7 @@ public RealmValue GetValueAtIndex(int index, RealmObjectBase.Metadata metadata, public abstract ResultsHandle Snapshot(); - public abstract ResultsHandle GetFilteredResults(string query); + public abstract ResultsHandle GetFilteredResults(string query, RealmValue[] arguments); public abstract CollectionHandleBase Freeze(SharedRealmHandle frozenRealmHandle); diff --git a/Realm/Realm/Handles/ListHandle.cs b/Realm/Realm/Handles/ListHandle.cs index e252705bdd..aac12faf47 100644 --- a/Realm/Realm/Handles/ListHandle.cs +++ b/Realm/Realm/Handles/ListHandle.cs @@ -214,7 +214,7 @@ public override ResultsHandle Snapshot() return new ResultsHandle(Root ?? this, ptr); } - public override ResultsHandle GetFilteredResults(string query) + public override ResultsHandle GetFilteredResults(string query, RealmValue[] arguments) { throw new NotImplementedException("Lists can't be filtered yet."); } diff --git a/Realm/Realm/Handles/ResultsHandle.cs b/Realm/Realm/Handles/ResultsHandle.cs index ea96eae6f3..5fbaa4a1c1 100644 --- a/Realm/Realm/Handles/ResultsHandle.cs +++ b/Realm/Realm/Handles/ResultsHandle.cs @@ -62,7 +62,7 @@ private static class NativeMethods public static extern IntPtr snapshot(ResultsHandle results, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "results_get_filtered_results", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_filtered_results(ResultsHandle results, [MarshalAs(UnmanagedType.LPWStr)] string query_buf, IntPtr query_len, out NativeException ex); + public static extern IntPtr get_filtered_results(ResultsHandle results, [MarshalAs(UnmanagedType.LPWStr)] string query_buf, IntPtr query_len, PrimitiveValue[] arguments, int args_count, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "results_find_object", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr find_object(ResultsHandle results, ObjectHandle objectHandle, out NativeException ex); @@ -187,9 +187,23 @@ public override ResultsHandle Snapshot() return new ResultsHandle(this, ptr); } - public override ResultsHandle GetFilteredResults(string query) + public override ResultsHandle GetFilteredResults(string query, RealmValue[] arguments) { - var ptr = NativeMethods.get_filtered_results(this, query, (IntPtr)query.Length, out var ex); + PrimitiveValue[] nativeArgs = new PrimitiveValue[arguments.Length]; + RealmValue.HandlesToCleanup?[] nativeHandles = new RealmValue.HandlesToCleanup?[arguments.Length]; + for (int i = 0; i < arguments.Length; ++i) + { + var converted = arguments[i].ToNative(); + nativeArgs[i] = converted.Value; + nativeHandles[i] = converted.Handles; + } + + var ptr = NativeMethods.get_filtered_results(this, query, (IntPtr)query.Length, nativeArgs, nativeArgs.Length, out var ex); + foreach (var handle in nativeHandles) + { + handle?.Dispose(); + } + ex.ThrowIfNecessary(); return new ResultsHandle(this, ptr); } diff --git a/Realm/Realm/Handles/SetHandle.cs b/Realm/Realm/Handles/SetHandle.cs index 94414f813b..44ae8d09a2 100644 --- a/Realm/Realm/Handles/SetHandle.cs +++ b/Realm/Realm/Handles/SetHandle.cs @@ -130,7 +130,7 @@ public override ResultsHandle Snapshot() return new ResultsHandle(Root ?? this, ptr); } - public override ResultsHandle GetFilteredResults(string query) + public override ResultsHandle GetFilteredResults(string query, RealmValue[] arguments) { throw new NotImplementedException("Sets can't be filtered yet."); } diff --git a/wrappers/src/results_cs.cpp b/wrappers/src/results_cs.cpp index 904b525a34..1a157f135f 100644 --- a/wrappers/src/results_cs.cpp +++ b/wrappers/src/results_cs.cpp @@ -119,7 +119,7 @@ REALM_EXPORT DescriptorOrdering* results_get_descriptor_ordering(Results& result }); } -REALM_EXPORT Results* results_get_filtered_results(const Results& results, uint16_t* query_buf, size_t query_len, NativeException::Marshallable& ex) +REALM_EXPORT Results* results_get_filtered_results(const Results& results, uint16_t* query_buf, size_t query_len, realm_value_t arguments[], size_t args_count, NativeException::Marshallable& ex) { return handle_errors(ex, [&]() { Utf16StringAccessor query_string(query_buf, query_len); @@ -128,8 +128,18 @@ REALM_EXPORT Results* results_get_filtered_results(const Results& results, uint1 query_parser::KeyPathMapping mapping; realm::populate_keypath_mapping(mapping, *realm); - query_parser::NoArguments no_args; - Query parsed_query = results.get_table()->query(query_string, no_args, mapping); + std::vector vec_args; + for (int i = 0; i < args_count; ++i ) { + if (arguments[i].type != realm_value_type::RLM_TYPE_LINK) { + vec_args.push_back(from_capi(arguments[i])); + } + else { + vec_args.push_back(from_capi(arguments[i].link.object, true)); + } + } + + + Query parsed_query = results.get_table()->query(query_string, vec_args , mapping); DescriptorOrdering new_order = results.get_descriptor_ordering(); if (auto parsed_ordering = parsed_query.get_ordering()) { new_order.append(*parsed_ordering); From 775bc51815310aef71fd662b008fdc8772256914 Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Fri, 22 Jan 2021 14:16:56 +0100 Subject: [PATCH 19/44] All types have a unit test for string based search --- .../Realm/Extensions/CollectionExtensions.cs | 1 + Tests/Realm.Tests/Database/CollectionTests.cs | 193 +++++++++++++++++- Tests/Realm.Tests/Database/TestObjects.cs | 95 +++++++++ wrappers/src/results_cs.cpp | 3 +- 4 files changed, 288 insertions(+), 4 deletions(-) diff --git a/Realm/Realm/Extensions/CollectionExtensions.cs b/Realm/Realm/Extensions/CollectionExtensions.cs index 74cc1f1c72..f4ef4477ba 100644 --- a/Realm/Realm/Extensions/CollectionExtensions.cs +++ b/Realm/Realm/Extensions/CollectionExtensions.cs @@ -160,6 +160,7 @@ public static void Move(this IList list, int from, int to) /// A Queryable collection, obtained by calling . /// /// The predicate that will be applied. + /// Comma separated values used for substitution in the predicate. /// A queryable observable collection of objects that match the predicate. /// /// This method can be used in combination with LINQ filtering, but it is strongly recommended diff --git a/Tests/Realm.Tests/Database/CollectionTests.cs b/Tests/Realm.Tests/Database/CollectionTests.cs index 547ab303af..1895f95ac8 100644 --- a/Tests/Realm.Tests/Database/CollectionTests.cs +++ b/Tests/Realm.Tests/Database/CollectionTests.cs @@ -20,6 +20,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using MongoDB.Bson; using NUnit.Framework; using Realms.Exceptions; @@ -411,6 +412,194 @@ public void Results_GetFiltered_SanityTest() Assert.That(query.ToArray().All(i => i.Int >= 5)); } + [Test] + public void Bool_string_based_query() => Generic_string_based_query(); + + [Test] + public void Char_string_based_query() => Generic_string_based_query(); + + [Test] + public void SingleByte_string_based_query() => Generic_string_based_query(); + + [Test] + public void Int16_string_based_query() => Generic_string_based_query(); + + [Test] + public void Int_string_based_query() => Generic_string_based_query(); + + [Test] + public void Int64_string_based_query() => Generic_string_based_query(); + + [Test] + public void Float_string_based_query() => Generic_string_based_query(); + + [Test] + public void Double_string_based_query() => Generic_string_based_query(); + + [Test] + public void Date_string_based_query() => Generic_string_based_query(); + + [Test] + public void Decimal_string_based_query() => Generic_string_based_query(); + + [Test] + public void ObjectId_string_based_query() => Generic_string_based_query(); + + [Test] + public void Guid_string_based_query() => Generic_string_based_query(); + + [Test] + public void Byte_string_based_query() => Generic_string_based_query(); + + [Test] + public void String_string_based_query() => Generic_string_based_query(); + + [Test] + public void Object_string_based_query() => Generic_string_based_query(); + + [Test] + public void Multiple_values_string_based_query() => Generic_string_based_query(); + + private static InfoContainer[] InitArray() + { + var intProp = new IntPropertyObject { Int = 42 }; + + return new InfoContainer[] + { + new InfoContainer + ( + new BoolPropertyObject { Bool = true }, + "Bool == $0", + true + ), + new InfoContainer + ( + new CharPropertyObject { Char = 'c' }, + "Char == $0", + 'c' + ), + new InfoContainer + ( + new SingleBytePropertyObject { Byte = 0x5 }, + "Byte == $0", + 0x5 + ), + new InfoContainer + ( + new Int16PropertyObject { Short = 6 }, + "Short == $0", + 6 + ), + new InfoContainer + ( + new IntPropertyObject { Int = 5 }, + "Int == $0", + 5 + ), + new InfoContainer + ( + new Int64PropertyObject { Long = 5L }, + "Long == $0", + 5L + ), + new InfoContainer + ( + new FloatPropertyObject { Float = 5.0f }, + "Float == $0", + 5.0f + ), + new InfoContainer + ( + new DoublePropertyObject { Double = 5.0 }, + "Double == $0", + 5.0 + ), + new InfoContainer + ( + new DatePropertyObject { Date = new DateTimeOffset(1956, 6, 1, 0, 0, 0, TimeSpan.Zero) }, + "Date == $0", + new DateTimeOffset(1956, 6, 1, 0, 0, 0, TimeSpan.Zero) + ), + new InfoContainer + ( + new DecimalPropertyObject { Decimal = new Decimal128(564.42343424323) }, + "Decimal == $0", + new Decimal128(564.42343424323) + ), + new InfoContainer + ( + new ObjectIdPropertyObject { Id = new ObjectId("5f64cd9f1691c361b2451d96") }, + "Id == $0", + new ObjectId("5f64cd9f1691c361b2451d96") + ), + new InfoContainer + ( + new GuidPropertyObject { Id = new Guid("0f8fad5b-d9cb-469f-a165-70867728950e") }, + "Id == $0", + new Guid("0f8fad5b-d9cb-469f-a165-70867728950e") + ), + new InfoContainer + ( + new BytePropertyObject { Data = new byte[] { 0x5, 0x4, 0x3, 0x2, 0x1 } }, + "Data == $0", + new byte[] { 0x5, 0x4, 0x3, 0x2, 0x1 } + ), + new InfoContainer + ( + new StringPropertyObject { String = "hello world" }, + "String == $0", + "hello world" + ), + new InfoContainer + ( + new ObjectPropertyObject { Object = intProp }, + "Object == $0", + intProp + ), + new InfoContainer + ( + new MultiPropertyObject + { + String = "hello pp", + Int = 9, + Float = 21.0f, + Char = 'p' + }, + "Int == $0 && Float == $1 && Char == $2 && String == $3", + 9, 21.0f, 'p', "hello pp" + ) + }; + } + + private void Generic_string_based_query() + where T : RealmObject + { + var listToStringQuery = InitArray(); + + if (listToStringQuery.Length < 1) + { + Assert.Fail("listToStringQuery is empty. Nothing to test."); + } + + InfoContainer foundObj = listToStringQuery[0]; + _realm.Write(() => + { + for (int i = 0; i < listToStringQuery.Length; ++i) + { + var currObj = listToStringQuery[i]; + if (typeof(T) == currObj.Obj.GetType()) + { + foundObj = currObj; + } + + _realm.Add(currObj.Obj); + } + }); + + var query = _realm.All().Filter(foundObj.QueryString, foundObj.Value); + Assert.That(query.Single().Equals(foundObj.Obj)); + } + [Test] public void Results_GetFiltered_List() { @@ -713,7 +902,7 @@ public void Query_Freeze_ReturnsAFrozenCopy() _realm.Add(new Dog { Name = "Betazaurus" }); }); - var query = _realm.All().Where(d => d.Name.StartsWith("B")); + var query = _realm.All().Where(d => d.Name.StartsWith("B", StringComparison.Ordinal)); var frozenQuery = Freeze(query); Assert.That(query.AsRealmCollection().IsValid); @@ -900,7 +1089,7 @@ public void FrozenQuery_WhenFiltered_DoesntChange() _realm.Add(new Dog { Name = "Lasse" }); }); - var frozenQuery = _realm.All().Where(d => d.Name.StartsWith("R")).Freeze(); + var frozenQuery = _realm.All().Where(d => d.Name.StartsWith("R", StringComparison.Ordinal)).Freeze(); Assert.That(frozenQuery.Count(), Is.EqualTo(2)); _realm.Write(() => diff --git a/Tests/Realm.Tests/Database/TestObjects.cs b/Tests/Realm.Tests/Database/TestObjects.cs index 4be8bfded7..d1581c6c5a 100644 --- a/Tests/Realm.Tests/Database/TestObjects.cs +++ b/Tests/Realm.Tests/Database/TestObjects.cs @@ -488,11 +488,106 @@ public class ContainerObject : RealmObject public IList Items { get; } } + public struct InfoContainer + { + public RealmObject Obj; + public string QueryString; + public RealmValue[] Value; + + public InfoContainer(RealmObject obj, string query, params RealmValue[] args) + { + Obj = obj; + QueryString = query; + Value = args; + } + } + + public class BoolPropertyObject : RealmObject + { + public bool Bool { get; set; } + } + + public class CharPropertyObject : RealmObject + { + public char Char { get; set; } + } + + public class SingleBytePropertyObject : RealmObject + { + public byte Byte { get; set; } + } + + public class Int16PropertyObject : RealmObject + { + public short Short { get; set; } + } + public class IntPropertyObject : RealmObject { public int Int { get; set; } } + public class Int64PropertyObject : RealmObject + { + public long Long { get; set; } + } + + public class FloatPropertyObject : RealmObject + { + public float Float { get; set; } + } + + public class DoublePropertyObject : RealmObject + { + public double Double { get; set; } + } + + public class DatePropertyObject : RealmObject + { + public DateTimeOffset Date { get; set; } + } + + public class DecimalPropertyObject : RealmObject + { + public Decimal128 Decimal { get; set; } + } + + public class ObjectIdPropertyObject : RealmObject + { + public ObjectId Id { get; set; } + } + + public class GuidPropertyObject : RealmObject + { + public Guid Id { get; set; } + } + + public class BytePropertyObject : RealmObject + { + public byte[] Data { get; set; } + } + + public class StringPropertyObject : RealmObject + { + public string String { get; set; } + } + + public class ObjectPropertyObject : RealmObject + { + public IntPropertyObject Object { get; set; } + } + + public class MultiPropertyObject : RealmObject + { + public string String { get; set; } + + public int Int { get; set; } + + public float Float { get; set; } + + public char Char { get; set; } + } + public class RecursiveBacklinksObject : RealmObject { public int Id { get; set; } diff --git a/wrappers/src/results_cs.cpp b/wrappers/src/results_cs.cpp index 1a157f135f..9882ff3d8f 100644 --- a/wrappers/src/results_cs.cpp +++ b/wrappers/src/results_cs.cpp @@ -134,11 +134,10 @@ REALM_EXPORT Results* results_get_filtered_results(const Results& results, uint1 vec_args.push_back(from_capi(arguments[i])); } else { - vec_args.push_back(from_capi(arguments[i].link.object, true)); + vec_args.push_back(from_capi(arguments[i].link.object, false)); } } - Query parsed_query = results.get_table()->query(query_string, vec_args , mapping); DescriptorOrdering new_order = results.get_descriptor_ordering(); if (auto parsed_ordering = parsed_query.get_ordering()) { From c9fc593724043f43ce9392431e6901ca4b8d0b6d Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Fri, 22 Jan 2021 14:55:16 +0100 Subject: [PATCH 20/44] Changelog updated --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index afa86224f6..722cc52bb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Enhancements * Add support for the `GUID` data type. It can be used as primary key and is indexable. (PR [#2120](https://github.com/realm/realm-dotnet/pull/2120)) * Replaced the implementation of the string query parser (the one used for [`realm.All().Filter("some-string-query")`](https://docs.mongodb.com/realm-sdks/dotnet/10.0.0-beta.3/reference/Realms.CollectionExtensions.html#Realms_CollectionExtensions_Filter__1_System_Linq_IQueryable___0__System_String_)). This results in ~5% reduction of the size of the native binary while keeping the query execution times on par with the old parser. (PR [#2185](https://github.com/realm/realm-dotnet/pull/2185), Core upgrade) +* Added support for value substitution in string based queries. So [`realm.All().Filter("field1 = $0 && field2 = $1", value1, value2)`](https://github.com/realm/realm-js/blob/master/docs/tutorials/query-language.md) is now possible ### Compatibility * Realm Studio: 10.0.0 or later. From a63bd5cccbddc7555a1f6248839c6934c43071db Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Fri, 22 Jan 2021 14:56:59 +0100 Subject: [PATCH 21/44] Added object store to gitignore and set lf as ending line in code style rules --- .editorconfig | 2 +- .gitignore | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index aece93547a..adba3374bb 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,7 +12,7 @@ indent_style = space tab_width = 4 # New line preferences -end_of_line = crlf +end_of_line = lf insert_final_newline = false #### .NET Coding Conventions #### diff --git a/.gitignore b/.gitignore index fd56047041..54e34224eb 100644 --- a/.gitignore +++ b/.gitignore @@ -198,3 +198,4 @@ Realm/Realm.Unity/Runtime/Dependencies/*.meta Realm/Realm.Unity/Runtime/Dependencies.meta Tests/PerformanceTests/BenchmarkDotNet.Artifacts/ Realm/Realm.Unity/**/*.tgz +wrappers/src/object-store/ From 0f0be4fb7c4a2e4509a6de12380cf079a7d09d2e Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Fri, 22 Jan 2021 15:11:01 +0100 Subject: [PATCH 22/44] Changed style of object instantiation in array --- Tests/Realm.Tests/Database/CollectionTests.cs | 118 +++--------------- 1 file changed, 16 insertions(+), 102 deletions(-) diff --git a/Tests/Realm.Tests/Database/CollectionTests.cs b/Tests/Realm.Tests/Database/CollectionTests.cs index 1895f95ac8..e8ea52b125 100644 --- a/Tests/Realm.Tests/Database/CollectionTests.cs +++ b/Tests/Realm.Tests/Database/CollectionTests.cs @@ -466,108 +466,22 @@ private static InfoContainer[] InitArray() return new InfoContainer[] { - new InfoContainer - ( - new BoolPropertyObject { Bool = true }, - "Bool == $0", - true - ), - new InfoContainer - ( - new CharPropertyObject { Char = 'c' }, - "Char == $0", - 'c' - ), - new InfoContainer - ( - new SingleBytePropertyObject { Byte = 0x5 }, - "Byte == $0", - 0x5 - ), - new InfoContainer - ( - new Int16PropertyObject { Short = 6 }, - "Short == $0", - 6 - ), - new InfoContainer - ( - new IntPropertyObject { Int = 5 }, - "Int == $0", - 5 - ), - new InfoContainer - ( - new Int64PropertyObject { Long = 5L }, - "Long == $0", - 5L - ), - new InfoContainer - ( - new FloatPropertyObject { Float = 5.0f }, - "Float == $0", - 5.0f - ), - new InfoContainer - ( - new DoublePropertyObject { Double = 5.0 }, - "Double == $0", - 5.0 - ), - new InfoContainer - ( - new DatePropertyObject { Date = new DateTimeOffset(1956, 6, 1, 0, 0, 0, TimeSpan.Zero) }, - "Date == $0", - new DateTimeOffset(1956, 6, 1, 0, 0, 0, TimeSpan.Zero) - ), - new InfoContainer - ( - new DecimalPropertyObject { Decimal = new Decimal128(564.42343424323) }, - "Decimal == $0", - new Decimal128(564.42343424323) - ), - new InfoContainer - ( - new ObjectIdPropertyObject { Id = new ObjectId("5f64cd9f1691c361b2451d96") }, - "Id == $0", - new ObjectId("5f64cd9f1691c361b2451d96") - ), - new InfoContainer - ( - new GuidPropertyObject { Id = new Guid("0f8fad5b-d9cb-469f-a165-70867728950e") }, - "Id == $0", - new Guid("0f8fad5b-d9cb-469f-a165-70867728950e") - ), - new InfoContainer - ( - new BytePropertyObject { Data = new byte[] { 0x5, 0x4, 0x3, 0x2, 0x1 } }, - "Data == $0", - new byte[] { 0x5, 0x4, 0x3, 0x2, 0x1 } - ), - new InfoContainer - ( - new StringPropertyObject { String = "hello world" }, - "String == $0", - "hello world" - ), - new InfoContainer - ( - new ObjectPropertyObject { Object = intProp }, - "Object == $0", - intProp - ), - new InfoContainer - ( - new MultiPropertyObject - { - String = "hello pp", - Int = 9, - Float = 21.0f, - Char = 'p' - }, - "Int == $0 && Float == $1 && Char == $2 && String == $3", - 9, 21.0f, 'p', "hello pp" - ) + new InfoContainer(new BoolPropertyObject { Bool = true }, "Bool == $0", true), + new InfoContainer(new CharPropertyObject { Char = 'c' }, "Char == $0", 'c'), + new InfoContainer(new SingleBytePropertyObject { Byte = 0x5 }, "Byte == $0", 0x5), + new InfoContainer(new Int16PropertyObject { Short = 6 }, "Short == $0", 6), + new InfoContainer(new IntPropertyObject { Int = 5 }, "Int == $0", 5), + new InfoContainer(new Int64PropertyObject { Long = 5L }, "Long == $0", 5L), + new InfoContainer(new FloatPropertyObject { Float = 5.0f }, "Float == $0", 5.0f), + new InfoContainer(new DoublePropertyObject { Double = 5.0 }, "Double == $0", 5.0), + new InfoContainer(new DatePropertyObject { Date = new DateTimeOffset(1956, 6, 1, 0, 0, 0, TimeSpan.Zero) }, "Date == $0", new DateTimeOffset(1956, 6, 1, 0, 0, 0, TimeSpan.Zero)), + new InfoContainer(new DecimalPropertyObject { Decimal = new Decimal128(564.42343424323) }, "Decimal == $0", new Decimal128(564.42343424323)), + new InfoContainer(new ObjectIdPropertyObject { Id = new ObjectId("5f64cd9f1691c361b2451d96") }, "Id == $0", new ObjectId("5f64cd9f1691c361b2451d96")), + new InfoContainer(new GuidPropertyObject { Id = new Guid("0f8fad5b-d9cb-469f-a165-70867728950e") }, "Id == $0", new Guid("0f8fad5b-d9cb-469f-a165-70867728950e")), + new InfoContainer(new BytePropertyObject { Data = new byte[] { 0x5, 0x4, 0x3, 0x2, 0x1 } }, "Data == $0", new byte[] { 0x5, 0x4, 0x3, 0x2, 0x1 }), + new InfoContainer(new StringPropertyObject { String = "hello world" }, "String == $0", "hello world"), + new InfoContainer(new ObjectPropertyObject { Object = intProp }, "Object == $0", intProp), + new InfoContainer(new MultiPropertyObject { String = "hello pp", Int = 9, Float = 21.0f, Char = 'p' }, "Int == $0 && Float == $1 && Char == $2 && String == $3", 9, 21.0f, 'p', "hello pp") }; } From faf5d2b372135af2093cf16c6b93bab58885524b Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Fri, 22 Jan 2021 16:38:17 +0100 Subject: [PATCH 23/44] Added unit test for in memory object in string based query --- Tests/Realm.Tests/Database/CollectionTests.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Tests/Realm.Tests/Database/CollectionTests.cs b/Tests/Realm.Tests/Database/CollectionTests.cs index e8ea52b125..265659007a 100644 --- a/Tests/Realm.Tests/Database/CollectionTests.cs +++ b/Tests/Realm.Tests/Database/CollectionTests.cs @@ -460,6 +460,18 @@ public void Results_GetFiltered_SanityTest() [Test] public void Multiple_values_string_based_query() => Generic_string_based_query(); + [Test] + public void In_Memory_String_Based_Query_Fails() + { + _realm.Write(() => + { + _realm.Add(new ObjectPropertyObject { Object = new IntPropertyObject { Int = 5 } }); + }); + + var query = _realm.All().Filter("Object = $0", new IntPropertyObject { Int = 5 }); + Assert.That(query.Count, Is.EqualTo(0)); + } + private static InfoContainer[] InitArray() { var intProp = new IntPropertyObject { Int = 42 }; From 6d06ad65419debcfcd3378c74fc0ef2843441214 Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Fri, 22 Jan 2021 16:49:18 +0100 Subject: [PATCH 24/44] Renamed tests to be more coherent with the chosen style --- Tests/Realm.Tests/Database/CollectionTests.cs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Tests/Realm.Tests/Database/CollectionTests.cs b/Tests/Realm.Tests/Database/CollectionTests.cs index 265659007a..ee7d3f4afe 100644 --- a/Tests/Realm.Tests/Database/CollectionTests.cs +++ b/Tests/Realm.Tests/Database/CollectionTests.cs @@ -413,55 +413,55 @@ public void Results_GetFiltered_SanityTest() } [Test] - public void Bool_string_based_query() => Generic_string_based_query(); + public void Bool_StringBasedQuery() => Generic_StringBasedQuery(); [Test] - public void Char_string_based_query() => Generic_string_based_query(); + public void Char_StringBasedQuery() => Generic_StringBasedQuery(); [Test] - public void SingleByte_string_based_query() => Generic_string_based_query(); + public void SingleByte_StringBasedQuery() => Generic_StringBasedQuery(); [Test] - public void Int16_string_based_query() => Generic_string_based_query(); + public void Int16_StringBasedQuery() => Generic_StringBasedQuery(); [Test] - public void Int_string_based_query() => Generic_string_based_query(); + public void Int_StringBasedQuery() => Generic_StringBasedQuery(); [Test] - public void Int64_string_based_query() => Generic_string_based_query(); + public void Int64_StringBasedQuery() => Generic_StringBasedQuery(); [Test] - public void Float_string_based_query() => Generic_string_based_query(); + public void Float_StringBasedQuery() => Generic_StringBasedQuery(); [Test] - public void Double_string_based_query() => Generic_string_based_query(); + public void Double_StringBasedQuery() => Generic_StringBasedQuery(); [Test] - public void Date_string_based_query() => Generic_string_based_query(); + public void Date_StringBasedQuery() => Generic_StringBasedQuery(); [Test] - public void Decimal_string_based_query() => Generic_string_based_query(); + public void Decimal_StringBasedQuery() => Generic_StringBasedQuery(); [Test] - public void ObjectId_string_based_query() => Generic_string_based_query(); + public void ObjectId_StringBasedQuery() => Generic_StringBasedQuery(); [Test] - public void Guid_string_based_query() => Generic_string_based_query(); + public void Guid_StringBasedQuery() => Generic_StringBasedQuery(); [Test] - public void Byte_string_based_query() => Generic_string_based_query(); + public void Byte_StringBasedQuery() => Generic_StringBasedQuery(); [Test] - public void String_string_based_query() => Generic_string_based_query(); + public void String_StringBasedQuery() => Generic_StringBasedQuery(); [Test] - public void Object_string_based_query() => Generic_string_based_query(); + public void Object_StringBasedQuery() => Generic_StringBasedQuery(); [Test] - public void Multiple_values_string_based_query() => Generic_string_based_query(); + public void MultipleValues_StringBasedQuery() => Generic_StringBasedQuery(); [Test] - public void In_Memory_String_Based_Query_Fails() + public void InMemoryObject_StringBasedQuery_ShouldFindNone() { _realm.Write(() => { @@ -497,7 +497,7 @@ private static InfoContainer[] InitArray() }; } - private void Generic_string_based_query() + private void Generic_StringBasedQuery() where T : RealmObject { var listToStringQuery = InitArray(); From 943be4f5f7cfd742da0d467920c51b4c73c4244d Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Fri, 22 Jan 2021 18:02:43 +0100 Subject: [PATCH 25/44] Improved some code style --- Realm/Realm/Handles/ResultsHandle.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Realm/Realm/Handles/ResultsHandle.cs b/Realm/Realm/Handles/ResultsHandle.cs index 5fbaa4a1c1..ff56f25053 100644 --- a/Realm/Realm/Handles/ResultsHandle.cs +++ b/Realm/Realm/Handles/ResultsHandle.cs @@ -17,6 +17,7 @@ //////////////////////////////////////////////////////////////////////////// using System; +using System.Linq; using System.Runtime.InteropServices; using Realms.Native; @@ -189,19 +190,16 @@ public override ResultsHandle Snapshot() public override ResultsHandle GetFilteredResults(string query, RealmValue[] arguments) { - PrimitiveValue[] nativeArgs = new PrimitiveValue[arguments.Length]; - RealmValue.HandlesToCleanup?[] nativeHandles = new RealmValue.HandlesToCleanup?[arguments.Length]; + var nativeArgs = new (PrimitiveValue Value, RealmValue.HandlesToCleanup? Handles)[arguments.Length]; for (int i = 0; i < arguments.Length; ++i) { - var converted = arguments[i].ToNative(); - nativeArgs[i] = converted.Value; - nativeHandles[i] = converted.Handles; + nativeArgs[i] = arguments[i].ToNative(); } - var ptr = NativeMethods.get_filtered_results(this, query, (IntPtr)query.Length, nativeArgs, nativeArgs.Length, out var ex); - foreach (var handle in nativeHandles) + var ptr = NativeMethods.get_filtered_results(this, query, (IntPtr)query.Length, nativeArgs.Select(x => x.Value).ToArray(), nativeArgs.Length, out var ex); + foreach (var arg in nativeArgs) { - handle?.Dispose(); + arg.Handles?.Dispose(); } ex.ThrowIfNecessary(); From e7ad03e165a5520384f14bdd7f36812af2d14712 Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Mon, 25 Jan 2021 11:36:51 +0100 Subject: [PATCH 26/44] Added comment to explain PrimitiveValue <--> realm_value_t relationship --- Realm/Realm/Native/PrimitiveValue.cs | 1 + wrappers/src/marshalling.hpp | 1 + 2 files changed, 2 insertions(+) diff --git a/Realm/Realm/Native/PrimitiveValue.cs b/Realm/Realm/Native/PrimitiveValue.cs index 4d4631b5b9..6bca3e05b6 100644 --- a/Realm/Realm/Native/PrimitiveValue.cs +++ b/Realm/Realm/Native/PrimitiveValue.cs @@ -24,6 +24,7 @@ namespace Realms.Native { + // This type is marshalled through C++ wrappers' realm_value_t [StructLayout(LayoutKind.Explicit)] [DebuggerDisplay("PrimitiveValue({Type})")] internal unsafe struct PrimitiveValue diff --git a/wrappers/src/marshalling.hpp b/wrappers/src/marshalling.hpp index 99a96c66c3..6e388e3557 100644 --- a/wrappers/src/marshalling.hpp +++ b/wrappers/src/marshalling.hpp @@ -74,6 +74,7 @@ typedef struct realm_uuid { uint8_t bytes[16]; } realm_uuid_t; +// This struct is used to marshall C#'s type PrimitiveValue typedef struct realm_value { union { int64_t integer; From 7a1fe0b39ccaeac3dba0ae2ada5947c282cc3501 Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Mon, 25 Jan 2021 12:27:00 +0100 Subject: [PATCH 27/44] Renamed a var --- wrappers/src/results_cs.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wrappers/src/results_cs.cpp b/wrappers/src/results_cs.cpp index 9882ff3d8f..ffb8b513b4 100644 --- a/wrappers/src/results_cs.cpp +++ b/wrappers/src/results_cs.cpp @@ -128,17 +128,17 @@ REALM_EXPORT Results* results_get_filtered_results(const Results& results, uint1 query_parser::KeyPathMapping mapping; realm::populate_keypath_mapping(mapping, *realm); - std::vector vec_args; + std::vector mixed_args; for (int i = 0; i < args_count; ++i ) { if (arguments[i].type != realm_value_type::RLM_TYPE_LINK) { - vec_args.push_back(from_capi(arguments[i])); + mixed_args.push_back(from_capi(arguments[i])); } else { - vec_args.push_back(from_capi(arguments[i].link.object, false)); + mixed_args.push_back(from_capi(arguments[i].link.object, false)); } } - Query parsed_query = results.get_table()->query(query_string, vec_args , mapping); + Query parsed_query = results.get_table()->query(query_string, mixed_args , mapping); DescriptorOrdering new_order = results.get_descriptor_ordering(); if (auto parsed_ordering = parsed_query.get_ordering()) { new_order.append(*parsed_ordering); From 7ccc215933b4a9992d9367bde5fa25b5ba41d588 Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Mon, 25 Jan 2021 17:25:58 +0100 Subject: [PATCH 28/44] Fixed changelog and gitignore --- .gitignore | 1 - CHANGELOG.md | 6 +----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 54e34224eb..fd56047041 100644 --- a/.gitignore +++ b/.gitignore @@ -198,4 +198,3 @@ Realm/Realm.Unity/Runtime/Dependencies/*.meta Realm/Realm.Unity/Runtime/Dependencies.meta Tests/PerformanceTests/BenchmarkDotNet.Artifacts/ Realm/Realm.Unity/**/*.tgz -wrappers/src/object-store/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 10792b8c0b..d6caccb572 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Enhancements * Add support for the `GUID` data type. It can be used as primary key and is indexable. (PR [#2120](https://github.com/realm/realm-dotnet/pull/2120)) +* Added support for value substitution in string based queries. So [`realm.All().Filter("field1 = $0 && field2 = $1", value1, value2)`](https://github.com/realm/realm-js/blob/master/docs/tutorials/query-language.md) is now possible ### Compatibility * Realm Studio: 10.0.0 or later. @@ -29,17 +30,12 @@ * Fix a race condition which would lead to "uncaught exception in notifier thread: N5realm15InvalidTableRefE: transaction_ended" and a crash when the source Realm was closed or invalidated at a very specific time during the first run of a collection notifier (Core upgrade) ### Enhancements -* Add support for the `GUID` data type. It can be used as primary key and is indexable. (PR [#2120](https://github.com/realm/realm-dotnet/pull/2120)) -* Replaced the implementation of the string query parser (the one used for [`realm.All().Filter("some-string-query")`](https://docs.mongodb.com/realm-sdks/dotnet/10.0.0-beta.3/reference/Realms.CollectionExtensions.html#Realms_CollectionExtensions_Filter__1_System_Linq_IQueryable___0__System_String_)). This results in ~5% reduction of the size of the native binary while keeping the query execution times on par with the old parser. (PR [#2185](https://github.com/realm/realm-dotnet/pull/2185), Core upgrade) -* Added support for value substitution in string based queries. So [`realm.All().Filter("field1 = $0 && field2 = $1", value1, value2)`](https://github.com/realm/realm-js/blob/master/docs/tutorials/query-language.md) is now possible * Replaced the implementation of the string query parser (the one used for [`realm.All().Filter("some-string-query")`](https://docs.mongodb.com/realm-sdks/dotnet/10.0.0-beta.3/reference/Realms.CollectionExtensions.html#Realms_CollectionExtensions_Filter__1_System_Linq_IQueryable___0__System_String_)). This results in ~5% reduction of the size of the native binary while keeping the query execution times on par with the old parser. (PR [#2185](https://github.com/realm/realm-dotnet/pull/2185), Core upgrade) ### Compatibility * Realm Studio: 10.0.0 or later. ### Internal -* Using Core 10.3.0. -* Migrate to bison parser * Using Core 10.3.3. * Migrated to bison parser. From 5ac3b31a6fce0bdaf84b1ca2d69cce0a7e0c00c2 Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Mon, 25 Jan 2021 18:23:19 +0100 Subject: [PATCH 29/44] Applied feedback from PR --- Realm/Realm/Handles/ResultsHandle.cs | 11 +- Tests/Realm.Tests/Database/CollectionTests.cs | 126 ++++++++++-------- Tests/Realm.Tests/Database/TestObjects.cs | 14 -- 3 files changed, 76 insertions(+), 75 deletions(-) diff --git a/Realm/Realm/Handles/ResultsHandle.cs b/Realm/Realm/Handles/ResultsHandle.cs index ff56f25053..f4a9188c0e 100644 --- a/Realm/Realm/Handles/ResultsHandle.cs +++ b/Realm/Realm/Handles/ResultsHandle.cs @@ -190,16 +190,17 @@ public override ResultsHandle Snapshot() public override ResultsHandle GetFilteredResults(string query, RealmValue[] arguments) { - var nativeArgs = new (PrimitiveValue Value, RealmValue.HandlesToCleanup? Handles)[arguments.Length]; + var primitiveValues = new PrimitiveValue[arguments.Length]; + var handles = new RealmValue.HandlesToCleanup?[arguments.Length]; for (int i = 0; i < arguments.Length; ++i) { - nativeArgs[i] = arguments[i].ToNative(); + (primitiveValues[i], handles[i]) = arguments[i].ToNative(); } - var ptr = NativeMethods.get_filtered_results(this, query, (IntPtr)query.Length, nativeArgs.Select(x => x.Value).ToArray(), nativeArgs.Length, out var ex); - foreach (var arg in nativeArgs) + var ptr = NativeMethods.get_filtered_results(this, query, (IntPtr)query.Length, primitiveValues, primitiveValues.Length, out var ex); + foreach (var handle in handles) { - arg.Handles?.Dispose(); + handle?.Dispose(); } ex.ThrowIfNecessary(); diff --git a/Tests/Realm.Tests/Database/CollectionTests.cs b/Tests/Realm.Tests/Database/CollectionTests.cs index ee7d3f4afe..16974c39b4 100644 --- a/Tests/Realm.Tests/Database/CollectionTests.cs +++ b/Tests/Realm.Tests/Database/CollectionTests.cs @@ -472,60 +472,6 @@ public void InMemoryObject_StringBasedQuery_ShouldFindNone() Assert.That(query.Count, Is.EqualTo(0)); } - private static InfoContainer[] InitArray() - { - var intProp = new IntPropertyObject { Int = 42 }; - - return new InfoContainer[] - { - new InfoContainer(new BoolPropertyObject { Bool = true }, "Bool == $0", true), - new InfoContainer(new CharPropertyObject { Char = 'c' }, "Char == $0", 'c'), - new InfoContainer(new SingleBytePropertyObject { Byte = 0x5 }, "Byte == $0", 0x5), - new InfoContainer(new Int16PropertyObject { Short = 6 }, "Short == $0", 6), - new InfoContainer(new IntPropertyObject { Int = 5 }, "Int == $0", 5), - new InfoContainer(new Int64PropertyObject { Long = 5L }, "Long == $0", 5L), - new InfoContainer(new FloatPropertyObject { Float = 5.0f }, "Float == $0", 5.0f), - new InfoContainer(new DoublePropertyObject { Double = 5.0 }, "Double == $0", 5.0), - new InfoContainer(new DatePropertyObject { Date = new DateTimeOffset(1956, 6, 1, 0, 0, 0, TimeSpan.Zero) }, "Date == $0", new DateTimeOffset(1956, 6, 1, 0, 0, 0, TimeSpan.Zero)), - new InfoContainer(new DecimalPropertyObject { Decimal = new Decimal128(564.42343424323) }, "Decimal == $0", new Decimal128(564.42343424323)), - new InfoContainer(new ObjectIdPropertyObject { Id = new ObjectId("5f64cd9f1691c361b2451d96") }, "Id == $0", new ObjectId("5f64cd9f1691c361b2451d96")), - new InfoContainer(new GuidPropertyObject { Id = new Guid("0f8fad5b-d9cb-469f-a165-70867728950e") }, "Id == $0", new Guid("0f8fad5b-d9cb-469f-a165-70867728950e")), - new InfoContainer(new BytePropertyObject { Data = new byte[] { 0x5, 0x4, 0x3, 0x2, 0x1 } }, "Data == $0", new byte[] { 0x5, 0x4, 0x3, 0x2, 0x1 }), - new InfoContainer(new StringPropertyObject { String = "hello world" }, "String == $0", "hello world"), - new InfoContainer(new ObjectPropertyObject { Object = intProp }, "Object == $0", intProp), - new InfoContainer(new MultiPropertyObject { String = "hello pp", Int = 9, Float = 21.0f, Char = 'p' }, "Int == $0 && Float == $1 && Char == $2 && String == $3", 9, 21.0f, 'p', "hello pp") - }; - } - - private void Generic_StringBasedQuery() - where T : RealmObject - { - var listToStringQuery = InitArray(); - - if (listToStringQuery.Length < 1) - { - Assert.Fail("listToStringQuery is empty. Nothing to test."); - } - - InfoContainer foundObj = listToStringQuery[0]; - _realm.Write(() => - { - for (int i = 0; i < listToStringQuery.Length; ++i) - { - var currObj = listToStringQuery[i]; - if (typeof(T) == currObj.Obj.GetType()) - { - foundObj = currObj; - } - - _realm.Add(currObj.Obj); - } - }); - - var query = _realm.All().Filter(foundObj.QueryString, foundObj.Value); - Assert.That(query.Single().Equals(foundObj.Obj)); - } - [Test] public void Results_GetFiltered_List() { @@ -828,7 +774,7 @@ public void Query_Freeze_ReturnsAFrozenCopy() _realm.Add(new Dog { Name = "Betazaurus" }); }); - var query = _realm.All().Where(d => d.Name.StartsWith("B", StringComparison.Ordinal)); + var query = _realm.All().Where(d => d.Name.StartsWith("B")); var frozenQuery = Freeze(query); Assert.That(query.AsRealmCollection().IsValid); @@ -1015,7 +961,7 @@ public void FrozenQuery_WhenFiltered_DoesntChange() _realm.Add(new Dog { Name = "Lasse" }); }); - var frozenQuery = _realm.All().Where(d => d.Name.StartsWith("R", StringComparison.Ordinal)).Freeze(); + var frozenQuery = _realm.All().Where(d => d.Name.StartsWith("R")).Freeze(); Assert.That(frozenQuery.Count(), Is.EqualTo(2)); _realm.Write(() => @@ -1093,5 +1039,73 @@ private class B : RealmObject { public IntPropertyObject C { get; set; } } + + public struct InfoContainer + { + public RealmObject Obj; + public string QueryString; + public RealmValue[] Value; + + public InfoContainer(RealmObject obj, string query, params RealmValue[] args) + { + Obj = obj; + QueryString = query; + Value = args; + } + } + + private static InfoContainer[] InitArray() + { + var intProp = new IntPropertyObject { Int = 42 }; + + return new InfoContainer[] + { + new InfoContainer(new BoolPropertyObject { Bool = true }, "Bool == $0", true), + new InfoContainer(new CharPropertyObject { Char = 'c' }, "Char == $0", 'c'), + new InfoContainer(new SingleBytePropertyObject { Byte = 0x5 }, "Byte == $0", 0x5), + new InfoContainer(new Int16PropertyObject { Short = 6 }, "Short == $0", 6), + new InfoContainer(new IntPropertyObject { Int = 5 }, "Int == $0", 5), + new InfoContainer(new Int64PropertyObject { Long = 5L }, "Long == $0", 5L), + new InfoContainer(new FloatPropertyObject { Float = 5.0f }, "Float == $0", 5.0f), + new InfoContainer(new DoublePropertyObject { Double = 5.0 }, "Double == $0", 5.0), + new InfoContainer(new DatePropertyObject { Date = new DateTimeOffset(1956, 6, 1, 0, 0, 0, TimeSpan.Zero) }, "Date == $0", new DateTimeOffset(1956, 6, 1, 0, 0, 0, TimeSpan.Zero)), + new InfoContainer(new DecimalPropertyObject { Decimal = new Decimal128(564.42343424323) }, "Decimal == $0", new Decimal128(564.42343424323)), + new InfoContainer(new ObjectIdPropertyObject { Id = new ObjectId("5f64cd9f1691c361b2451d96") }, "Id == $0", new ObjectId("5f64cd9f1691c361b2451d96")), + new InfoContainer(new GuidPropertyObject { Id = new Guid("0f8fad5b-d9cb-469f-a165-70867728950e") }, "Id == $0", new Guid("0f8fad5b-d9cb-469f-a165-70867728950e")), + new InfoContainer(new BytePropertyObject { Data = new byte[] { 0x5, 0x4, 0x3, 0x2, 0x1 } }, "Data == $0", new byte[] { 0x5, 0x4, 0x3, 0x2, 0x1 }), + new InfoContainer(new StringPropertyObject { String = "hello world" }, "String == $0", "hello world"), + new InfoContainer(new ObjectPropertyObject { Object = intProp }, "Object == $0", intProp), + new InfoContainer(new MultiPropertyObject { String = "hello pp", Int = 9, Float = 21.0f, Char = 'p' }, "Int == $0 && Float == $1 && Char == $2 && String == $3", 9, 21.0f, 'p', "hello pp") + }; + } + + private void Generic_StringBasedQuery() + where T : RealmObject + { + var listToStringQuery = InitArray(); + + if (listToStringQuery.Length < 1) + { + Assert.Fail("listToStringQuery is empty. Nothing to test."); + } + + InfoContainer foundObj = listToStringQuery[0]; + _realm.Write(() => + { + for (int i = 0; i < listToStringQuery.Length; ++i) + { + var currObj = listToStringQuery[i]; + if (typeof(T) == currObj.Obj.GetType()) + { + foundObj = currObj; + } + + _realm.Add(currObj.Obj); + } + }); + + var query = _realm.All().Filter(foundObj.QueryString, foundObj.Value); + Assert.That(query.Single().Equals(foundObj.Obj)); + } } } diff --git a/Tests/Realm.Tests/Database/TestObjects.cs b/Tests/Realm.Tests/Database/TestObjects.cs index d1581c6c5a..1c5332a066 100644 --- a/Tests/Realm.Tests/Database/TestObjects.cs +++ b/Tests/Realm.Tests/Database/TestObjects.cs @@ -488,20 +488,6 @@ public class ContainerObject : RealmObject public IList Items { get; } } - public struct InfoContainer - { - public RealmObject Obj; - public string QueryString; - public RealmValue[] Value; - - public InfoContainer(RealmObject obj, string query, params RealmValue[] args) - { - Obj = obj; - QueryString = query; - Value = args; - } - } - public class BoolPropertyObject : RealmObject { public bool Bool { get; set; } From ea52b5ead53b8acada4393aba8346c11283c94a9 Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Wed, 27 Jan 2021 15:35:10 +0100 Subject: [PATCH 30/44] Applied general feedback from PR --- CHANGELOG.md | 2 +- Realm/Realm/Extensions/CollectionExtensions.cs | 4 +++- Realm/Realm/Handles/ResultsHandle.cs | 10 ++++++---- Tests/Realm.Tests/GlobalSuppressions.cs | 1 + wrappers/src/results_cs.cpp | 7 ++++--- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6caccb572..4fd6637c44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ ### Enhancements * Add support for the `GUID` data type. It can be used as primary key and is indexable. (PR [#2120](https://github.com/realm/realm-dotnet/pull/2120)) -* Added support for value substitution in string based queries. So [`realm.All().Filter("field1 = $0 && field2 = $1", value1, value2)`](https://github.com/realm/realm-js/blob/master/docs/tutorials/query-language.md) is now possible +* Added support for value substitution in string based queries. This enables expressions following [this syntax](https://github.com/realm/realm-js/blob/master/docs/tutorials/query-language.md): `realm.All().Filter("field1 = $0 && field2 = $1", 123, "some-string-value")`. (Issue [#1822](https://github.com/realm/realm-dotnet/issues/1822)) ### Compatibility * Realm Studio: 10.0.0 or later. diff --git a/Realm/Realm/Extensions/CollectionExtensions.cs b/Realm/Realm/Extensions/CollectionExtensions.cs index f4ef4477ba..ad3e3eecf9 100644 --- a/Realm/Realm/Extensions/CollectionExtensions.cs +++ b/Realm/Realm/Extensions/CollectionExtensions.cs @@ -160,7 +160,7 @@ public static void Move(this IList list, int from, int to) /// A Queryable collection, obtained by calling . /// /// The predicate that will be applied. - /// Comma separated values used for substitution in the predicate. + /// Values used for substitution in the predicate. Note that all primitive types are accepted as they are implicitly converted to RealmValue. /// A queryable observable collection of objects that match the predicate. /// /// This method can be used in combination with LINQ filtering, but it is strongly recommended @@ -174,6 +174,7 @@ public static void Move(this IList list, int from, int to) /// var results1 = realm.All<Foo>("Bar.IntValue > 0"); /// var results2 = realm.All<Foo>("Bar.IntValue > 0 SORT(Bar.IntValue ASC Bar.StringValue DESC)"); /// var results3 = realm.All<Foo>("Bar.IntValue > 0 SORT(Bar.IntValue ASC Bar.StringValue DESC) DISTINCT(Bar.IntValue)"); + /// var results4 = realm.All<Foo>("Bar.IntValue > $0 || (Bar.String == $1 && Bar.Bool == $2)" 5, "small", true"); /// /// /// @@ -182,6 +183,7 @@ public static void Move(this IList list, int from, int to) /// NSPredicate Cheatsheet public static IQueryable Filter(this IQueryable query, string predicate, params RealmValue[] arguments) { + Argument.NotNull(arguments, nameof(arguments)); var realmResults = Argument.EnsureType>(query, $"{nameof(query)} must be a query obtained by calling Realm.All.", nameof(query)); return realmResults.GetFilteredResults(predicate, arguments); } diff --git a/Realm/Realm/Handles/ResultsHandle.cs b/Realm/Realm/Handles/ResultsHandle.cs index f4a9188c0e..bbc6673105 100644 --- a/Realm/Realm/Handles/ResultsHandle.cs +++ b/Realm/Realm/Handles/ResultsHandle.cs @@ -17,7 +17,6 @@ //////////////////////////////////////////////////////////////////////////// using System; -using System.Linq; using System.Runtime.InteropServices; using Realms.Native; @@ -63,7 +62,10 @@ private static class NativeMethods public static extern IntPtr snapshot(ResultsHandle results, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "results_get_filtered_results", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_filtered_results(ResultsHandle results, [MarshalAs(UnmanagedType.LPWStr)] string query_buf, IntPtr query_len, PrimitiveValue[] arguments, int args_count, out NativeException ex); + public static extern IntPtr get_filtered_results(ResultsHandle results, + [MarshalAs(UnmanagedType.LPWStr)] string query_buf, IntPtr query_len, + [MarshalAs(UnmanagedType.LPArray), In] PrimitiveValue[] arguments, IntPtr args_count, + out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "results_find_object", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr find_object(ResultsHandle results, ObjectHandle objectHandle, out NativeException ex); @@ -192,12 +194,12 @@ public override ResultsHandle GetFilteredResults(string query, RealmValue[] argu { var primitiveValues = new PrimitiveValue[arguments.Length]; var handles = new RealmValue.HandlesToCleanup?[arguments.Length]; - for (int i = 0; i < arguments.Length; ++i) + for (var i = 0; i < arguments.Length; i++) { (primitiveValues[i], handles[i]) = arguments[i].ToNative(); } - var ptr = NativeMethods.get_filtered_results(this, query, (IntPtr)query.Length, primitiveValues, primitiveValues.Length, out var ex); + var ptr = NativeMethods.get_filtered_results(this, query, (IntPtr)query.Length, primitiveValues, (IntPtr)primitiveValues.Length, out var ex); foreach (var handle in handles) { handle?.Dispose(); diff --git a/Tests/Realm.Tests/GlobalSuppressions.cs b/Tests/Realm.Tests/GlobalSuppressions.cs index 6a08f54c39..aa1c3847c7 100644 --- a/Tests/Realm.Tests/GlobalSuppressions.cs +++ b/Tests/Realm.Tests/GlobalSuppressions.cs @@ -25,3 +25,4 @@ [assembly: SuppressMessage("Design", "CA1051:Do not declare visible instance fields", Justification = "This is fine for tests.", Scope = "module")] [assembly: SuppressMessage("Design", "CA1054:URI-like parameters should not be strings", Justification = "This is fine for tests.", Scope = "module")] [assembly: SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "This is fine for tests.", Scope = "module")] +[assembly: SuppressMessage("Globalization", "CA1310: Specify StringComparison for correctness", Justification = "We can't assume users follow best practices.", Scope = "module")] diff --git a/wrappers/src/results_cs.cpp b/wrappers/src/results_cs.cpp index ffb8b513b4..c3ca574493 100644 --- a/wrappers/src/results_cs.cpp +++ b/wrappers/src/results_cs.cpp @@ -119,7 +119,7 @@ REALM_EXPORT DescriptorOrdering* results_get_descriptor_ordering(Results& result }); } -REALM_EXPORT Results* results_get_filtered_results(const Results& results, uint16_t* query_buf, size_t query_len, realm_value_t arguments[], size_t args_count, NativeException::Marshallable& ex) +REALM_EXPORT Results* results_get_filtered_results(const Results& results, uint16_t* query_buf, size_t query_len, realm_value_t* arguments, size_t args_count, NativeException::Marshallable& ex) { return handle_errors(ex, [&]() { Utf16StringAccessor query_string(query_buf, query_len); @@ -129,12 +129,13 @@ REALM_EXPORT Results* results_get_filtered_results(const Results& results, uint1 realm::populate_keypath_mapping(mapping, *realm); std::vector mixed_args; - for (int i = 0; i < args_count; ++i ) { + mixed_args.reserve(args_count); + for (int i = 0; i < args_count; ++i) { if (arguments[i].type != realm_value_type::RLM_TYPE_LINK) { mixed_args.push_back(from_capi(arguments[i])); } else { - mixed_args.push_back(from_capi(arguments[i].link.object, false)); + mixed_args.push_back(from_capi(arguments[i].link.object, true)); } } From 0f6253c68288cb6041029ac9c4bd08b235e9ff6f Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Fri, 29 Jan 2021 15:23:02 +0100 Subject: [PATCH 31/44] Reshaped tests and added more --- Tests/Realm.Tests/Database/CollectionTests.cs | 328 ++++++++++++------ Tests/Realm.Tests/Database/TestObjects.cs | 67 +--- 2 files changed, 228 insertions(+), 167 deletions(-) diff --git a/Tests/Realm.Tests/Database/CollectionTests.cs b/Tests/Realm.Tests/Database/CollectionTests.cs index 16974c39b4..bb66a1e39b 100644 --- a/Tests/Realm.Tests/Database/CollectionTests.cs +++ b/Tests/Realm.Tests/Database/CollectionTests.cs @@ -16,6 +16,8 @@ // //////////////////////////////////////////////////////////////////////////// +#nullable enable + using System; using System.Collections.Generic; using System.Linq; @@ -412,64 +414,256 @@ public void Results_GetFiltered_SanityTest() Assert.That(query.ToArray().All(i => i.Int >= 5)); } - [Test] - public void Bool_StringBasedQuery() => Generic_StringBasedQuery(); + public struct StringQueryTestData + { + public string PropertyName; + public RealmValue MatchingValue; + public RealmValue NonMatchingValue; - [Test] - public void Char_StringBasedQuery() => Generic_StringBasedQuery(); + private delegate object Converter(RealmValue value); - [Test] - public void SingleByte_StringBasedQuery() => Generic_StringBasedQuery(); + public StringQueryTestData(string propertyName, RealmValue matchingValue, RealmValue nonMatchingValue) + { + PropertyName = propertyName; + MatchingValue = matchingValue; + NonMatchingValue = nonMatchingValue; + } - [Test] - public void Int16_StringBasedQuery() => Generic_StringBasedQuery(); + public (object? MatchingValue, object? NonMatchingValue) ConvertBoxedValueToCorrectType(Type targetType) + { + if (targetType == null) + { + return (null, null); + } - [Test] - public void Int_StringBasedQuery() => Generic_StringBasedQuery(); + // boxed value types can't be casted to Decimal128 + if (PropertyName == "Decimal128Property") + { + return (TryToConvert(MatchingValue, (value) => value.AsDecimal128()), TryToConvert(NonMatchingValue, (value) => value.AsDecimal128())); + } + else + { + return (TryToConvert(MatchingValue.AsAny(), targetType), TryToConvert(NonMatchingValue.AsAny(), targetType)); + } + } - [Test] - public void Int64_StringBasedQuery() => Generic_StringBasedQuery(); + private static object? TryToConvert(RealmValue value, Converter converter) + { + try + { + return converter(value); + } + catch + { + return null; + } + } - [Test] - public void Float_StringBasedQuery() => Generic_StringBasedQuery(); + private static object? TryToConvert(object value, Type targetType) + { + try + { + return Convert.ChangeType(value, targetType); + } + catch + { + return null; + } + } + } - [Test] - public void Double_StringBasedQuery() => Generic_StringBasedQuery(); + public static IEnumerable StringQueryTestCases() + { + yield return new StringQueryTestData(nameof(AllTypesObject.CharProperty), 'c', 'b'); + yield return new StringQueryTestData(nameof(AllTypesObject.ByteProperty), 0x5, 0x4); + yield return new StringQueryTestData(nameof(AllTypesObject.Int16Property), 5, 4); + yield return new StringQueryTestData(nameof(AllTypesObject.Int32Property), 34, 42); + yield return new StringQueryTestData(nameof(AllTypesObject.Int64Property), 74L, 23L); + yield return new StringQueryTestData(nameof(AllTypesObject.SingleProperty), 3.0f, 2.0f); + yield return new StringQueryTestData(nameof(AllTypesObject.DoubleProperty), 4.0, 2.0); + yield return new StringQueryTestData(nameof(AllTypesObject.BooleanProperty), true, false); + yield return new StringQueryTestData(nameof(AllTypesObject.DateTimeOffsetProperty), new DateTimeOffset(1956, 6, 1, 0, 0, 0, TimeSpan.Zero), new DateTimeOffset(2020, 8, 2, 0, 0, 0, TimeSpan.Zero)); + yield return new StringQueryTestData(nameof(AllTypesObject.DecimalProperty), 0.9999999999999999999999999999, 0.3333333333333333333333333333); + yield return new StringQueryTestData(nameof(AllTypesObject.Decimal128Property), new Decimal128(564.42343424323), new Decimal128(666.42300000003)); + yield return new StringQueryTestData(nameof(AllTypesObject.ObjectIdProperty), new ObjectId("5f64cd9f1691c361b2451d96"), new ObjectId("5ffffffffffff22222222222")); + yield return new StringQueryTestData(nameof(AllTypesObject.GuidProperty), new Guid("0f8fad5b-d9cb-469f-a165-70867728950e"), new Guid("0ffffffb-dddd-4444-aaaa-70000000000e")); + yield return new StringQueryTestData(nameof(AllTypesObject.StringProperty), "hello world", "no salute"); + yield return new StringQueryTestData(nameof(AllTypesObject.ByteArrayProperty), new byte[] { 0x5, 0x4, 0x3, 0x2, 0x1 }, new byte[] { 0x1, 0x1, 0x1 }); + } - [Test] - public void Date_StringBasedQuery() => Generic_StringBasedQuery(); + public static IEnumerable StringQueryTestCases_NumbericValues_ImplicitConvertion_Matches() + { + yield return new StringQueryTestData(nameof(AllTypesObject.CharProperty), 'a', 97); + yield return new StringQueryTestData(nameof(AllTypesObject.ByteProperty), 0x6, 6); + yield return new StringQueryTestData(nameof(AllTypesObject.ByteProperty), 0xf, 15.0f); + yield return new StringQueryTestData(nameof(AllTypesObject.Int16Property), 55, 55.0f); + yield return new StringQueryTestData(nameof(AllTypesObject.Int32Property), 66, 66.0); + yield return new StringQueryTestData(nameof(AllTypesObject.Int32Property), 19, 19.0f); + yield return new StringQueryTestData(nameof(AllTypesObject.Int64Property), 77L, 77); + yield return new StringQueryTestData(nameof(AllTypesObject.Int64Property), 82L, 82m); + yield return new StringQueryTestData(nameof(AllTypesObject.SingleProperty), 88.8f, 88.8); + yield return new StringQueryTestData(nameof(AllTypesObject.SingleProperty), 49f, 49); + yield return new StringQueryTestData(nameof(AllTypesObject.DoubleProperty), 106.0, 106m); + yield return new StringQueryTestData(nameof(AllTypesObject.BooleanProperty), true, 1); + yield return new StringQueryTestData(nameof(AllTypesObject.DecimalProperty), 1m, 1f); + yield return new StringQueryTestData(nameof(AllTypesObject.DecimalProperty), 5m, 5.0); + } - [Test] - public void Decimal_StringBasedQuery() => Generic_StringBasedQuery(); + public static IEnumerable StringQueryTestCases_NumbericValues_ImplicitConvertion_Mismatches() + { + yield return new StringQueryTestData(nameof(AllTypesObject.CharProperty), 'c', 2555); + yield return new StringQueryTestData(nameof(AllTypesObject.ByteProperty), 0x5, 542359); + yield return new StringQueryTestData(nameof(AllTypesObject.Int16Property), 5, 35L); + yield return new StringQueryTestData(nameof(AllTypesObject.Int32Property), 34, 563.0f); + yield return new StringQueryTestData(nameof(AllTypesObject.Int64Property), 74L, 7435); + yield return new StringQueryTestData(nameof(AllTypesObject.SingleProperty), 3.0f, 21.0); + yield return new StringQueryTestData(nameof(AllTypesObject.DoubleProperty), 4.0, 'c'); + yield return new StringQueryTestData(nameof(AllTypesObject.BooleanProperty), true, 298); + } - [Test] - public void ObjectId_StringBasedQuery() => Generic_StringBasedQuery(); + public static IEnumerable StringQueryTestCases_NumbericValues_NoImplicitConvertion_Mismatches() + { + yield return new StringQueryTestData(nameof(AllTypesObject.DoubleProperty), 109.9, 109.9f); + } - [Test] - public void Guid_StringBasedQuery() => Generic_StringBasedQuery(); + public static IEnumerable StringQueryTestCases_MismatchingValueTypes_ToThrow() + { + yield return new StringQueryTestData(nameof(AllTypesObject.CharProperty), 'c', "who are you"); + yield return new StringQueryTestData(nameof(AllTypesObject.Int16Property), 2, "no one is here"); + yield return new StringQueryTestData(nameof(AllTypesObject.Int32Property), 3, "go away"); + yield return new StringQueryTestData(nameof(AllTypesObject.Int64Property), 32L, "again you?"); + yield return new StringQueryTestData(nameof(AllTypesObject.SingleProperty), 4.0f, "I said go"); + yield return new StringQueryTestData(nameof(AllTypesObject.DoubleProperty), 5.0, "I'm getting angry"); + yield return new StringQueryTestData(nameof(AllTypesObject.ByteProperty), 0x6, "I give up"); + yield return new StringQueryTestData(nameof(AllTypesObject.BooleanProperty), true, "enough"); + yield return new StringQueryTestData(nameof(AllTypesObject.DateTimeOffsetProperty), new DateTimeOffset(1956, 6, 1, 0, 0, 0, TimeSpan.Zero), 5); + yield return new StringQueryTestData(nameof(AllTypesObject.DecimalProperty), 7m, new byte[] { 0x1, 0x2, 0x3 }); + yield return new StringQueryTestData(nameof(AllTypesObject.Decimal128Property), new Decimal128(564.42343424323), new byte[] { 0x3, 0x2, 0x1 }); + yield return new StringQueryTestData(nameof(AllTypesObject.ObjectIdProperty), new ObjectId("5f64cd9f1691c361b2451d96"), "hello world"); + yield return new StringQueryTestData(nameof(AllTypesObject.GuidProperty), new Guid("0f8fad5b-d9cb-469f-a165-70867728950e"), 'v'); + yield return new StringQueryTestData(nameof(AllTypesObject.StringProperty), "hello you3", 13m); + yield return new StringQueryTestData(nameof(AllTypesObject.ByteArrayProperty), new byte[] { 0x5, 0x4, 0x3, 0x2, 0x1 }, 34L); + } - [Test] - public void Byte_StringBasedQuery() => Generic_StringBasedQuery(); + [TestCaseSource(nameof(StringQueryTestCases))] + public void QueryFilter_WithArguments_ShouldReturnMatchingValues(StringQueryTestData data) + { + var pi = typeof(AllTypesObject).GetProperty(data.PropertyName); + (object? MatchingVal, object? NonMatchingVal) castedValues = data.ConvertBoxedValueToCorrectType(pi.PropertyType); + + _realm.Write(() => + { + var matchingObj = _realm.Add(new AllTypesObject { RequiredStringProperty = string.Empty }); + pi.SetValue(matchingObj, castedValues.MatchingVal); + + var nonMatchingObj = _realm.Add(new AllTypesObject { RequiredStringProperty = string.Empty }); + pi.SetValue(nonMatchingObj, castedValues.NonMatchingVal); + }); + + var matches = _realm.All().Filter($"{data.PropertyName} = $0", data.MatchingValue); + var foundVal = pi.GetValue(matches.Single()); + Assert.AreEqual(foundVal, castedValues.MatchingVal); + } + + [TestCaseSource(nameof(StringQueryTestCases_NumbericValues_ImplicitConvertion_Matches))] + public void QueryFilter_WithImplicitlyConvertibleNumericArguments_ShouldMatch(StringQueryTestData data) + { + QueryFilter_matchCounter_internal(data, 1); + } + + [TestCaseSource(nameof(StringQueryTestCases_NumbericValues_ImplicitConvertion_Mismatches))] + public void QueryFilter_WithImplicitlyConvertibleNumericArguments_ShouldNotMatch(StringQueryTestData data) + { + QueryFilter_matchCounter_internal(data, 0); + } + + [TestCaseSource(nameof(StringQueryTestCases_NumbericValues_NoImplicitConvertion_Mismatches))] + public void QueryFilter_WithNonImplicitlyConvertibleArguments_ShouldMismatch(StringQueryTestData data) + { + QueryFilter_matchCounter_internal(data, 0); + } + + private void QueryFilter_matchCounter_internal(StringQueryTestData data, int matchingCount) + { + var pi = typeof(AllTypesObject).GetProperty(data.PropertyName); + (object? MatchingVal, object? NonMatchingVal) castedValues = data.ConvertBoxedValueToCorrectType(pi.PropertyType); + _realm.Write(() => + { + var matchingObj = _realm.Add(new AllTypesObject { RequiredStringProperty = string.Empty }); + pi.SetValue(matchingObj, castedValues.MatchingVal); + }); + + var matches = _realm.All().Filter($"{data.PropertyName} = $0", data.NonMatchingValue); + Assert.AreEqual(matches.Count(), matchingCount); + + // this method always assumes no more than 1 match, given the structure of StringQueryTestData + if (matchingCount == 1) + { + Assert.AreEqual(pi.GetValue(matches.Single()), castedValues.MatchingVal); + } + } + + [TestCaseSource(nameof(StringQueryTestCases_MismatchingValueTypes_ToThrow))] + public void QueryFilter_WithWrongArguments_ShouldThrow(StringQueryTestData data) + { + var pi = typeof(AllTypesObject).GetProperty(data.PropertyName); + (object? MatchingVal, object? NonMatchingVal) castedValues = data.ConvertBoxedValueToCorrectType(pi.PropertyType); + _realm.Write(() => + { + var matchingObj = _realm.Add(new AllTypesObject { RequiredStringProperty = string.Empty }); + pi.SetValue(matchingObj, castedValues.MatchingVal); + }); + + Assert.Throws(() => _realm.All().Filter($"{data.PropertyName} = $0", data.NonMatchingValue), $"Unsupported comparison between type {pi.PropertyType.Name} and type {data.NonMatchingValue.GetType().Name}"); + } [Test] - public void String_StringBasedQuery() => Generic_StringBasedQuery(); + public void QueryFilter_WithArgumentsObject_ShouldMatch() + { + var matchingObj = new PrimaryKeyInt32Object { Int32Property = 42 }; + var nonMatchingObj = new PrimaryKeyInt32Object { Int32Property = 4 }; + _realm.Write(() => + { + _realm.Add(new ObjectPropertyObject { Object = matchingObj }); + _realm.Add(new ObjectPropertyObject { Object = nonMatchingObj }); + }); + + var matches = _realm.All().Filter("Object = $0", matchingObj); + var foundObj = matches.Single(); + Assert.AreEqual(matchingObj, foundObj.Object); + } [Test] - public void Object_StringBasedQuery() => Generic_StringBasedQuery(); + public void QueryFilter_WithArgumentsInMemoryObjects_ShouldThrow() + { + _realm.Write(() => + { + _realm.Add(new ObjectPropertyObject { Object = new PrimaryKeyInt32Object { Int32Property = 42 } }); + }); + + Assert.Throws(() => _realm.All().Filter("Object = $0", new PrimaryKeyInt32Object { Int32Property = 42 }), "**put your message here!**"); + } [Test] - public void MultipleValues_StringBasedQuery() => Generic_StringBasedQuery(); + public void QueryFilter_WithNullArguments_ShouldThrow() + { + Assert.Throws(() => _realm.All().Filter("Name = $0", null)); + } [Test] - public void InMemoryObject_StringBasedQuery_ShouldFindNone() + public void QueryFilter_WithMultipleArguments_ShouldMatch() { + var matchingObj = new MultiPropertyObject { String = "hello pp", Int = 9, Float = 21.0f, Char = 'p' }; + var nonMatchingObj = new MultiPropertyObject { String = "no salute", Int = 0, Float = 1.0f, Char = 'h' }; _realm.Write(() => { - _realm.Add(new ObjectPropertyObject { Object = new IntPropertyObject { Int = 5 } }); + _realm.Add(matchingObj); + _realm.Add(nonMatchingObj); }); - var query = _realm.All().Filter("Object = $0", new IntPropertyObject { Int = 5 }); - Assert.That(query.Count, Is.EqualTo(0)); + var matches = _realm.All().Filter("Int == $0 && Float == $1 && Char == $2 && String == $3", 9, 21.0f, 'p', "hello pp"); + var foundObj = matches.Single(); + Assert.AreEqual(foundObj, matchingObj); } [Test] @@ -1039,73 +1233,5 @@ private class B : RealmObject { public IntPropertyObject C { get; set; } } - - public struct InfoContainer - { - public RealmObject Obj; - public string QueryString; - public RealmValue[] Value; - - public InfoContainer(RealmObject obj, string query, params RealmValue[] args) - { - Obj = obj; - QueryString = query; - Value = args; - } - } - - private static InfoContainer[] InitArray() - { - var intProp = new IntPropertyObject { Int = 42 }; - - return new InfoContainer[] - { - new InfoContainer(new BoolPropertyObject { Bool = true }, "Bool == $0", true), - new InfoContainer(new CharPropertyObject { Char = 'c' }, "Char == $0", 'c'), - new InfoContainer(new SingleBytePropertyObject { Byte = 0x5 }, "Byte == $0", 0x5), - new InfoContainer(new Int16PropertyObject { Short = 6 }, "Short == $0", 6), - new InfoContainer(new IntPropertyObject { Int = 5 }, "Int == $0", 5), - new InfoContainer(new Int64PropertyObject { Long = 5L }, "Long == $0", 5L), - new InfoContainer(new FloatPropertyObject { Float = 5.0f }, "Float == $0", 5.0f), - new InfoContainer(new DoublePropertyObject { Double = 5.0 }, "Double == $0", 5.0), - new InfoContainer(new DatePropertyObject { Date = new DateTimeOffset(1956, 6, 1, 0, 0, 0, TimeSpan.Zero) }, "Date == $0", new DateTimeOffset(1956, 6, 1, 0, 0, 0, TimeSpan.Zero)), - new InfoContainer(new DecimalPropertyObject { Decimal = new Decimal128(564.42343424323) }, "Decimal == $0", new Decimal128(564.42343424323)), - new InfoContainer(new ObjectIdPropertyObject { Id = new ObjectId("5f64cd9f1691c361b2451d96") }, "Id == $0", new ObjectId("5f64cd9f1691c361b2451d96")), - new InfoContainer(new GuidPropertyObject { Id = new Guid("0f8fad5b-d9cb-469f-a165-70867728950e") }, "Id == $0", new Guid("0f8fad5b-d9cb-469f-a165-70867728950e")), - new InfoContainer(new BytePropertyObject { Data = new byte[] { 0x5, 0x4, 0x3, 0x2, 0x1 } }, "Data == $0", new byte[] { 0x5, 0x4, 0x3, 0x2, 0x1 }), - new InfoContainer(new StringPropertyObject { String = "hello world" }, "String == $0", "hello world"), - new InfoContainer(new ObjectPropertyObject { Object = intProp }, "Object == $0", intProp), - new InfoContainer(new MultiPropertyObject { String = "hello pp", Int = 9, Float = 21.0f, Char = 'p' }, "Int == $0 && Float == $1 && Char == $2 && String == $3", 9, 21.0f, 'p', "hello pp") - }; - } - - private void Generic_StringBasedQuery() - where T : RealmObject - { - var listToStringQuery = InitArray(); - - if (listToStringQuery.Length < 1) - { - Assert.Fail("listToStringQuery is empty. Nothing to test."); - } - - InfoContainer foundObj = listToStringQuery[0]; - _realm.Write(() => - { - for (int i = 0; i < listToStringQuery.Length; ++i) - { - var currObj = listToStringQuery[i]; - if (typeof(T) == currObj.Obj.GetType()) - { - foundObj = currObj; - } - - _realm.Add(currObj.Obj); - } - }); - - var query = _realm.All().Filter(foundObj.QueryString, foundObj.Value); - Assert.That(query.Single().Equals(foundObj.Obj)); - } } } diff --git a/Tests/Realm.Tests/Database/TestObjects.cs b/Tests/Realm.Tests/Database/TestObjects.cs index 1c5332a066..5314f4a3de 100644 --- a/Tests/Realm.Tests/Database/TestObjects.cs +++ b/Tests/Realm.Tests/Database/TestObjects.cs @@ -488,79 +488,14 @@ public class ContainerObject : RealmObject public IList Items { get; } } - public class BoolPropertyObject : RealmObject - { - public bool Bool { get; set; } - } - - public class CharPropertyObject : RealmObject - { - public char Char { get; set; } - } - - public class SingleBytePropertyObject : RealmObject - { - public byte Byte { get; set; } - } - - public class Int16PropertyObject : RealmObject - { - public short Short { get; set; } - } - public class IntPropertyObject : RealmObject { public int Int { get; set; } } - public class Int64PropertyObject : RealmObject - { - public long Long { get; set; } - } - - public class FloatPropertyObject : RealmObject - { - public float Float { get; set; } - } - - public class DoublePropertyObject : RealmObject - { - public double Double { get; set; } - } - - public class DatePropertyObject : RealmObject - { - public DateTimeOffset Date { get; set; } - } - - public class DecimalPropertyObject : RealmObject - { - public Decimal128 Decimal { get; set; } - } - - public class ObjectIdPropertyObject : RealmObject - { - public ObjectId Id { get; set; } - } - - public class GuidPropertyObject : RealmObject - { - public Guid Id { get; set; } - } - - public class BytePropertyObject : RealmObject - { - public byte[] Data { get; set; } - } - - public class StringPropertyObject : RealmObject - { - public string String { get; set; } - } - public class ObjectPropertyObject : RealmObject { - public IntPropertyObject Object { get; set; } + public PrimaryKeyInt32Object Object { get; set; } } public class MultiPropertyObject : RealmObject From d16127a2458b9dfc0cd4b4e72efee650a0aa36f3 Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Fri, 29 Jan 2021 17:19:08 +0100 Subject: [PATCH 32/44] Point to right core and updated DictionaryHandle to respect signature of GetFilteredResults --- Realm/Realm/Handles/DictionaryHandle.cs | 2 +- wrappers/realm-core | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Realm/Realm/Handles/DictionaryHandle.cs b/Realm/Realm/Handles/DictionaryHandle.cs index 1f7da836db..6ec17a6532 100644 --- a/Realm/Realm/Handles/DictionaryHandle.cs +++ b/Realm/Realm/Handles/DictionaryHandle.cs @@ -136,7 +136,7 @@ public override ThreadSafeReferenceHandle GetThreadSafeReference() return new ThreadSafeReferenceHandle(result); } - public override ResultsHandle GetFilteredResults(string query) + public override ResultsHandle GetFilteredResults(string query, RealmValue[] arguments) { throw new NotImplementedException("Dictionaries can't be filtered yet."); } diff --git a/wrappers/realm-core b/wrappers/realm-core index b200d987d3..a0a9441899 160000 --- a/wrappers/realm-core +++ b/wrappers/realm-core @@ -1 +1 @@ -Subproject commit b200d987d3a60106fa1f77339bfb98e893935d51 +Subproject commit a0a9441899a74add4bff2e4c0aeb4b5c007c5545 From 9d37a7c7bfa71e5cad53fb18de70bb3bc12f1dc2 Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Mon, 1 Feb 2021 11:07:43 +0100 Subject: [PATCH 33/44] Some renaming --- Tests/Realm.Tests/Database/CollectionTests.cs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Tests/Realm.Tests/Database/CollectionTests.cs b/Tests/Realm.Tests/Database/CollectionTests.cs index bb66a1e39b..783e2e20a9 100644 --- a/Tests/Realm.Tests/Database/CollectionTests.cs +++ b/Tests/Realm.Tests/Database/CollectionTests.cs @@ -472,7 +472,7 @@ public StringQueryTestData(string propertyName, RealmValue matchingValue, RealmV } } - public static IEnumerable StringQueryTestCases() + public static IEnumerable StringQueryTestCases_GeneralCases() { yield return new StringQueryTestData(nameof(AllTypesObject.CharProperty), 'c', 'b'); yield return new StringQueryTestData(nameof(AllTypesObject.ByteProperty), 0x5, 0x4); @@ -545,23 +545,23 @@ public static IEnumerable StringQueryTestCases_MismatchingV yield return new StringQueryTestData(nameof(AllTypesObject.ByteArrayProperty), new byte[] { 0x5, 0x4, 0x3, 0x2, 0x1 }, 34L); } - [TestCaseSource(nameof(StringQueryTestCases))] - public void QueryFilter_WithArguments_ShouldReturnMatchingValues(StringQueryTestData data) + [TestCaseSource(nameof(StringQueryTestCases_GeneralCases))] + public void QueryFilter_WithArguments_ShouldMatch(StringQueryTestData data) { - var pi = typeof(AllTypesObject).GetProperty(data.PropertyName); - (object? MatchingVal, object? NonMatchingVal) castedValues = data.ConvertBoxedValueToCorrectType(pi.PropertyType); + var propInfo = typeof(AllTypesObject).GetProperty(data.PropertyName); + (object? MatchingVal, object? NonMatchingVal) castedValues = data.ConvertBoxedValueToCorrectType(propInfo.PropertyType); _realm.Write(() => { var matchingObj = _realm.Add(new AllTypesObject { RequiredStringProperty = string.Empty }); - pi.SetValue(matchingObj, castedValues.MatchingVal); + propInfo.SetValue(matchingObj, castedValues.MatchingVal); var nonMatchingObj = _realm.Add(new AllTypesObject { RequiredStringProperty = string.Empty }); - pi.SetValue(nonMatchingObj, castedValues.NonMatchingVal); + propInfo.SetValue(nonMatchingObj, castedValues.NonMatchingVal); }); var matches = _realm.All().Filter($"{data.PropertyName} = $0", data.MatchingValue); - var foundVal = pi.GetValue(matches.Single()); + var foundVal = propInfo.GetValue(matches.Single()); Assert.AreEqual(foundVal, castedValues.MatchingVal); } @@ -578,19 +578,19 @@ public void QueryFilter_WithImplicitlyConvertibleNumericArguments_ShouldNotMatch } [TestCaseSource(nameof(StringQueryTestCases_NumbericValues_NoImplicitConvertion_Mismatches))] - public void QueryFilter_WithNonImplicitlyConvertibleArguments_ShouldMismatch(StringQueryTestData data) + public void QueryFilter_WithNonImplicitlyConvertibleArguments_ShouldNotMatch(StringQueryTestData data) { QueryFilter_matchCounter_internal(data, 0); } private void QueryFilter_matchCounter_internal(StringQueryTestData data, int matchingCount) { - var pi = typeof(AllTypesObject).GetProperty(data.PropertyName); - (object? MatchingVal, object? NonMatchingVal) castedValues = data.ConvertBoxedValueToCorrectType(pi.PropertyType); + var propInfo = typeof(AllTypesObject).GetProperty(data.PropertyName); + (object? MatchingVal, object? NonMatchingVal) castedValues = data.ConvertBoxedValueToCorrectType(propInfo.PropertyType); _realm.Write(() => { var matchingObj = _realm.Add(new AllTypesObject { RequiredStringProperty = string.Empty }); - pi.SetValue(matchingObj, castedValues.MatchingVal); + propInfo.SetValue(matchingObj, castedValues.MatchingVal); }); var matches = _realm.All().Filter($"{data.PropertyName} = $0", data.NonMatchingValue); @@ -599,22 +599,22 @@ private void QueryFilter_matchCounter_internal(StringQueryTestData data, int mat // this method always assumes no more than 1 match, given the structure of StringQueryTestData if (matchingCount == 1) { - Assert.AreEqual(pi.GetValue(matches.Single()), castedValues.MatchingVal); + Assert.AreEqual(propInfo.GetValue(matches.Single()), castedValues.MatchingVal); } } [TestCaseSource(nameof(StringQueryTestCases_MismatchingValueTypes_ToThrow))] public void QueryFilter_WithWrongArguments_ShouldThrow(StringQueryTestData data) { - var pi = typeof(AllTypesObject).GetProperty(data.PropertyName); - (object? MatchingVal, object? NonMatchingVal) castedValues = data.ConvertBoxedValueToCorrectType(pi.PropertyType); + var propInfo = typeof(AllTypesObject).GetProperty(data.PropertyName); + (object? MatchingVal, object? NonMatchingVal) castedValues = data.ConvertBoxedValueToCorrectType(propInfo.PropertyType); _realm.Write(() => { var matchingObj = _realm.Add(new AllTypesObject { RequiredStringProperty = string.Empty }); - pi.SetValue(matchingObj, castedValues.MatchingVal); + propInfo.SetValue(matchingObj, castedValues.MatchingVal); }); - Assert.Throws(() => _realm.All().Filter($"{data.PropertyName} = $0", data.NonMatchingValue), $"Unsupported comparison between type {pi.PropertyType.Name} and type {data.NonMatchingValue.GetType().Name}"); + Assert.Throws(() => _realm.All().Filter($"{data.PropertyName} = $0", data.NonMatchingValue), $"Unsupported comparison between type {propInfo.PropertyType.Name} and type {data.NonMatchingValue.GetType().Name}"); } [Test] From 80fdbafc5d12060caf4a108e7bd5f8b97921f095 Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Mon, 1 Feb 2021 14:33:32 +0100 Subject: [PATCH 34/44] Fixed changelog --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 921160423b..cc26abf150 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ var obj = new MyObject(); obj.Denominations.Add("quarter", 0.25d); ``` +* Added support for value substitution in string based queries. This enables expressions following [this syntax](https://github.com/realm/realm-js/blob/master/docs/tutorials/query-language.md): `realm.All().Filter("field1 = $0 && field2 = $1", 123, "some-string-value")`. (Issue [#1822](https://github.com/realm/realm-dotnet/issues/1822)) ### Compatibility * Realm Studio: 10.0.0 or later. @@ -38,8 +39,6 @@ * Fixed a regression in 10.0.0-beta.5 that incorrectly stores and retrieves `DateTimeOffset` values. (PR [#2200](https://github.com/realm/realm-dotnet/pull/2200)) ### Enhancements -* Add support for the `GUID` data type. It can be used as primary key and is indexable. (PR [#2120](https://github.com/realm/realm-dotnet/pull/2120)) -* Added support for value substitution in string based queries. This enables expressions following [this syntax](https://github.com/realm/realm-js/blob/master/docs/tutorials/query-language.md): `realm.All().Filter("field1 = $0 && field2 = $1", 123, "some-string-value")`. (Issue [#1822](https://github.com/realm/realm-dotnet/issues/1822)) * None ### Compatibility From 844ac8ad2c41959b9bb221455ad090da0fa68ed5 Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Mon, 1 Feb 2021 18:07:13 +0100 Subject: [PATCH 35/44] Some of the PR feedback applied --- Tests/Realm.Tests/Database/CollectionTests.cs | 48 ++++++++++++------- Tests/Realm.Tests/Database/TestObjects.cs | 16 ------- 2 files changed, 31 insertions(+), 33 deletions(-) diff --git a/Tests/Realm.Tests/Database/CollectionTests.cs b/Tests/Realm.Tests/Database/CollectionTests.cs index 783e2e20a9..3ed960127b 100644 --- a/Tests/Realm.Tests/Database/CollectionTests.cs +++ b/Tests/Realm.Tests/Database/CollectionTests.cs @@ -16,8 +16,6 @@ // //////////////////////////////////////////////////////////////////////////// -#nullable enable - using System; using System.Collections.Generic; using System.Linq; @@ -491,7 +489,7 @@ public static IEnumerable StringQueryTestCases_GeneralCases yield return new StringQueryTestData(nameof(AllTypesObject.ByteArrayProperty), new byte[] { 0x5, 0x4, 0x3, 0x2, 0x1 }, new byte[] { 0x1, 0x1, 0x1 }); } - public static IEnumerable StringQueryTestCases_NumbericValues_ImplicitConvertion_Matches() + public static IEnumerable StringQueryTestCases_NumericValues_ImplicitConvertion_Matches() { yield return new StringQueryTestData(nameof(AllTypesObject.CharProperty), 'a', 97); yield return new StringQueryTestData(nameof(AllTypesObject.ByteProperty), 0x6, 6); @@ -565,7 +563,7 @@ public void QueryFilter_WithArguments_ShouldMatch(StringQueryTestData data) Assert.AreEqual(foundVal, castedValues.MatchingVal); } - [TestCaseSource(nameof(StringQueryTestCases_NumbericValues_ImplicitConvertion_Matches))] + [TestCaseSource(nameof(StringQueryTestCases_NumericValues_ImplicitConvertion_Matches))] public void QueryFilter_WithImplicitlyConvertibleNumericArguments_ShouldMatch(StringQueryTestData data) { QueryFilter_matchCounter_internal(data, 1); @@ -617,31 +615,47 @@ public void QueryFilter_WithWrongArguments_ShouldThrow(StringQueryTestData data) Assert.Throws(() => _realm.All().Filter($"{data.PropertyName} = $0", data.NonMatchingValue), $"Unsupported comparison between type {propInfo.PropertyType.Name} and type {data.NonMatchingValue.GetType().Name}"); } + [Test] + public void QueryFilter_WithArgumentsObjectList_ShouldMatch() + { + Internal_QueryFilter_OnObjects(true); + } + [Test] public void QueryFilter_WithArgumentsObject_ShouldMatch() { - var matchingObj = new PrimaryKeyInt32Object { Int32Property = 42 }; - var nonMatchingObj = new PrimaryKeyInt32Object { Int32Property = 4 }; + Internal_QueryFilter_OnObjects(false); + } + + private void Internal_QueryFilter_OnObjects(bool queryList) + { + var dog1 = new Dog { Name = "Fido", Color = "black", Vaccinated = true }; + var dog2 = new Dog { Name = "Pluto", Color = "white", Vaccinated = true }; + var dog3 = new Dog { Name = "Pippo", Color = "brown", Vaccinated = false }; + var dog4 = new Dog { Name = "JustDog", Color = "pink", Vaccinated = false }; + var marioOwner = new Owner { Name = "Mario", TopDog = dog1, Dogs = { dog1, dog2, dog3 } }; + var luigiOwner = new Owner { Name = "Luigi", TopDog = dog4, Dogs = { dog4 } }; + _realm.Write(() => { - _realm.Add(new ObjectPropertyObject { Object = matchingObj }); - _realm.Add(new ObjectPropertyObject { Object = nonMatchingObj }); + _realm.Add(marioOwner); + _realm.Add(luigiOwner); }); - var matches = _realm.All().Filter("Object = $0", matchingObj); - var foundObj = matches.Single(); - Assert.AreEqual(matchingObj, foundObj.Object); + var matches = queryList ? _realm.All().Filter("ANY Dogs.Name == $0", dog1.Name) : _realm.All().Filter("TopDog == $0", dog1); + var foundOwner = matches.Single(); + Assert.AreEqual(marioOwner, foundOwner); } [Test] - public void QueryFilter_WithArgumentsInMemoryObjects_ShouldThrow() + public void QueryFilter_WithArgumentsUnmanagedObjects_ShouldThrow() { _realm.Write(() => { - _realm.Add(new ObjectPropertyObject { Object = new PrimaryKeyInt32Object { Int32Property = 42 } }); + _realm.Add(new Owner { TopDog = new Dog { Name = "Doge", Color = "almost yellow", Vaccinated = true } }); }); - Assert.Throws(() => _realm.All().Filter("Object = $0", new PrimaryKeyInt32Object { Int32Property = 42 }), "**put your message here!**"); + Assert.Throws(() => _realm.All().Filter("TopDog = $0", new Dog { Name = "Doge", Color = "almost yellow", Vaccinated = true }), "**put your message here!**"); } [Test] @@ -653,15 +667,15 @@ public void QueryFilter_WithNullArguments_ShouldThrow() [Test] public void QueryFilter_WithMultipleArguments_ShouldMatch() { - var matchingObj = new MultiPropertyObject { String = "hello pp", Int = 9, Float = 21.0f, Char = 'p' }; - var nonMatchingObj = new MultiPropertyObject { String = "no salute", Int = 0, Float = 1.0f, Char = 'h' }; + var matchingObj = new AllTypesObject { RequiredStringProperty = "hello pp", Int32Property = 9, SingleProperty = 21.0f, CharProperty = 'p' }; + var nonMatchingObj = new AllTypesObject { RequiredStringProperty = "no salute", Int32Property = 0, SingleProperty = 1.0f, CharProperty = 'h' }; _realm.Write(() => { _realm.Add(matchingObj); _realm.Add(nonMatchingObj); }); - var matches = _realm.All().Filter("Int == $0 && Float == $1 && Char == $2 && String == $3", 9, 21.0f, 'p', "hello pp"); + var matches = _realm.All().Filter("Int32Property == $0 && SingleProperty == $1 && CharProperty == $2 && RequiredStringProperty == $3", 9, 21.0f, 'p', "hello pp"); var foundObj = matches.Single(); Assert.AreEqual(foundObj, matchingObj); } diff --git a/Tests/Realm.Tests/Database/TestObjects.cs b/Tests/Realm.Tests/Database/TestObjects.cs index 0129ec44a7..60f7b13fa9 100644 --- a/Tests/Realm.Tests/Database/TestObjects.cs +++ b/Tests/Realm.Tests/Database/TestObjects.cs @@ -572,22 +572,6 @@ public class IntPropertyObject : RealmObject public int Int { get; set; } } - public class ObjectPropertyObject : RealmObject - { - public PrimaryKeyInt32Object Object { get; set; } - } - - public class MultiPropertyObject : RealmObject - { - public string String { get; set; } - - public int Int { get; set; } - - public float Float { get; set; } - - public char Char { get; set; } - } - public class RecursiveBacklinksObject : RealmObject { public int Id { get; set; } From 4ba9d7118e45129db25c682b5cd886a539c93319 Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Thu, 4 Feb 2021 11:26:33 +0100 Subject: [PATCH 36/44] Simplified tests and added test for embedded objects and extended embedded obj to have Guid, ObjectId and byte[] --- Tests/Realm.Tests/Database/CollectionTests.cs | 226 +++++++++--------- Tests/Realm.Tests/Database/TestObjects.cs | 6 + 2 files changed, 118 insertions(+), 114 deletions(-) diff --git a/Tests/Realm.Tests/Database/CollectionTests.cs b/Tests/Realm.Tests/Database/CollectionTests.cs index 3ed960127b..9ec2b5711d 100644 --- a/Tests/Realm.Tests/Database/CollectionTests.cs +++ b/Tests/Realm.Tests/Database/CollectionTests.cs @@ -412,14 +412,28 @@ public void Results_GetFiltered_SanityTest() Assert.That(query.ToArray().All(i => i.Int >= 5)); } + public struct StringQueryNumericData + { + public string PropertyName; + public RealmValue ValueToAddToDB; + public RealmValue ValueToQueryFor; + public bool ExpectedMatch; + + public StringQueryNumericData(string propertyName, RealmValue valueToAddToDB, RealmValue valueToQueryFor, bool expectedMatch) + { + PropertyName = propertyName; + ValueToAddToDB = valueToAddToDB; + ValueToQueryFor = valueToQueryFor; + ExpectedMatch = expectedMatch; + } + } + public struct StringQueryTestData { public string PropertyName; public RealmValue MatchingValue; public RealmValue NonMatchingValue; - private delegate object Converter(RealmValue value); - public StringQueryTestData(string propertyName, RealmValue matchingValue, RealmValue nonMatchingValue) { PropertyName = propertyName; @@ -427,50 +441,20 @@ public StringQueryTestData(string propertyName, RealmValue matchingValue, RealmV NonMatchingValue = nonMatchingValue; } - public (object? MatchingValue, object? NonMatchingValue) ConvertBoxedValueToCorrectType(Type targetType) - { - if (targetType == null) - { - return (null, null); - } - - // boxed value types can't be casted to Decimal128 - if (PropertyName == "Decimal128Property") - { - return (TryToConvert(MatchingValue, (value) => value.AsDecimal128()), TryToConvert(NonMatchingValue, (value) => value.AsDecimal128())); - } - else - { - return (TryToConvert(MatchingValue.AsAny(), targetType), TryToConvert(NonMatchingValue.AsAny(), targetType)); - } - } + } - private static object? TryToConvert(RealmValue value, Converter converter) + public static object BoxValue(RealmValue val, Type targetType) + { + var boxed = val.AsAny(); + if (boxed != null && boxed.GetType() != targetType) { - try - { - return converter(value); - } - catch - { - return null; - } + boxed = Convert.ChangeType(boxed, targetType); } - private static object? TryToConvert(object value, Type targetType) - { - try - { - return Convert.ChangeType(value, targetType); - } - catch - { - return null; - } - } + return boxed; } - public static IEnumerable StringQueryTestCases_GeneralCases() + public static IEnumerable StringQuery_AllTypes() { yield return new StringQueryTestData(nameof(AllTypesObject.CharProperty), 'c', 'b'); yield return new StringQueryTestData(nameof(AllTypesObject.ByteProperty), 0x5, 0x4); @@ -489,42 +473,39 @@ public static IEnumerable StringQueryTestCases_GeneralCases yield return new StringQueryTestData(nameof(AllTypesObject.ByteArrayProperty), new byte[] { 0x5, 0x4, 0x3, 0x2, 0x1 }, new byte[] { 0x1, 0x1, 0x1 }); } - public static IEnumerable StringQueryTestCases_NumericValues_ImplicitConvertion_Matches() - { - yield return new StringQueryTestData(nameof(AllTypesObject.CharProperty), 'a', 97); - yield return new StringQueryTestData(nameof(AllTypesObject.ByteProperty), 0x6, 6); - yield return new StringQueryTestData(nameof(AllTypesObject.ByteProperty), 0xf, 15.0f); - yield return new StringQueryTestData(nameof(AllTypesObject.Int16Property), 55, 55.0f); - yield return new StringQueryTestData(nameof(AllTypesObject.Int32Property), 66, 66.0); - yield return new StringQueryTestData(nameof(AllTypesObject.Int32Property), 19, 19.0f); - yield return new StringQueryTestData(nameof(AllTypesObject.Int64Property), 77L, 77); - yield return new StringQueryTestData(nameof(AllTypesObject.Int64Property), 82L, 82m); - yield return new StringQueryTestData(nameof(AllTypesObject.SingleProperty), 88.8f, 88.8); - yield return new StringQueryTestData(nameof(AllTypesObject.SingleProperty), 49f, 49); - yield return new StringQueryTestData(nameof(AllTypesObject.DoubleProperty), 106.0, 106m); - yield return new StringQueryTestData(nameof(AllTypesObject.BooleanProperty), true, 1); - yield return new StringQueryTestData(nameof(AllTypesObject.DecimalProperty), 1m, 1f); - yield return new StringQueryTestData(nameof(AllTypesObject.DecimalProperty), 5m, 5.0); - } - - public static IEnumerable StringQueryTestCases_NumbericValues_ImplicitConvertion_Mismatches() - { - yield return new StringQueryTestData(nameof(AllTypesObject.CharProperty), 'c', 2555); - yield return new StringQueryTestData(nameof(AllTypesObject.ByteProperty), 0x5, 542359); - yield return new StringQueryTestData(nameof(AllTypesObject.Int16Property), 5, 35L); - yield return new StringQueryTestData(nameof(AllTypesObject.Int32Property), 34, 563.0f); - yield return new StringQueryTestData(nameof(AllTypesObject.Int64Property), 74L, 7435); - yield return new StringQueryTestData(nameof(AllTypesObject.SingleProperty), 3.0f, 21.0); - yield return new StringQueryTestData(nameof(AllTypesObject.DoubleProperty), 4.0, 'c'); - yield return new StringQueryTestData(nameof(AllTypesObject.BooleanProperty), true, 298); - } - - public static IEnumerable StringQueryTestCases_NumbericValues_NoImplicitConvertion_Mismatches() - { - yield return new StringQueryTestData(nameof(AllTypesObject.DoubleProperty), 109.9, 109.9f); - } - - public static IEnumerable StringQueryTestCases_MismatchingValueTypes_ToThrow() + public static IEnumerable StringQuery_NumericValues() + { + // implicit conversion match + yield return new StringQueryNumericData(nameof(AllTypesObject.CharProperty), 'a', 97, true); + yield return new StringQueryNumericData(nameof(AllTypesObject.ByteProperty), 0x6, 6, true); + yield return new StringQueryNumericData(nameof(AllTypesObject.ByteProperty), 0xf, 15.0f, true); + yield return new StringQueryNumericData(nameof(AllTypesObject.Int16Property), 55, 55.0f, true); + yield return new StringQueryNumericData(nameof(AllTypesObject.Int32Property), 66, 66.0, true); + yield return new StringQueryNumericData(nameof(AllTypesObject.Int32Property), 19, 19.0f, true); + yield return new StringQueryNumericData(nameof(AllTypesObject.Int64Property), 77L, 77, true); + yield return new StringQueryNumericData(nameof(AllTypesObject.Int64Property), 82L, 82m, true); + yield return new StringQueryNumericData(nameof(AllTypesObject.SingleProperty), 88.8f, 88.8, true); + yield return new StringQueryNumericData(nameof(AllTypesObject.SingleProperty), 49f, 49, true); + yield return new StringQueryNumericData(nameof(AllTypesObject.DoubleProperty), 106.0, 106m, true); + yield return new StringQueryNumericData(nameof(AllTypesObject.BooleanProperty), true, 1, true); + yield return new StringQueryNumericData(nameof(AllTypesObject.DecimalProperty), 1m, 1f, true); + yield return new StringQueryNumericData(nameof(AllTypesObject.DecimalProperty), 5m, 5.0, true); + + // implicit conversion no match + yield return new StringQueryNumericData(nameof(AllTypesObject.CharProperty), 'c', 2555, false); + yield return new StringQueryNumericData(nameof(AllTypesObject.ByteProperty), 0x5, 'g', false); + yield return new StringQueryNumericData(nameof(AllTypesObject.Int16Property), 5, 35L, false); + yield return new StringQueryNumericData(nameof(AllTypesObject.Int32Property), 34, 563.0f, false); + yield return new StringQueryNumericData(nameof(AllTypesObject.Int64Property), 74L, 7435, false); + yield return new StringQueryNumericData(nameof(AllTypesObject.SingleProperty), 3.0f, 21.0, false); + yield return new StringQueryNumericData(nameof(AllTypesObject.DoubleProperty), 4.0, 'c', false); + yield return new StringQueryNumericData(nameof(AllTypesObject.BooleanProperty), true, 298, false); + + // no implicit conversion no match + yield return new StringQueryNumericData(nameof(AllTypesObject.DoubleProperty), 109.9, 109.9f, false); + } + + public static IEnumerable StringQuery_MismatchingTypes_ToThrow() { yield return new StringQueryTestData(nameof(AllTypesObject.CharProperty), 'c', "who are you"); yield return new StringQueryTestData(nameof(AllTypesObject.Int16Property), 2, "no one is here"); @@ -539,77 +520,92 @@ public static IEnumerable StringQueryTestCases_MismatchingV yield return new StringQueryTestData(nameof(AllTypesObject.Decimal128Property), new Decimal128(564.42343424323), new byte[] { 0x3, 0x2, 0x1 }); yield return new StringQueryTestData(nameof(AllTypesObject.ObjectIdProperty), new ObjectId("5f64cd9f1691c361b2451d96"), "hello world"); yield return new StringQueryTestData(nameof(AllTypesObject.GuidProperty), new Guid("0f8fad5b-d9cb-469f-a165-70867728950e"), 'v'); - yield return new StringQueryTestData(nameof(AllTypesObject.StringProperty), "hello you3", 13m); + yield return new StringQueryTestData(nameof(AllTypesObject.StringProperty), "hello you", 13m); yield return new StringQueryTestData(nameof(AllTypesObject.ByteArrayProperty), new byte[] { 0x5, 0x4, 0x3, 0x2, 0x1 }, 34L); } - [TestCaseSource(nameof(StringQueryTestCases_GeneralCases))] - public void QueryFilter_WithArguments_ShouldMatch(StringQueryTestData data) + [TestCaseSource(nameof(StringQuery_AllTypes))] + public void QueryFilter_WithAnyArguments_ShouldMatch(StringQueryTestData data) { var propInfo = typeof(AllTypesObject).GetProperty(data.PropertyName); - (object? MatchingVal, object? NonMatchingVal) castedValues = data.ConvertBoxedValueToCorrectType(propInfo.PropertyType); + var boxedMatch = BoxValue(data.MatchingValue, propInfo.PropertyType); + var boxedNonMatch = BoxValue(data.NonMatchingValue, propInfo.PropertyType); _realm.Write(() => { var matchingObj = _realm.Add(new AllTypesObject { RequiredStringProperty = string.Empty }); - propInfo.SetValue(matchingObj, castedValues.MatchingVal); + propInfo.SetValue(matchingObj, boxedMatch); var nonMatchingObj = _realm.Add(new AllTypesObject { RequiredStringProperty = string.Empty }); - propInfo.SetValue(nonMatchingObj, castedValues.NonMatchingVal); + propInfo.SetValue(nonMatchingObj, boxedNonMatch); }); var matches = _realm.All().Filter($"{data.PropertyName} = $0", data.MatchingValue); var foundVal = propInfo.GetValue(matches.Single()); - Assert.AreEqual(foundVal, castedValues.MatchingVal); + Assert.AreEqual(foundVal, boxedMatch); } - [TestCaseSource(nameof(StringQueryTestCases_NumericValues_ImplicitConvertion_Matches))] - public void QueryFilter_WithImplicitlyConvertibleNumericArguments_ShouldMatch(StringQueryTestData data) + [TestCaseSource(nameof(StringQuery_NumericValues))] + public void QueryFilter_WithNumericArguments(StringQueryNumericData data) { - QueryFilter_matchCounter_internal(data, 1); - } + var propInfo = typeof(AllTypesObject).GetProperty(data.PropertyName); + var boxedMatch = BoxValue(data.ValueToAddToDB, propInfo.PropertyType); - [TestCaseSource(nameof(StringQueryTestCases_NumbericValues_ImplicitConvertion_Mismatches))] - public void QueryFilter_WithImplicitlyConvertibleNumericArguments_ShouldNotMatch(StringQueryTestData data) - { - QueryFilter_matchCounter_internal(data, 0); - } + _realm.Write(() => + { + var matchingObj = _realm.Add(new AllTypesObject { RequiredStringProperty = string.Empty }); + propInfo.SetValue(matchingObj, boxedMatch); + }); - [TestCaseSource(nameof(StringQueryTestCases_NumbericValues_NoImplicitConvertion_Mismatches))] - public void QueryFilter_WithNonImplicitlyConvertibleArguments_ShouldNotMatch(StringQueryTestData data) - { - QueryFilter_matchCounter_internal(data, 0); + var matches = _realm.All().Filter($"{data.PropertyName} = $0", data.ValueToQueryFor); + if (data.ExpectedMatch) + { + Assert.AreEqual(matches.Count(), 1); + Assert.AreEqual(propInfo.GetValue(matches.Single()), boxedMatch); + } + else + { + Assert.AreEqual(matches.Count(), 0); + } } - private void QueryFilter_matchCounter_internal(StringQueryTestData data, int matchingCount) + [TestCaseSource(nameof(StringQuery_AllTypes))] + public void QueryFilter_WithAnyEmbeddedObjectArguments_ShouldMatch(StringQueryTestData data) { - var propInfo = typeof(AllTypesObject).GetProperty(data.PropertyName); - (object? MatchingVal, object? NonMatchingVal) castedValues = data.ConvertBoxedValueToCorrectType(propInfo.PropertyType); + var propInfoObjWithEmbedded = typeof(ObjectWithEmbeddedProperties).GetProperty("AllTypesObject"); + var propInfoEmbeddedAllTypes = typeof(EmbeddedAllTypesObject).GetProperty(data.PropertyName); + + var boxedMatch = BoxValue(data.MatchingValue, propInfoEmbeddedAllTypes.PropertyType); + var embeddedAllTypesMatch = new EmbeddedAllTypesObject(); + propInfoEmbeddedAllTypes.SetValue(embeddedAllTypesMatch, boxedMatch); + + var boxedNonMatch = BoxValue(data.NonMatchingValue, propInfoEmbeddedAllTypes.PropertyType); + var embeddedAllTypesNonMatch = new EmbeddedAllTypesObject(); + propInfoEmbeddedAllTypes.SetValue(embeddedAllTypesNonMatch, boxedNonMatch); + _realm.Write(() => { - var matchingObj = _realm.Add(new AllTypesObject { RequiredStringProperty = string.Empty }); - propInfo.SetValue(matchingObj, castedValues.MatchingVal); - }); + var matchingEmbeddedObj = _realm.Add(new ObjectWithEmbeddedProperties { PrimaryKey = 0 }); + propInfoObjWithEmbedded.SetValue(matchingEmbeddedObj, embeddedAllTypesMatch); - var matches = _realm.All().Filter($"{data.PropertyName} = $0", data.NonMatchingValue); - Assert.AreEqual(matches.Count(), matchingCount); + var nonMatchingEmbeddedObj = _realm.Add(new ObjectWithEmbeddedProperties { PrimaryKey = 1 }); + propInfoObjWithEmbedded.SetValue(nonMatchingEmbeddedObj, embeddedAllTypesNonMatch); + }); - // this method always assumes no more than 1 match, given the structure of StringQueryTestData - if (matchingCount == 1) - { - Assert.AreEqual(propInfo.GetValue(matches.Single()), castedValues.MatchingVal); - } + var matches = _realm.All().Filter("AllTypesObject = $0", embeddedAllTypesMatch); + Assert.AreEqual(propInfoEmbeddedAllTypes.GetValue(matches.Single().AllTypesObject), boxedMatch); } - [TestCaseSource(nameof(StringQueryTestCases_MismatchingValueTypes_ToThrow))] + [TestCaseSource(nameof(StringQuery_MismatchingTypes_ToThrow))] public void QueryFilter_WithWrongArguments_ShouldThrow(StringQueryTestData data) { var propInfo = typeof(AllTypesObject).GetProperty(data.PropertyName); - (object? MatchingVal, object? NonMatchingVal) castedValues = data.ConvertBoxedValueToCorrectType(propInfo.PropertyType); + var boxedMatch = BoxValue(data.MatchingValue, propInfo.PropertyType); + _realm.Write(() => { var matchingObj = _realm.Add(new AllTypesObject { RequiredStringProperty = string.Empty }); - propInfo.SetValue(matchingObj, castedValues.MatchingVal); + propInfo.SetValue(matchingObj, boxedMatch); }); Assert.Throws(() => _realm.All().Filter($"{data.PropertyName} = $0", data.NonMatchingValue), $"Unsupported comparison between type {propInfo.PropertyType.Name} and type {data.NonMatchingValue.GetType().Name}"); @@ -641,7 +637,8 @@ private void Internal_QueryFilter_OnObjects(bool queryList) _realm.Add(marioOwner); _realm.Add(luigiOwner); }); - + IList list = new List(); + list.Add(dog1); var matches = queryList ? _realm.All().Filter("ANY Dogs.Name == $0", dog1.Name) : _realm.All().Filter("TopDog == $0", dog1); var foundOwner = matches.Single(); Assert.AreEqual(marioOwner, foundOwner); @@ -661,7 +658,8 @@ public void QueryFilter_WithArgumentsUnmanagedObjects_ShouldThrow() [Test] public void QueryFilter_WithNullArguments_ShouldThrow() { - Assert.Throws(() => _realm.All().Filter("Name = $0", null)); + RealmValue[] argumentsArray = null; + Assert.Throws(() => _realm.All().Filter("Name = $0", argumentsArray)); } [Test] diff --git a/Tests/Realm.Tests/Database/TestObjects.cs b/Tests/Realm.Tests/Database/TestObjects.cs index 60f7b13fa9..d60bfc2fab 100644 --- a/Tests/Realm.Tests/Database/TestObjects.cs +++ b/Tests/Realm.Tests/Database/TestObjects.cs @@ -663,6 +663,12 @@ public class EmbeddedAllTypesObject : EmbeddedObject public DateTimeOffset DateTimeOffsetProperty { get; set; } + public ObjectId ObjectIdProperty { get; set; } + + public Guid GuidProperty { get; set; } + + public byte[] ByteArrayProperty { get; set; } + public char? NullableCharProperty { get; set; } public byte? NullableByteProperty { get; set; } From c7e9790e8bdada0398739452f50ca30647cda22a Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Thu, 4 Feb 2021 14:30:43 +0100 Subject: [PATCH 37/44] Added RealmInteger tests for string query value sustitution --- Tests/Realm.Tests/Database/CollectionTests.cs | 36 ++++++++++++++++++- Tests/Realm.Tests/Database/TestObjects.cs | 8 +++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/Tests/Realm.Tests/Database/CollectionTests.cs b/Tests/Realm.Tests/Database/CollectionTests.cs index 9ec2b5711d..93c963f955 100644 --- a/Tests/Realm.Tests/Database/CollectionTests.cs +++ b/Tests/Realm.Tests/Database/CollectionTests.cs @@ -448,7 +448,37 @@ public static object BoxValue(RealmValue val, Type targetType) var boxed = val.AsAny(); if (boxed != null && boxed.GetType() != targetType) { - boxed = Convert.ChangeType(boxed, targetType); + try + { + boxed = Convert.ChangeType(boxed, targetType); + } + catch (System.InvalidCastException e) + { + if (e.Message.Contains("RealmInteger")) + { + var innerType = targetType.GenericTypeArguments.Single(); + if (innerType == typeof(int)) + { + boxed = (RealmInteger)val; + } + else if (innerType == typeof(long)) + { + boxed = (RealmInteger)val; + } + else if (innerType == typeof(byte)) + { + boxed = (RealmInteger)val; + } + else if (innerType == typeof(short)) + { + boxed = (RealmInteger)val; + } + } + else + { + throw; + } + } } return boxed; @@ -471,6 +501,10 @@ public static IEnumerable StringQuery_AllTypes() yield return new StringQueryTestData(nameof(AllTypesObject.GuidProperty), new Guid("0f8fad5b-d9cb-469f-a165-70867728950e"), new Guid("0ffffffb-dddd-4444-aaaa-70000000000e")); yield return new StringQueryTestData(nameof(AllTypesObject.StringProperty), "hello world", "no salute"); yield return new StringQueryTestData(nameof(AllTypesObject.ByteArrayProperty), new byte[] { 0x5, 0x4, 0x3, 0x2, 0x1 }, new byte[] { 0x1, 0x1, 0x1 }); + yield return new StringQueryTestData(nameof(AllTypesObject.ByteCounterProperty), new RealmInteger(0x6), new RealmInteger(0x8)); + yield return new StringQueryTestData(nameof(AllTypesObject.Int16CounterProperty), new RealmInteger(2), new RealmInteger(7)); + yield return new StringQueryTestData(nameof(AllTypesObject.Int32CounterProperty), new RealmInteger(10), new RealmInteger(24)); + yield return new StringQueryTestData(nameof(AllTypesObject.Int64CounterProperty), new RealmInteger(5L), new RealmInteger(22L)); } public static IEnumerable StringQuery_NumericValues() diff --git a/Tests/Realm.Tests/Database/TestObjects.cs b/Tests/Realm.Tests/Database/TestObjects.cs index d60bfc2fab..60b0ef25cd 100644 --- a/Tests/Realm.Tests/Database/TestObjects.cs +++ b/Tests/Realm.Tests/Database/TestObjects.cs @@ -85,6 +85,14 @@ public class AllTypesObject : RealmObject public ObjectId? NullableObjectIdProperty { get; set; } public Guid? NullableGuidProperty { get; set; } + + public RealmInteger ByteCounterProperty { get; set; } + + public RealmInteger Int16CounterProperty { get; set; } + + public RealmInteger Int32CounterProperty { get; set; } + + public RealmInteger Int64CounterProperty { get; set; } } public class DecimalsObject : RealmObject From 41d3d024598c7134a83d05f37523f040fae1064e Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Thu, 4 Feb 2021 14:32:46 +0100 Subject: [PATCH 38/44] Forgot to rethrow properly --- Tests/Realm.Tests/Database/CollectionTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Realm.Tests/Database/CollectionTests.cs b/Tests/Realm.Tests/Database/CollectionTests.cs index 93c963f955..a94386acbd 100644 --- a/Tests/Realm.Tests/Database/CollectionTests.cs +++ b/Tests/Realm.Tests/Database/CollectionTests.cs @@ -476,7 +476,7 @@ public static object BoxValue(RealmValue val, Type targetType) } else { - throw; + throw e; } } } From 2050194f7afc7e31e0457d5c2ceb05093c949bbd Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Thu, 4 Feb 2021 14:52:31 +0100 Subject: [PATCH 39/44] Test nullable null value correctly passed to core in query string value substitution --- Tests/Realm.Tests/Database/CollectionTests.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Tests/Realm.Tests/Database/CollectionTests.cs b/Tests/Realm.Tests/Database/CollectionTests.cs index a94386acbd..1b96487525 100644 --- a/Tests/Realm.Tests/Database/CollectionTests.cs +++ b/Tests/Realm.Tests/Database/CollectionTests.cs @@ -690,8 +690,19 @@ public void QueryFilter_WithArgumentsUnmanagedObjects_ShouldThrow() } [Test] - public void QueryFilter_WithNullArguments_ShouldThrow() + public void QueryFilter_WithNullArguments() { + var nullableObjMatch = new AllTypesObject { RequiredStringProperty = "hello", NullableInt32Property = null }; + var nullableObjNoMatch = new AllTypesObject { RequiredStringProperty = "world", NullableInt32Property = 42 }; + _realm.Write(() => + { + _realm.Add(nullableObjMatch); + _realm.Add(nullableObjNoMatch); + }); + + var matches = _realm.All().Filter("NullableInt32Property = $0", (int?)null); + Assert.AreEqual(matches.Single(), nullableObjMatch); + RealmValue[] argumentsArray = null; Assert.Throws(() => _realm.All().Filter("Name = $0", argumentsArray)); } From 65e1cf556162f16cd2a4be351f0b6e850fea457e Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Thu, 4 Feb 2021 15:45:20 +0100 Subject: [PATCH 40/44] Small style fixes --- Tests/Realm.Tests/Database/CollectionTests.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Tests/Realm.Tests/Database/CollectionTests.cs b/Tests/Realm.Tests/Database/CollectionTests.cs index 1b96487525..2158c2adf4 100644 --- a/Tests/Realm.Tests/Database/CollectionTests.cs +++ b/Tests/Realm.Tests/Database/CollectionTests.cs @@ -415,14 +415,14 @@ public void Results_GetFiltered_SanityTest() public struct StringQueryNumericData { public string PropertyName; - public RealmValue ValueToAddToDB; + public RealmValue ValueToAddToRealm; public RealmValue ValueToQueryFor; public bool ExpectedMatch; public StringQueryNumericData(string propertyName, RealmValue valueToAddToDB, RealmValue valueToQueryFor, bool expectedMatch) { PropertyName = propertyName; - ValueToAddToDB = valueToAddToDB; + ValueToAddToRealm = valueToAddToDB; ValueToQueryFor = valueToQueryFor; ExpectedMatch = expectedMatch; } @@ -583,7 +583,7 @@ public void QueryFilter_WithAnyArguments_ShouldMatch(StringQueryTestData data) public void QueryFilter_WithNumericArguments(StringQueryNumericData data) { var propInfo = typeof(AllTypesObject).GetProperty(data.PropertyName); - var boxedMatch = BoxValue(data.ValueToAddToDB, propInfo.PropertyType); + var boxedMatch = BoxValue(data.ValueToAddToRealm, propInfo.PropertyType); _realm.Write(() => { @@ -627,7 +627,8 @@ public void QueryFilter_WithAnyEmbeddedObjectArguments_ShouldMatch(StringQueryTe }); var matches = _realm.All().Filter("AllTypesObject = $0", embeddedAllTypesMatch); - Assert.AreEqual(propInfoEmbeddedAllTypes.GetValue(matches.Single().AllTypesObject), boxedMatch); + var foundObj = propInfoEmbeddedAllTypes.GetValue(matches.Single().AllTypesObject); + Assert.AreEqual(foundObj, boxedMatch); } [TestCaseSource(nameof(StringQuery_MismatchingTypes_ToThrow))] @@ -674,8 +675,7 @@ private void Internal_QueryFilter_OnObjects(bool queryList) IList list = new List(); list.Add(dog1); var matches = queryList ? _realm.All().Filter("ANY Dogs.Name == $0", dog1.Name) : _realm.All().Filter("TopDog == $0", dog1); - var foundOwner = matches.Single(); - Assert.AreEqual(marioOwner, foundOwner); + Assert.AreEqual(marioOwner, matches.Single()); } [Test] @@ -719,8 +719,7 @@ public void QueryFilter_WithMultipleArguments_ShouldMatch() }); var matches = _realm.All().Filter("Int32Property == $0 && SingleProperty == $1 && CharProperty == $2 && RequiredStringProperty == $3", 9, 21.0f, 'p', "hello pp"); - var foundObj = matches.Single(); - Assert.AreEqual(foundObj, matchingObj); + Assert.AreEqual(matches.Single(), matchingObj); } [Test] From a98d164c59a0491a1723a143e318078c3e1b1b74 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Thu, 4 Feb 2021 16:32:26 +0100 Subject: [PATCH 41/44] Simplify boxing + add tostring overloads for test case data (#2228) --- Tests/Realm.Tests/Database/CollectionTests.cs | 45 ++++++------------- 1 file changed, 13 insertions(+), 32 deletions(-) diff --git a/Tests/Realm.Tests/Database/CollectionTests.cs b/Tests/Realm.Tests/Database/CollectionTests.cs index 2158c2adf4..b9ad470d78 100644 --- a/Tests/Realm.Tests/Database/CollectionTests.cs +++ b/Tests/Realm.Tests/Database/CollectionTests.cs @@ -19,6 +19,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using MongoDB.Bson; using NUnit.Framework; @@ -426,6 +427,8 @@ public StringQueryNumericData(string propertyName, RealmValue valueToAddToDB, Re ValueToQueryFor = valueToQueryFor; ExpectedMatch = expectedMatch; } + + public override string ToString() => $"{PropertyName}: '{ValueToAddToRealm}' should{(ExpectedMatch ? string.Empty : " NOT")} match '{ValueToQueryFor}': {ExpectedMatch}"; } public struct StringQueryTestData @@ -441,44 +444,22 @@ public StringQueryTestData(string propertyName, RealmValue matchingValue, RealmV NonMatchingValue = nonMatchingValue; } + public override string ToString() => $"{PropertyName}, match: '{MatchingValue}' non-match: '{NonMatchingValue}'"; } - public static object BoxValue(RealmValue val, Type targetType) + private static object BoxValue(RealmValue val, Type targetType) { var boxed = val.AsAny(); + if (boxed != null && targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(RealmInteger<>)) + { + var wrappedType = targetType.GetGenericArguments().Single(); + boxed = Convert.ChangeType(boxed, wrappedType); + return Activator.CreateInstance(targetType, BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { boxed }, null); + } + if (boxed != null && boxed.GetType() != targetType) { - try - { - boxed = Convert.ChangeType(boxed, targetType); - } - catch (System.InvalidCastException e) - { - if (e.Message.Contains("RealmInteger")) - { - var innerType = targetType.GenericTypeArguments.Single(); - if (innerType == typeof(int)) - { - boxed = (RealmInteger)val; - } - else if (innerType == typeof(long)) - { - boxed = (RealmInteger)val; - } - else if (innerType == typeof(byte)) - { - boxed = (RealmInteger)val; - } - else if (innerType == typeof(short)) - { - boxed = (RealmInteger)val; - } - } - else - { - throw e; - } - } + return Convert.ChangeType(boxed, targetType); } return boxed; From 10976dd5a0bd21cf6181812ee53d0a7f5a9d6c08 Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Fri, 5 Feb 2021 14:55:13 +0100 Subject: [PATCH 42/44] Reverted to correct core submodule to be equal to master --- wrappers/realm-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappers/realm-core b/wrappers/realm-core index a0a9441899..b62be193b2 160000 --- a/wrappers/realm-core +++ b/wrappers/realm-core @@ -1 +1 @@ -Subproject commit a0a9441899a74add4bff2e4c0aeb4b5c007c5545 +Subproject commit b62be193b28f2c4fca7b7af82d5c82ce175b783b From a390ead0ed234de60a33fd81cc9f181358431d30 Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Fri, 5 Feb 2021 15:28:33 +0100 Subject: [PATCH 43/44] Fixed documentation for the Filter method --- Realm/Realm/Extensions/CollectionExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Realm/Realm/Extensions/CollectionExtensions.cs b/Realm/Realm/Extensions/CollectionExtensions.cs index 08c7b34714..f889dd2c70 100644 --- a/Realm/Realm/Extensions/CollectionExtensions.cs +++ b/Realm/Realm/Extensions/CollectionExtensions.cs @@ -211,7 +211,7 @@ public static IDisposable SubscribeForNotifications(this IDictionary 0"); /// var results2 = realm.All<Foo>("Bar.IntValue > 0 SORT(Bar.IntValue ASC Bar.StringValue DESC)"); /// var results3 = realm.All<Foo>("Bar.IntValue > 0 SORT(Bar.IntValue ASC Bar.StringValue DESC) DISTINCT(Bar.IntValue)"); - /// var results4 = realm.All<Foo>("Bar.IntValue > $0 || (Bar.String == $1 && Bar.Bool == $2)" 5, "small", true"); + /// var results4 = realm.All<Foo>("Bar.IntValue > $0 || (Bar.String == $1 && Bar.Bool == $2)", 5, "small", true); /// /// /// From 75202173332d7bdbf3e1feefae39e2822a3608ba Mon Sep 17 00:00:00 2001 From: Andrea Catalini Date: Tue, 23 Feb 2021 15:24:00 +0100 Subject: [PATCH 44/44] Updated realm-core to fix unit test --- wrappers/realm-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappers/realm-core b/wrappers/realm-core index 5c03339ddf..aa393fb401 160000 --- a/wrappers/realm-core +++ b/wrappers/realm-core @@ -1 +1 @@ -Subproject commit 5c03339ddf98b64bf8970056c48cb49bb9a0d633 +Subproject commit aa393fb4019bd3c13f687e499240beba77fcb05e