From 6a7ce4c0dcd56cef7cfb93a1ee7ee68db4c30d71 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Wed, 7 Jun 2023 10:01:10 +0200 Subject: [PATCH 1/3] Update core 13.13 in bindgen branch (#6683) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Christian Melchior Co-authored-by: James Stone Co-authored-by: realm-ci Co-authored-by: Kirill Burtsev Co-authored-by: Daniel Tabacaru <96778637+danieltabacaru@users.noreply.github.com> Co-authored-by: Thomas Goyne Co-authored-by: Thomas Goyne Co-authored-by: Jørgen Edelbo Co-authored-by: Michael Wilkerson-Barker Co-authored-by: Nicola Cabiddu fix entries that went to the wrong change version (#6632) fix a race in a test (#6651) Fix a lock order inversion in tests (#6666) Fix an assertion failure if an async write callback ran during a write transaction (#6661) --- CHANGELOG.md | 60 +- Package.swift | 2 +- bindgen/spec.yml | 2 - dependencies.list | 2 +- evergreen/config.yml | 2 +- external/catch | 2 +- .../LIBRARY/src/bid128_string.c | 11 +- src/external/s2/base/casts.h | 2 +- src/external/s2/base/logging.h | 2 +- src/external/s2/base/macros.h | 63 +-- src/external/s2/s2loop.cc | 8 +- src/external/s2/s2polygon.cc | 16 +- src/external/s2/util/math/mathutil.cc | 13 - src/external/s2/util/math/mathutil.h | 73 +-- src/realm.h | 3 +- src/realm/array.cpp | 93 +-- src/realm/array_backlink.cpp | 23 +- src/realm/array_blob.cpp | 2 +- src/realm/bplustree.hpp | 21 + src/realm/cluster_tree.cpp | 12 +- src/realm/dictionary.cpp | 16 +- src/realm/geospatial.cpp | 302 ++++++++-- src/realm/geospatial.hpp | 73 ++- src/realm/group.cpp | 8 + src/realm/group.hpp | 5 +- src/realm/metrics/query_info.cpp | 1 - src/realm/metrics/query_info.hpp | 1 - src/realm/mixed.cpp | 7 +- src/realm/mixed.hpp | 3 +- src/realm/obj.cpp | 6 +- src/realm/object-store/c_api/app.cpp | 15 +- .../object-store/impl/realm_coordinator.cpp | 29 +- src/realm/object-store/results.cpp | 32 +- src/realm/object-store/results.hpp | 12 +- src/realm/object-store/sectioned_results.cpp | 483 ++++++++-------- src/realm/object-store/sectioned_results.hpp | 62 +- src/realm/object-store/shared_realm.cpp | 6 + src/realm/object-store/sync/app.cpp | 219 +++++--- src/realm/object-store/sync/app.hpp | 30 +- src/realm/object-store/sync/sync_session.cpp | 16 +- .../object-store/util/apple/scheduler.hpp | 23 + src/realm/object-store/util/scheduler.cpp | 6 +- src/realm/parser/driver.cpp | 43 +- src/realm/parser/driver.hpp | 7 +- src/realm/parser/generated/query_bison.cpp | 460 +++++++-------- src/realm/parser/generated/query_bison.hpp | 16 +- src/realm/parser/generated/query_flex.cpp | 528 +++++++++--------- src/realm/parser/query_bison.yy | 5 +- src/realm/parser/query_flex.ll | 2 +- src/realm/query.cpp | 17 +- src/realm/query.hpp | 19 +- src/realm/query_engine.cpp | 25 +- src/realm/query_engine.hpp | 378 ++++--------- src/realm/query_expression.cpp | 189 +++---- src/realm/query_expression.hpp | 253 +++++---- src/realm/sort_descriptor.cpp | 31 +- src/realm/sort_descriptor.hpp | 3 +- src/realm/sync/network/network.hpp | 50 +- src/realm/table.cpp | 9 +- src/realm/table_view.cpp | 15 +- src/realm/table_view.hpp | 2 +- src/realm/util/features.h | 5 + src/realm/util/platform_info.hpp | 39 ++ src/realm/utilities.hpp | 25 - test/CMakeLists.txt | 6 + test/benchmark-common-tasks/CMakeLists.txt | 2 +- test/benchmark-common-tasks/main.cpp | 295 +++++++++- test/object-store/CMakeLists.txt | 3 + test/object-store/benchmarks/object.cpp | 1 - test/object-store/benchmarks/results.cpp | 97 +++- test/object-store/c_api/c_api.cpp | 38 +- test/object-store/dictionary.cpp | 3 - test/object-store/frozen_objects.cpp | 34 +- test/object-store/realm.cpp | 34 +- test/object-store/results.cpp | 1 - test/object-store/sectioned_results.cpp | 121 ++-- test/object-store/set.cpp | 2 - test/object-store/sync/app.cpp | 94 +++- test/object-store/sync/flx_sync.cpp | 115 +++- test/object-store/sync/sync_test_utils.cpp | 26 + test/object-store/sync/sync_test_utils.hpp | 3 + test/object-store/util/baas_admin_api.hpp | 4 +- test/object-store/util/event_loop.cpp | 23 + test/object-store/util/event_loop.hpp | 3 + test/object-store/util/test_file.cpp | 6 +- test/test_parser.cpp | 151 +++-- test/test_query_geo.cpp | 214 ++++++- test/test_table.cpp | 2 +- test/test_unresolved_links.cpp | 236 ++++++-- tools/cmake/SpecialtyBuilds.cmake | 6 +- 90 files changed, 3296 insertions(+), 2112 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d74df3b230..f86f89fe480 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,59 @@ +# 13.13.0 Release notes + +### Enhancements +* None. + +### Fixed +* Allow numeric substitutions into a geospatial query. Example `location GEOWITHIN geoCircle([$0, $1], $2)`. ([#6662](https://github.com/realm/realm-core/issues/6662)) +* Access token refresh for websockets was not updating the location metadata ([#6630](https://github.com/realm/realm-core/issues/6630), since v13.9.3) +* Fix several UBSan failures which did not appear to result in functional bugs ([#6649](https://github.com/realm/realm-core/pull/6649)). +* Fix an out-of-bounds read in sectioned results when sectioned are removed by modifying all objects in that section to no longer appear in that section ([#6649](https://github.com/realm/realm-core/pull/6649), since v13.12.0) +* Using both synchronous and asynchronous transactions on the same thread or scheduler could hit the assertion failure "!realm.is_in_transaction()" if one of the callbacks for an asynchronous transaction happened to be scheduled during a synchronous transaction ([#6659](https://github.com/realm/realm-core/issues/6659), since v11.8.0) + +### Breaking changes +* None. + +### Compatibility +* Fileformat: Generates files with format v23. Reads and automatically upgrade from fileformat v5. + +----------- + +### Internals +* Simplify the implementation of query expression nodes which have a btree leaf cache. +* Fix a lock order inversion hit by object store tests running on linux. The cycle required test-specific code and so is not applicable to non-tests. +* Remove catch() clause to prevent truncating stack trace in AsyncOper::do_recycle_and_execute() ([PR #6667](https://github.com/realm/realm-core/pull/6667)) + +---------------------------------------------- + +# 13.12.0 Release notes + +### Enhancements +* Improve performance of SectionedResults. With a single section it is now ~10% faster, and the runtime of sectioning no longer scales significantly with section count, giving >100% speedups when there are large numbers of sections ([PR #6606](https://github.com/realm/realm-core/pull/6606)). +* Very slightly improve performance of runtime thread checking on the main thread on Apple platforms. ([PR #6606](https://github.com/realm/realm-core/pull/6606)) + +### Fixed +* We could crash when removing backlinks in cases where forward links did not have a corresponding backlink due to corruption. We now silently ignore this inconsistency in release builds, allowing the app to continue. ([#6585](https://github.com/realm/realm-core/issues/6585), v6.0.0) +* If you freeze a Results based on a collection of objects, the result would be invalid if you delete the collection ([#6635](https://github.com/realm/realm-core/issues/6635), since V13.11.0) +* Geospatial polygons now have built in normalization and validation in line with the MongoDB server side behaviour and the geoJSON standard. ([#6607](https://github.com/realm/realm-core/pull/6607), since v13.11.0) +* Dictionary::get_any() would expose unresolved links rather than mapping them to null. In addition to allowing invalid objects to be read from Dictionaries, this resulted in queries on Dictionaries sometimes having incorrect results ([#6644](https://github.com/realm/realm-core/pull/6644)). + +### Breaking changes +* `platform` and `cpu_arch` fields in the `device_info` structure in App::Config can no longer be provided by the SDK's, they are inferred by the library ([PR #6612](https://github.com/realm/realm-core/pull/6612)) +* `bundle_id` is now a required field in the `device_info` structure in App::Config ([PR #6612](https://github.com/realm/realm-core/pull/6612)) +* The API for sectioned results change notifications has changed. Changes are now reported in a vector rather than a sparse map. +* Renamed `GeoCenterSphere` to `GeoCircle` and in RQL `geoSphere` to `geoCircle`. The GeoPoints of query shapes are now validated before use and an exception will be thrown if invalid. Geospatial queries are no longer allowed on top-level tables. Fixed query results using ANY/ALL/NONE and matching on lists ([PR #6645](https://github.com/realm/realm-core/issues/6645)) + +### Compatibility +* Fileformat: Generates files with format v23. Reads and automatically upgrade from fileformat v5. + +----------- + +### Internals +* Upgraded to Catch from v3.0.1 to v3.3.2. ([#6623](https://github.com/realm/realm-core/issues/6623)) +* Added some geospatial benchmarks. ([#6622](https://github.com/realm/realm-core/issues/6622)) + +---------------------------------------------- + # 13.11.0 Release notes ### Enhancements @@ -8,6 +64,7 @@ * Fixed a fatal error (reported to the sync error handler) during client reset (or automatic PBS to FLX migration) if the reset has been triggered during an async open and the schema being applied has added new classes. ([#6601](https://github.com/realm/realm-core/issues/6601), since automatic client resets were introduced in v11.5.0) * Full text search would sometimes find words where the word only matches the beginning of the search token ([#6591](https://github.com/realm/realm-core/issues/6591), since v13.0.0) * Added missing includes of `` surfaced by gcc13 ([#6616](https://github.com/realm/realm-core/pull/6616)) +* Prevent crashing on Results.freeze if underlying object or table were removed by making Results.is_valid correctly report its state. ([#6401](https://github.com/realm/realm-core/issues/6401)) ### Compatibility * Fileformat: Generates files with format v23. Reads and automatically upgrade from fileformat v5. @@ -18,6 +75,7 @@ * Add initial support for targeting WebAssembly with Emscripten ([PR #6263](https://github.com/realm/realm-core/pull/6263)). * Sync session multiplexing is now enabled by default. The method `SyncManager::enable_session_multiplexing()` has been renamed `SyncManager::set_session_multiplexing()`. (PR [#6557](https://github.com/realm/realm-core/pull/6557)) * Bump protocol to v9 to indicate client has fix for client reset error during async open ([#6609](https://github.com/realm/realm-core/issues/6609)) +* Fixed `Results::is_valid()` in order to return `false` if the results is bound to a deleted object or table. (PR [#6445](https://github.com/realm/realm-core/pull/6445)) ---------------------------------------------- @@ -30,7 +88,6 @@ ### Fixed * Exclusion of words in a full text search does not work ([#6512](https://github.com/realm/realm-core/issues/6512), since v13.0.0 ); -* Prevent crashing on Results.freeze if underlying object or table were removed by making Results.is_valid correctly report its state. ([#6401](https://github.com/realm/realm-core/issues/6401)) ### Breaking changes * Add `service_name` parameter to `realm_app_call_function` (PR [#6394](https://github.com/realm/realm-core/pull/6394)). @@ -44,7 +101,6 @@ * Reduce the memory footprint of an automatic (discard or recover) client reset when there are large incoming changes from the server. ([#6567](https://github.com/realm/realm-core/issues/6567)) * `get_committed_file_format_version()` safe access to mappings vector from multiple threads. * Add CI tests for Clang 16/Ubuntu 22.04 and update lint task ([PR #6563](https://github.com/realm/realm-core/pull/6563)) -* Fixed `Results::is_valid()` in order to return `false` if the results is bound to a deleted object or table. (PR [#6445](https://github.com/realm/realm-core/pull/6445)) ---------------------------------------------- diff --git a/Package.swift b/Package.swift index ab04968fe49..b4a4fde21d7 100644 --- a/Package.swift +++ b/Package.swift @@ -3,7 +3,7 @@ import PackageDescription import Foundation -let versionStr = "13.11.0" +let versionStr = "13.13.0" let versionPieces = versionStr.split(separator: "-") let versionCompontents = versionPieces[0].split(separator: ".") let versionExtra = versionPieces.count > 1 ? versionPieces[1] : "" diff --git a/bindgen/spec.yml b/bindgen/spec.yml index 67afc5547ca..e470877981e 100644 --- a/bindgen/spec.yml +++ b/bindgen/spec.yml @@ -577,11 +577,9 @@ records: DeviceInfo: cppName: app::App::Config::DeviceInfo fields: - platform: std::string platform_version: std::string sdk_version: std::string sdk: std::string - cpu_arch: std::string device_name: std::string device_version: std::string framework_name: std::string diff --git a/dependencies.list b/dependencies.list index a6f5d19ccd1..1698fcc23d8 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-core -VERSION=13.11.0 +VERSION=13.13.0 OPENSSL_VERSION=3.0.8 WIN32_ZLIB_VERSION=1.2.13 MDBREALM_TEST_SERVER_TAG=2023-05-05 diff --git a/evergreen/config.yml b/evergreen/config.yml index e0224aef3b1..2a229ca84db 100644 --- a/evergreen/config.yml +++ b/evergreen/config.yml @@ -670,7 +670,7 @@ tasks: export DEVELOPER_DIR="${xcode_developer_dir}" fi - ./evergreen/install_baas.sh -w ./baas-work-dir -b 773fd0e93fb569b0b4304bb4ca9401f33351e0f1 2>&1 | tee install_baas_output.log + ./evergreen/install_baas.sh -w ./baas-work-dir -b df5aafd817ba377744d32976c15b175c9b68685a 2>&1 | tee install_baas_output.log fi - command: shell.exec diff --git a/external/catch b/external/catch index 605a34765aa..3f0283de7a9 160000 --- a/external/catch +++ b/external/catch @@ -1 +1 @@ -Subproject commit 605a34765aa5d5ecbf476b4598a862ada971b0cc +Subproject commit 3f0283de7a9c43200033da996ff9093be3ac84dc diff --git a/src/external/IntelRDFPMathLib20U2/LIBRARY/src/bid128_string.c b/src/external/IntelRDFPMathLib20U2/LIBRARY/src/bid128_string.c index fcc43198e70..72f7c168623 100755 --- a/src/external/IntelRDFPMathLib20U2/LIBRARY/src/bid128_string.c +++ b/src/external/IntelRDFPMathLib20U2/LIBRARY/src/bid128_string.c @@ -213,7 +213,11 @@ bid128_to_string (char *str, BID_UINT128 x __L1_Split_MiDi_6_Lead (HI_18Dig, ptr); __L1_Split_MiDi_6 (LO_18Dig, ptr); } - len = ptr - MiDi; + + // Realm edit: silence warning about lengths by casting + // this should not be a problem under the assumption that + // we are working with strings with reasonable lengths + len = (int)(ptr - MiDi); c_ptr_start = &(str[k]); c_ptr = c_ptr_start; @@ -222,7 +226,10 @@ bid128_to_string (char *str, BID_UINT128 x for (k_lcv = 1; k_lcv < len; k_lcv++) { __L0_MiDi2Str (MiDi[k_lcv], c_ptr); } - k = k + (c_ptr - c_ptr_start); + + // Realm edit: explicit cast to silence warning + // assumes working with strings of reasonable length + k = k + (unsigned int)(c_ptr - c_ptr_start); } // print E and sign of exponent diff --git a/src/external/s2/base/casts.h b/src/external/s2/base/casts.h index ff5c6c1a7ec..ff7948bcc2e 100644 --- a/src/external/s2/base/casts.h +++ b/src/external/s2/base/casts.h @@ -161,7 +161,7 @@ template inline Dest bit_cast(const Source& source) { // Compile time assertion: sizeof(Dest) == sizeof(Source) // A compile error here means your Dest and Source have different sizes. - typedef char VerifySizesAreEqual [sizeof(Dest) == sizeof(Source) ? 1 : -1]; + COMPILE_ASSERT(sizeof(Dest) == sizeof(Source), different_sizes); Dest dest; memcpy(&dest, &source, sizeof(dest)); diff --git a/src/external/s2/base/logging.h b/src/external/s2/base/logging.h index f8428142d35..11978c1b5b3 100644 --- a/src/external/s2/base/logging.h +++ b/src/external/s2/base/logging.h @@ -38,7 +38,7 @@ #define DCHECK_GE(val1, val2) REALM_ASSERT_DEBUG_EX(val1 >= val2, val1, val2) #define DCHECK_GT(val1, val2) REALM_ASSERT_DEBUG_EX(val1 > val2, val1, val2) -static std::shared_ptr& s2_logger() +static inline std::shared_ptr& s2_logger() { return realm::util::Logger::get_default_logger(); } diff --git a/src/external/s2/base/macros.h b/src/external/s2/base/macros.h index 96493f2bc04..eaf28febb66 100644 --- a/src/external/s2/base/macros.h +++ b/src/external/s2/base/macros.h @@ -22,69 +22,8 @@ #define ABSTRACT = 0 #endif -// The COMPILE_ASSERT macro can be used to verify that a compile time -// expression is true. For example, you could use it to verify the -// size of a static array: -// -// COMPILE_ASSERT(ARRAYSIZE(content_type_names) == CONTENT_NUM_TYPES, -// content_type_names_incorrect_size); -// -// or to make sure a struct is smaller than a certain size: -// -// COMPILE_ASSERT(sizeof(foo) < 128, foo_too_large); -// -// The second argument to the macro is the name of the variable. If -// the expression is false, most compilers will issue a warning/error -// containing the name of the variable. - -template -struct CompileAssert { -}; - #define COMPILE_ASSERT(expr, msg) \ - typedef CompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1] - -// Implementation details of COMPILE_ASSERT: -// -// - COMPILE_ASSERT works by defining an array type that has -1 -// elements (and thus is invalid) when the expression is false. -// -// - The simpler definition -// -// #define COMPILE_ASSERT(expr, msg) typedef char msg[(expr) ? 1 : -1] -// -// does not work, as gcc supports variable-length arrays whose sizes -// are determined at run-time (this is gcc's extension and not part -// of the C++ standard). As a result, gcc fails to reject the -// following code with the simple definition: -// -// int foo; -// COMPILE_ASSERT(foo, msg); // not supposed to compile as foo is -// // not a compile-time constant. -// -// - By using the type CompileAssert<(bool(expr))>, we ensures that -// expr is a compile-time constant. (Template arguments must be -// determined at compile-time.) -// -// - The outter parentheses in CompileAssert<(bool(expr))> are necessary -// to work around a bug in gcc 3.4.4 and 4.0.1. If we had written -// -// CompileAssert -// -// instead, these compilers will refuse to compile -// -// COMPILE_ASSERT(5 > 0, some_message); -// -// (They seem to think the ">" in "5 > 0" marks the end of the -// template argument list.) -// -// - The array size is (bool(expr) ? 1 : -1), instead of simply -// -// ((expr) ? 1 : -1). -// -// This is to avoid running into a bug in MS VC 7.1, which -// causes ((0.0) ? 1 : -1) to incorrectly evaluate to 1. - + static_assert(expr, #msg) // A macro to disallow the copy constructor and operator= functions // This should be used in the private: declarations for a class diff --git a/src/external/s2/s2loop.cc b/src/external/s2/s2loop.cc index 83bb163380f..81f242eae6d 100644 --- a/src/external/s2/s2loop.cc +++ b/src/external/s2/s2loop.cc @@ -134,10 +134,12 @@ bool S2Loop::IsValid(string* err) const { previous_index = ai + 1; if (crosses) { std::string msg = realm::util::format("Edges %1 and %2 cross. Edge locations in degrees: %3-%4 and %5-%6", i, ai, S2LatLng(vertex(i)), S2LatLng(vertex(i + 1)), S2LatLng(vertex(ai)), S2LatLng(vertex(ai + 1))); - s2_logger()->error(msg.c_str()); // additional debugging information, reverse Lat/Lng order. - if (NULL != err) { - *err = msg; + if (err) { + *err = msg; + } + else { + s2_logger()->error(msg.c_str()); } break; } diff --git a/src/external/s2/s2polygon.cc b/src/external/s2/s2polygon.cc index 98c4efbf99f..cd9414853d7 100644 --- a/src/external/s2/s2polygon.cc +++ b/src/external/s2/s2polygon.cc @@ -126,9 +126,11 @@ bool S2Polygon::IsValid(const vector& loops, string* err) { continue; } pair other = edges[key]; - s2_logger()->error("Duplicate edge: loop %1, edge %2 and loop %3, edge %4", i, j, other.first, other.second); if (err) { - *err = realm::util::format("Duplicate edge: loop %1, edge %2 and loop %3, edge %4", i, j, other.first, other.second); + *err = realm::util::format("Duplicate edge: ring %1, edge %2 and ring %3, edge %4", i, j, other.first, other.second); + } + else { + s2_logger()->error("Duplicate edge: ring %1, edge %2 and ring %3, edge %4", i, j, other.first, other.second); } return false; } @@ -139,16 +141,16 @@ bool S2Polygon::IsValid(const vector& loops, string* err) { // two loops cross. for (size_t i = 0; i < loops.size(); ++i) { if (!loops[i]->IsNormalized()) { - s2_logger()->error("Loop %1 encloses more than half the sphere", i); - if (err) *err = realm::util::format("Loop %1 encloses more than half the sphere", i); + s2_logger()->error("Ring %1 encloses more than half the sphere", i); + if (err) *err = realm::util::format("Ring %1 encloses more than half the sphere", i); return false; } for (size_t j = i + 1; j < loops.size(); ++j) { // This test not only checks for edge crossings, it also detects // cases where the two boundaries cross at a shared vertex. if (loops[i]->ContainsOrCrosses(loops[j]) < 0) { - s2_logger()->error("Loop %1 crosses loop %2", i, j); - if (err) *err = realm::util::format("Loop %1 crosses loop %2", i, j); + s2_logger()->error("Ring %1 crosses ring %2", i, j); + if (err) *err = realm::util::format("Ring %1 crosses ring %2", i, j); return false; } } @@ -1051,7 +1053,7 @@ bool S2Polygon::IsNormalized(string* err) const { } if (count > 1) { if (err) { - *err = realm::util::format("Loop %1 shares more than one vertex with its parent loop %2", i, GetParent(i)); + *err = realm::util::format("Ring %1 shares more than one vertex with its parent ring %2", i, GetParent(i)); } return false; } diff --git a/src/external/s2/util/math/mathutil.cc b/src/external/s2/util/math/mathutil.cc index 8dc8e8b35b8..34c2761b66c 100644 --- a/src/external/s2/util/math/mathutil.cc +++ b/src/external/s2/util/math/mathutil.cc @@ -15,19 +15,6 @@ using std::vector; #include "base/integral_types.h" #include "base/logging.h" - template - IntOut MathUtil::Round(FloatIn x) { - COMPILE_ASSERT(!MathLimits::kIsInteger, FloatIn_is_integer); - COMPILE_ASSERT(MathLimits::kIsInteger, IntOut_is_not_integer); - - // We don't use sgn(x) below because there is no need to distinguish the - // (x == 0) case. Also note that there are specialized faster versions - // of this function for Intel processors at the bottom of this file. - return static_cast(x < 0 ? (x - 0.5) : (x + 0.5)); - } - -template int MathUtil::Round(double x); - MathUtil::QuadraticRootType MathUtil::RealRootsForQuadratic(long double a, long double b, long double c, diff --git a/src/external/s2/util/math/mathutil.h b/src/external/s2/util/math/mathutil.h index 39090b1473e..1fb93f63312 100644 --- a/src/external/s2/util/math/mathutil.h +++ b/src/external/s2/util/math/mathutil.h @@ -7,7 +7,7 @@ #ifndef UTIL_MATH_MATHUTIL_H__ #define UTIL_MATH_MATHUTIL_H__ -#include +#include #include using std::min; using std::max; @@ -318,33 +318,6 @@ class MathUtil { static void ShardsToRead(const vector& shards_to_write, vector* shards_to_read); - // -------------------------------------------------------------------- - // Round, IntRound - // These functions round a floating-point number to an integer. They - // work for positive or negative numbers. - // - // Values that are halfway between two integers may be rounded up or - // down, for example IntRound(0.5) == 0 and IntRound(1.5) == 2. This - // allows these functions to be implemented efficiently on Intel - // processors (see the template specializations at the bottom of this - // file). You should not use these functions if you care about which - // way such half-integers are rounded. - // - // Example usage: - // double y, z; - // int x = IntRound(y + 3.7); - // int64 b = Round(0.3 * z); - // - // Note that the floating-point template parameter is typically inferred - // from the argument type, i.e. there is no need to specify it explicitly. - // -------------------------------------------------------------------- - template - static IntOut Round(FloatIn x); - - // Example usage: IntRound(3.6) (no need for IntRound(3.6)). - template - static int IntRound(FloatIn x) { return Round(x); } - // -------------------------------------------------------------------- // FastIntRound, FastInt64Round // Fast routines for converting floating-point numbers to integers. @@ -358,11 +331,10 @@ class MathUtil { // floating-point pipeline (unless programs are compiled specifically // for the Pentium 4, which has a new instruction to avoid this). // - // Numbers that are halfway between two integers may be rounded up or - // down. This is because the conversion is done using the default + // Numbers that are halfway between two integers follow the default // rounding mode, which rounds towards the closest even number in case // of ties. So for example, FastIntRound(0.5) == 0, but - // FastIntRound(1.5) == 2. These functions should only be used with + // FastIntRound(1.5) == 2. These functions should only be used with // applications that don't care about which way such half-integers are // rounded. // @@ -370,10 +342,8 @@ class MathUtil { // functions (for "int" and "int64" only), but it's safer to call them // directly. // - // This functions are equivalent to lrint() and llrint() as defined in - // the ISO C99 standard. Unfortunately this standard does not seem to - // widely adopted yet and these functions are not available by default. - // -------------------------------------------------------------------- + // This functions are equivalent to rint() and llrint(). + // -------------------------------------------------------------------- static int32 FastIntRound(double x) { // This function is not templatized because gcc doesn't seem to be able @@ -401,7 +371,7 @@ class MathUtil { return result; #endif // if defined __x86_64__ || ... #else - return Round(x); + return static_cast(std::rint(x)); #endif // if defined __GNUC__ && ... } @@ -427,7 +397,7 @@ class MathUtil { return result; #endif // if defined __i386__ #else - return Round(x); + return static_cast(std::llrint(x)); #endif // if defined __GNUC__ && ... } @@ -688,35 +658,6 @@ class MathUtil { } }; -// ========================================================================= // - -#if (defined __i386__ || defined __x86_64__) && defined __GNUC__ && !(REALM_WATCHOS && !REALM_APPLE_DEVICE) - -// We define template specializations of Round() to get the more efficient -// Intel versions when possible. Note that gcc does not currently support -// partial specialization of templatized functions. -template<> -inline int32 MathUtil::Round(double x) { - return FastIntRound(x); -} - -template<> -inline int32 MathUtil::Round(float x) { - return FastIntRound(x); -} - -template<> -inline int64 MathUtil::Round(double x) { - return FastInt64Round(x); -} - -template<> -inline int64 MathUtil::Round(float x) { - return FastInt64Round(x); -} - -#endif - template bool MathUtil::WithinFraction(const T x, const T y, const T fraction) { // not just "0 <= fraction" to fool the compiler for unsigned types diff --git a/src/realm.h b/src/realm.h index 331eaee685c..549ab231faf 100644 --- a/src/realm.h +++ b/src/realm.h @@ -2855,11 +2855,9 @@ RLM_API void realm_app_config_set_base_url(realm_app_config_t*, const char*) RLM RLM_API void realm_app_config_set_local_app_name(realm_app_config_t*, const char*) RLM_API_NOEXCEPT; RLM_API void realm_app_config_set_local_app_version(realm_app_config_t*, const char*) RLM_API_NOEXCEPT; RLM_API void realm_app_config_set_default_request_timeout(realm_app_config_t*, uint64_t ms) RLM_API_NOEXCEPT; -RLM_API void realm_app_config_set_platform(realm_app_config_t*, const char*) RLM_API_NOEXCEPT; RLM_API void realm_app_config_set_platform_version(realm_app_config_t*, const char*) RLM_API_NOEXCEPT; RLM_API void realm_app_config_set_sdk_version(realm_app_config_t*, const char*) RLM_API_NOEXCEPT; RLM_API void realm_app_config_set_sdk(realm_app_config_t* config, const char* sdk) RLM_API_NOEXCEPT; -RLM_API void realm_app_config_set_cpu_arch(realm_app_config_t* config, const char* cpu_arch) RLM_API_NOEXCEPT; RLM_API void realm_app_config_set_device_name(realm_app_config_t* config, const char* device_name) RLM_API_NOEXCEPT; RLM_API void realm_app_config_set_device_version(realm_app_config_t* config, const char* device_version) RLM_API_NOEXCEPT; @@ -2867,6 +2865,7 @@ RLM_API void realm_app_config_set_framework_name(realm_app_config_t* config, const char* framework_name) RLM_API_NOEXCEPT; RLM_API void realm_app_config_set_framework_version(realm_app_config_t* config, const char* framework_version) RLM_API_NOEXCEPT; +RLM_API void realm_app_config_set_bundle_id(realm_app_config_t* config, const char* bundle_id) RLM_API_NOEXCEPT; /** * Get an existing @a realm_app_credentials_t and return it's json representation diff --git a/src/realm/array.cpp b/src/realm/array.cpp index 69189f66fc3..434b9649b6a 100644 --- a/src/realm/array.cpp +++ b/src/realm/array.cpp @@ -1168,62 +1168,75 @@ void Array::update_width_cache_from_header() noexcept } // This method reads 8 concecutive values into res[8], starting from index 'ndx'. It's allowed for the 8 values to -// exceed array length; in this case, remainder of res[8] will be left untouched. +// exceed array length; in this case, remainder of res[8] will be be set to 0. template void Array::get_chunk(size_t ndx, int64_t res[8]) const noexcept { REALM_ASSERT_3(ndx, <, m_size); - // To make Valgrind happy. Todo, I *think* it should work without, now, but if it reappears, add memset again. - // memset(res, 0, 8*8); - - if (REALM_X86_OR_X64_TRUE && (w == 1 || w == 2 || w == 4) && ndx + 32 < m_size) { - // This method is *multiple* times faster than performing 8 times get, even if unrolled. Apparently - // compilers - // can't figure out to optimize it. - uint64_t c; - size_t bytealign = ndx / (8 / no0(w)); - if (w == 1) { - c = *reinterpret_cast(m_data + bytealign); - c >>= (ndx - bytealign * 8) * w; - } - else if (w == 2) { - c = *reinterpret_cast(m_data + bytealign); - c >>= (ndx - bytealign * 4) * w; - } - else if (w == 4) { - c = *reinterpret_cast(m_data + bytealign); - c >>= (ndx - bytealign * 2) * w; + size_t i = 0; + + // if constexpr to avoid producing spurious warnings resulting from + // instantiating for too large w + if constexpr (w > 0 && w <= 4) { + // Calling get() in a loop results in one load per call to get, but + // for w < 8 we can do better than that + constexpr size_t elements_per_byte = 8 / w; + + // Round m_size down to byte granularity as the trailing bits in the last + // byte are uninitialized + size_t bytes_available = m_size / elements_per_byte; + + // Round start and end to be byte-aligned. Start is rounded down and + // end is rounded up as we may read up to 7 unused bits at each end. + size_t start = ndx / elements_per_byte; + size_t end = std::min(bytes_available, (ndx + 8 + elements_per_byte - 1) / elements_per_byte); + + if (end > start) { + // Loop in reverse order because data is stored in little endian order + uint64_t c = 0; + for (size_t i = end; i > start; --i) { + c <<= 8; + c += *reinterpret_cast(m_data + i - 1); + } + // Trim off leading bits which aren't part of the requested range + c >>= (ndx - start * elements_per_byte) * w; + + uint64_t mask = (1ULL << w) - 1ULL; + res[0] = (c >> 0 * w) & mask; + res[1] = (c >> 1 * w) & mask; + res[2] = (c >> 2 * w) & mask; + res[3] = (c >> 3 * w) & mask; + res[4] = (c >> 4 * w) & mask; + res[5] = (c >> 5 * w) & mask; + res[6] = (c >> 6 * w) & mask; + res[7] = (c >> 7 * w) & mask; + + // Read the last few elements via get if needed + i = std::min(8, end * elements_per_byte - ndx); } - uint64_t mask = (w == 64 ? ~0ULL : ((1ULL << (w == 64 ? 0 : w)) - 1ULL)); - // The '?' is to avoid warnings about shifting too much - res[0] = (c >> 0 * (w > 4 ? 0 : w)) & mask; - res[1] = (c >> 1 * (w > 4 ? 0 : w)) & mask; - res[2] = (c >> 2 * (w > 4 ? 0 : w)) & mask; - res[3] = (c >> 3 * (w > 4 ? 0 : w)) & mask; - res[4] = (c >> 4 * (w > 4 ? 0 : w)) & mask; - res[5] = (c >> 5 * (w > 4 ? 0 : w)) & mask; - res[6] = (c >> 6 * (w > 4 ? 0 : w)) & mask; - res[7] = (c >> 7 * (w > 4 ? 0 : w)) & mask; } - else { - size_t i = 0; - for (; i + ndx < m_size && i < 8; i++) - res[i] = get(ndx + i); - for (; i < 8; i++) - res[i] = 0; - } + for (; i + ndx < m_size && i < 8; i++) + res[i] = get(ndx + i); + for (; i < 8; i++) + res[i] = 0; #ifdef REALM_DEBUG for (int j = 0; j + ndx < m_size && j < 8; j++) { int64_t expected = get(ndx + j); - if (res[j] != expected) - REALM_ASSERT(false); + REALM_ASSERT(res[j] == expected); } #endif } +template <> +void Array::get_chunk<0>(size_t ndx, int64_t res[8]) const noexcept +{ + REALM_ASSERT_3(ndx, <, m_size); + memset(res, 0, sizeof(int64_t) * 8); +} + template void Array::set(size_t ndx, int64_t value) diff --git a/src/realm/array_backlink.cpp b/src/realm/array_backlink.cpp index 0f2922b8053..c84fdbec0c3 100644 --- a/src/realm/array_backlink.cpp +++ b/src/realm/array_backlink.cpp @@ -101,14 +101,19 @@ void ArrayBacklink::add(size_t ndx, ObjKey key) bool ArrayBacklink::remove(size_t ndx, ObjKey key) { uint64_t value = Array::get(ndx); - REALM_ASSERT(value != 0); + REALM_ASSERT_DEBUG(value != 0); + if (value == 0) + return true; // If there is only a single backlink, it can be stored as // a tagged value if ((value & 1) != 0) { - REALM_ASSERT_3(int64_t(value >> 1), ==, key.value); - set(ndx, 0); - return true; + REALM_ASSERT_DEBUG(int64_t(value >> 1) == key.value); + if (int64_t(value >> 1) == key.value) { + set(ndx, 0); + return true; + } + return false; } // if there is a list of backlinks we have to find @@ -119,10 +124,12 @@ bool ArrayBacklink::remove(size_t ndx, ObjKey key) size_t last_ndx = backlink_list.size() - 1; size_t backlink_ndx = backlink_list.find_first(key.value); - REALM_ASSERT_3(backlink_ndx, !=, not_found); - if (backlink_ndx != last_ndx) - backlink_list.set(backlink_ndx, backlink_list.get(last_ndx)); - backlink_list.truncate(last_ndx); // Throws + REALM_ASSERT_DEBUG(backlink_ndx != not_found); + if (backlink_ndx != not_found) { + if (backlink_ndx != last_ndx) + backlink_list.set(backlink_ndx, backlink_list.get(last_ndx)); + backlink_list.truncate(last_ndx); // Throws + } // If there is only one backlink left we can inline it as tagged value if (last_ndx == 1) { diff --git a/src/realm/array_blob.cpp b/src/realm/array_blob.cpp index c30aa1756a4..1751c5d2c8e 100644 --- a/src/realm/array_blob.cpp +++ b/src/realm/array_blob.cpp @@ -142,7 +142,7 @@ ref_type ArrayBlob::replace(size_t begin, size_t end, const char* data, size_t d return new_root.blob_replace(begin, end, data, data_size, add_zero_term); } - if (remove_size == add_size && is_read_only() && memcmp(m_data + begin, data, data_size) == 0) + if (remove_size == add_size && is_read_only() && (data_size == 0 || memcmp(m_data + begin, data, data_size) == 0)) return get_ref(); // Reallocate if needed - also updates header diff --git a/src/realm/bplustree.hpp b/src/realm/bplustree.hpp index 449d834ee7a..8555d335b64 100644 --- a/src/realm/bplustree.hpp +++ b/src/realm/bplustree.hpp @@ -526,6 +526,27 @@ class BPlusTree : public BPlusTreeBase { m_root->bptree_traverse(func); } + template + void for_all(Func&& callback) const + { + using Ret = std::invoke_result_t; + m_root->bptree_traverse([&callback](BPlusTreeNode* node, size_t) { + LeafNode* leaf = static_cast(node); + size_t sz = leaf->size(); + for (size_t i = 0; i < sz; i++) { + if constexpr (std::is_same_v) { + callback(leaf->get(i)); + } + else { + if (!callback(leaf->get(i))) + return IteratorControl::Stop; + } + } + return IteratorControl::AdvanceToNext; + }); + } + + void dump_values(std::ostream& o, int level) const { std::string indent(" ", level * 2); diff --git a/src/realm/cluster_tree.cpp b/src/realm/cluster_tree.cpp index ba4068d34e3..ea9256f92ed 100644 --- a/src/realm/cluster_tree.cpp +++ b/src/realm/cluster_tree.cpp @@ -1205,16 +1205,10 @@ void ClusterTree::remove_all_links(CascadeState& state) values.init_from_parent(); // Iterate through values and insert all link values - values.traverse([&](BPlusTreeNode* node, size_t) { - auto bplustree_leaf = static_cast::LeafNode*>(node); - auto sz = bplustree_leaf->size(); - for (size_t i = 0; i < sz; i++) { - auto mix = bplustree_leaf->get(i); - if (mix.is_type(type_TypedLink)) { - links.push_back(mix.get()); - } + values.for_all([&](Mixed val) { + if (val.is_type(type_TypedLink)) { + links.push_back(val.get()); } - return IteratorControl::AdvanceToNext; }); if (links.size() > 0) { diff --git a/src/realm/dictionary.cpp b/src/realm/dictionary.cpp index 9121be7d3d3..0e6179e7110 100644 --- a/src/realm/dictionary.cpp +++ b/src/realm/dictionary.cpp @@ -133,7 +133,7 @@ Mixed Dictionary::get_any(size_t ndx) const { // Note: `size()` calls `update_if_needed()`. CollectionBase::validate_index("get_any()", ndx, size()); - return m_values->get(ndx); + return do_get(ndx); } std::pair Dictionary::get_pair(size_t ndx) const @@ -703,8 +703,10 @@ void Dictionary::nullify(Mixed key) void Dictionary::remove_backlinks(CascadeState& state) const { - for (auto&& elem : *this) { - clear_backlink(elem.second, state); + if (size() > 0) { + m_values->for_all([&](Mixed val) { + clear_backlink(val, state); + }); } } @@ -831,12 +833,8 @@ Mixed Dictionary::do_get(size_t ndx) const Mixed val = m_values->get(ndx); // Filter out potential unresolved links - if (val.is_type(type_TypedLink)) { - auto link = val.get(); - auto key = link.get_obj_key(); - if (key.is_unresolved()) { - return {}; - } + if (val.is_type(type_TypedLink) && val.get().is_unresolved()) { + return {}; } return val; } diff --git a/src/realm/geospatial.cpp b/src/realm/geospatial.cpp index f5be8419388..a5eafbae025 100644 --- a/src/realm/geospatial.cpp +++ b/src/realm/geospatial.cpp @@ -49,13 +49,15 @@ #include #include #include +#include namespace { -static bool type_is_valid(std::string str_type) +static bool type_is_valid(realm::StringData str_type) { - std::transform(str_type.begin(), str_type.end(), str_type.begin(), realm::toLowerAscii); - return str_type == "point"; + return str_type.size() == 5 && (str_type[0] == 'P' || str_type[0] == 'p') && + (str_type[1] == 'o' || str_type[1] == 'O') && (str_type[2] == 'i' || str_type[2] == 'I') && + (str_type[3] == 'n' || str_type[3] == 'N') && (str_type[4] == 't' || str_type[4] == 'T'); } } // anonymous namespace @@ -71,8 +73,8 @@ std::string Geospatial::get_type_string() const noexcept return "Box"; case Type::Polygon: return "Polygon"; - case Type::CenterSphere: - return "CenterSphere"; + case Type::Circle: + return "Circle"; case Type::Invalid: return "Invalid"; } @@ -107,7 +109,7 @@ bool Geospatial::is_geospatial(const TableRef table, ColKey link_col) return true; } -Geospatial Geospatial::from_obj(const Obj& obj, ColKey type_col, ColKey coords_col) +std::optional Geospatial::point_from_obj(const Obj& obj, ColKey type_col, ColKey coords_col) { if (!type_col) { type_col = obj.get_table()->get_column_key(StringData(c_geo_point_type_col_name)); @@ -120,20 +122,20 @@ Geospatial Geospatial::from_obj(const Obj& obj, ColKey type_col, ColKey coords_c coords_col = obj.get_table()->get_column_key(StringData(c_geo_point_coords_col_name)); } else { - REALM_ASSERT_EX(type_col.get_type() == ColumnType(ColumnType::Type::Double), type_col.get_type()); - REALM_ASSERT(type_col.is_list()); + REALM_ASSERT_EX(coords_col.get_type() == ColumnType(ColumnType::Type::Double), coords_col.get_type()); + REALM_ASSERT(coords_col.is_list()); } - String geo_type = obj.get(type_col); - if (!type_is_valid(geo_type)) { + if (!type_is_valid(obj.get(type_col))) { throw IllegalOperation("The only Geospatial type currently supported is 'point'"); } Lst coords = obj.get_list(coords_col); - if (coords.size() < 2) { - return Geospatial(); // invalid + const size_t coord_size = coords.size(); + if (coord_size < 2) { + return {}; // invalid } - if (coords.size() > 2) { + if (coord_size > 2) { return GeoPoint{coords[0], coords[1], coords[2]}; } return GeoPoint{coords[0], coords[1]}; @@ -187,28 +189,29 @@ void Geospatial::assign_to(Obj& link) const auto&& point = get(); link.set(type_col, get_type_string()); Lst coords = link.get_list(coords_col); - if (coords.size() >= 1) { + size_t existing_size = coords.size(); + std::optional altitude = point.get_altitude(); + if (existing_size >= 1) { coords.set(0, point.longitude); } else { coords.add(point.longitude); } - if (coords.size() >= 2) { + if (existing_size >= 2) { coords.set(1, point.latitude); } else { coords.add(point.latitude); } - std::optional altitude = point.get_altitude(); if (altitude) { - if (coords.size() >= 3) { + if (existing_size >= 3) { coords.set(2, *altitude); } else { coords.add(*altitude); } } - else if (coords.size() >= 3) { + else if (existing_size >= 3) { coords.remove(2, coords.size()); } } @@ -221,6 +224,30 @@ static std::string point_str(const GeoPoint& point) return util::format("[%1, %2]", point.longitude, point.latitude); } +Status Geospatial::is_valid() const noexcept +{ + switch (get_type()) { + case Type::Polygon: + case Type::Box: + case Type::Circle: + return get_region().get_conversion_status(); + default: + return Status::OK(); + } +} + +bool Geospatial::contains(const GeoPoint& point) const noexcept +{ + return get_region().contains(point); +} + +GeoRegion& Geospatial::get_region() const +{ + if (!m_region) + m_region = std::make_unique(*this); + return *m_region.get(); +} + std::string Geospatial::to_string() const { return mpark::visit( @@ -244,8 +271,8 @@ std::string Geospatial::to_string() const } return util::format("GeoPolygon(%1)", points); }, - [](const GeoCenterSphere& sphere) { - return util::format("GeoSphere(%1, %2)", point_str(sphere.center), sphere.radius_radians); + [](const GeoCircle& circle) { + return util::format("GeoCircle(%1, %2)", point_str(circle.center), circle.radius_radians); }, [](const mpark::monostate&) { return std::string("NULL"); @@ -259,61 +286,246 @@ std::ostream& operator<<(std::ostream& ostr, const Geospatial& geo) return ostr; } +// The following validation follows the server: +// https://github.com/mongodb/mongo/blob/053ff9f355555cddddf3a476ffa9ddf899b1657d/src/mongo/db/geo/geoparser.cpp#L140 + + +// Technically lat/long bounds, not really tied to earth radius. +static bool is_valid_lat_lng(double lng, double lat) +{ + return abs(lng) <= 180 && abs(lat) <= 90; +} + +static Status coord_to_point(double lng, double lat, S2Point& out) +{ + if (!is_valid_lat_lng(lng, lat)) + return Status(ErrorCodes::InvalidQueryArg, + util::format("Longitude/latitude is out of bounds, lng: %1 lat: %2", lng, lat)); + // Note that it's (lat, lng) for S2 but (lng, lat) for MongoDB. + S2LatLng ll = S2LatLng::FromDegrees(lat, lng).Normalized(); + // This shouldn't happen since we should only have valid lng/lats. + REALM_ASSERT_EX(ll.is_valid(), util::format("coords invalid after normalization, lng = %1, lat = %2", lng, lat)); + out = ll.ToPoint(); + return Status::OK(); +} + +static void erase_duplicate_adjacent_points(std::vector& vertices) +{ + vertices.erase(std::unique(vertices.begin(), vertices.end()), vertices.end()); +} + +static Status is_ring_closed(const std::vector& ring, const std::vector& points) +{ + if (ring.empty()) { + return Status(ErrorCodes::InvalidQueryArg, "Ring has no vertices"); + } + + if (points[0] != points[points.size() - 1]) { + return Status(ErrorCodes::InvalidQueryArg, + util::format("Ring is not closed, first vertex '%1' does not equal last vertex '%2'", points[0], + points[points.size() - 1])); + } + + return Status::OK(); +} + +static Status parse_polygon_coordinates(const GeoPolygon& polygon, S2Polygon* out) +{ + REALM_ASSERT(out); + std::vector rings; + rings.reserve(polygon.points.size()); + Status status = Status::OK(); + std::string err; + + // if the polygon is successfully created s2 takes ownership + // of the pointers and clears our vector + auto guard = util::make_scope_exit([&rings]() noexcept { + for (auto& ring : rings) { + delete ring; + } + }); + // Iterate all rings of the polygon. + for (size_t i = 0; i < polygon.points.size(); ++i) { + // Check if the ring is closed. + std::vector points; + points.reserve(polygon.points[i].size()); + for (auto&& p : polygon.points[i]) { + S2Point s2p; + status = coord_to_point(p.longitude, p.latitude, s2p); + if (!status.is_ok()) { + return status; + } + points.push_back(s2p); + } + + status = is_ring_closed(points, polygon.points[i]); + if (!status.is_ok()) + return status; + + erase_duplicate_adjacent_points(points); + // Drop the duplicated last point. + points.resize(points.size() - 1); + + // At least 3 vertices. + if (points.size() < 3) { + return Status( + ErrorCodes::InvalidQueryArg, + util::format("Ring %1 must have at least 3 different vertices, %2 unique vertices were provided", i, + points.size())); + } + + rings.push_back(new S2Loop(points)); + S2Loop* ring = rings.back(); + + // Check whether this ring is valid if vaildation hasn't been already done on 2dSphere index + // insertion which is controlled by the 'skipValidation' flag. + // 1. At least 3 vertices. + // 2. All vertices must be unit length. Guaranteed by parsePoints(). + // 3. Rings are not allowed to have any duplicate vertices. + // 4. Non-adjacent edges are not allowed to intersect. + if (!ring->IsValid(&err)) { + return Status(ErrorCodes::InvalidQueryArg, util::format("Ring %1 is not valid: '%2'", i, err)); + } + // If the ring is more than one hemisphere, invert it. + ring->Normalize(); + + // Check the first ring must be the exterior ring and any others must be + // interior rings or holes. + if (rings.size() > 1 && !rings[0]->Contains(ring)) { + return Status(ErrorCodes::InvalidQueryArg, + util::format("Secondary ring %1 not contained by first exterior ring - " + "secondary rings must be holes in the first ring", + i)); + } + } + + if (rings.empty()) { + return Status(ErrorCodes::InvalidQueryArg, "Polygon has no rings."); + } + + // Check if the given rings form a valid polygon. + // 1. If a ring contains an edge AB, then no other ring may contain AB or BA. + // 2. No ring covers more than half of the sphere. + // 3. No two ring cross. + if (!S2Polygon::IsValid(rings, &err)) + return Status(ErrorCodes::InvalidQueryArg, util::format("Polygon isn't valid: '%1'", err)); + + // Given all rings are valid / normalized and S2Polygon::IsValid() above returns true. + // The polygon must be valid. See S2Polygon member function IsValid(). + + { + // Transfer ownership of the rings and clears ring vector. + out->Init(&rings); + } + + // Check if every ring of this polygon shares at most one vertex with + // its parent ring. + if (!out->IsNormalized(&err)) + // "err" looks like "Ring 1 shares more than one vertex with its parent ring 0" + return Status(ErrorCodes::InvalidQueryArg, util::format("Polygon is not normalized: '%1'", err)); + + // S2Polygon contains more than one ring, which is allowed by S2, but not by GeoJSON. + // + // Rings are indexed according to a preorder traversal of the nesting hierarchy. + // GetLastDescendant() returns the index of the last ring that is contained within + // a given ring. We guarantee that the first ring is the exterior ring. + if (out->GetLastDescendant(0) < out->num_loops() - 1) { + return Status(ErrorCodes::InvalidQueryArg, "Only one exterior polygon ring is allowed"); + } + + // In GeoJSON, only one nesting is allowed. + // The depth of a ring is set by polygon according to the nesting hierarchy of polygon, + // so the exterior ring's depth is 0, a hole in it is 1, etc. + for (int i = 0; i < out->num_loops(); i++) { + if (out->loop(i)->depth() > 1) { + return Status(ErrorCodes::InvalidQueryArg, + util::format("Polygon interior rings cannot be nested: %1", i)); + } + } + return Status::OK(); +} + GeoRegion::GeoRegion(const Geospatial& geo) + : m_status(Status::OK()) { struct Visitor { + Visitor(Status& out) + : m_status_out(out) + { + m_status_out = Status::OK(); + } + Status& m_status_out; std::unique_ptr operator()(const GeoBox& box) const { - return std::make_unique(S2LatLng::FromDegrees(box.lo.latitude, box.lo.longitude), - S2LatLng::FromDegrees(box.hi.latitude, box.hi.longitude)); + S2Point s2_lo, s2_hi; + m_status_out = coord_to_point(box.lo.longitude, box.lo.latitude, s2_lo); + if (!m_status_out.is_ok()) + return nullptr; + m_status_out = coord_to_point(box.hi.longitude, box.hi.latitude, s2_hi); + if (!m_status_out.is_ok()) + return nullptr; + auto ret = std::make_unique(S2LatLng(s2_lo), S2LatLng(s2_hi)); + if (!ret->is_valid()) { + m_status_out = Status(ErrorCodes::InvalidQueryArg, "Invalid rectangle"); + return nullptr; + } + return ret; } std::unique_ptr operator()(const GeoPolygon& polygon) const { - REALM_ASSERT(polygon.points.size() >= 1); - std::vector loops; - loops.reserve(polygon.points.size()); - std::vector points; - for (auto& geo_points : polygon.points) { - points.clear(); - points.reserve(geo_points.size()); - for (auto&& p : geo_points) { - // FIXME rewrite without copying - points.push_back(S2LatLng::FromDegrees(p.latitude, p.longitude).ToPoint()); - } - loops.push_back(new S2Loop(points)); - } - // S2Polygon takes ownership of all the S2Loop pointers - return std::make_unique(&loops); + auto poly = std::make_unique(); + m_status_out = parse_polygon_coordinates(polygon, poly.get()); + return poly; } - std::unique_ptr operator()(const GeoCenterSphere& sphere) const + std::unique_ptr operator()(const GeoCircle& circle) const { - auto center = S2LatLng::FromDegrees(sphere.center.latitude, sphere.center.longitude).ToPoint(); - auto radius = S1Angle::Radians(sphere.radius_radians); + S2Point center; + m_status_out = coord_to_point(circle.center.longitude, circle.center.latitude, center); + if (!m_status_out.is_ok()) + return nullptr; + if (circle.radius_radians < 0 || std::isnan(circle.radius_radians)) { + m_status_out = + Status(ErrorCodes::InvalidQueryArg, "The radius of a circle must be a non-negative number"); + return nullptr; + } + auto radius = S1Angle::Radians(circle.radius_radians); return std::make_unique(S2Cap::FromAxisAngle(center, radius)); } std::unique_ptr operator()(const mpark::monostate&) const { - REALM_UNREACHABLE(); + m_status_out = Status(ErrorCodes::InvalidQueryArg, + "NULL cannot be used on the right hand side of a GEOWITHIN query"); + return nullptr; } std::unique_ptr operator()(const GeoPoint&) const { - REALM_UNREACHABLE(); + m_status_out = Status(ErrorCodes::InvalidQueryArg, + "A point cannot be used on the right hand side of GEOWITHIN query"); + return nullptr; } }; - m_region = mpark::visit(Visitor(), geo.m_value); + m_region = mpark::visit(Visitor(m_status), geo.m_value); } GeoRegion::~GeoRegion() = default; -bool GeoRegion::contains(const GeoPoint& geo_point) const noexcept +bool GeoRegion::contains(const std::optional& geo_point) const noexcept { - auto point = S2LatLng::FromDegrees(geo_point.latitude, geo_point.longitude).ToPoint(); + if (!m_status.is_ok() || !geo_point) { + return false; + } + auto point = S2LatLng::FromDegrees(geo_point->latitude, geo_point->longitude).ToPoint(); return m_region->VirtualContainsPoint(point); } +Status GeoRegion::get_conversion_status() const noexcept +{ + return m_status; +} + } // namespace realm diff --git a/src/realm/geospatial.hpp b/src/realm/geospatial.hpp index d3ee46178d3..ae25aa44a87 100644 --- a/src/realm/geospatial.hpp +++ b/src/realm/geospatial.hpp @@ -36,6 +36,7 @@ namespace realm { class Obj; class TableRef; +class Geospatial; struct GeoPoint { GeoPoint() = delete; @@ -61,6 +62,10 @@ struct GeoPoint { (latitude == other.latitude || (std::isnan(latitude) && std::isnan(other.latitude))) && ((!has_altitude() && !other.has_altitude()) || altitude == other.altitude); } + bool operator!=(const GeoPoint& other) const + { + return !(*this == other); + } bool is_valid() const { @@ -145,11 +150,11 @@ struct GeoPolygon { std::vector> points; }; -struct GeoCenterSphere { +struct GeoCircle { double radius_radians = 0.0; GeoPoint center; - bool operator==(const GeoCenterSphere& other) const + bool operator==(const GeoCircle& other) const { return radius_radians == other.radius_radians && center == other.center; } @@ -158,15 +163,28 @@ struct GeoCenterSphere { // src/mongo/db/geo/geoconstants.h constexpr static double c_radius_meters = 6378100.0; - static GeoCenterSphere from_kms(double km, GeoPoint&& p) + static GeoCircle from_kms(double km, GeoPoint&& p) { - return GeoCenterSphere{km * 1000 / c_radius_meters, p}; + return GeoCircle{km * 1000 / c_radius_meters, p}; } }; +class GeoRegion { +public: + GeoRegion(const Geospatial& geo); + ~GeoRegion(); + + bool contains(const std::optional& point) const noexcept; + Status get_conversion_status() const noexcept; + +private: + std::unique_ptr m_region; + Status m_status; +}; + class Geospatial { public: - enum class Type : uint8_t { Invalid, Point, Box, Polygon, CenterSphere }; + enum class Type : uint8_t { Invalid, Point, Box, Polygon, Circle }; Geospatial() : m_value(mpark::monostate{}) @@ -184,18 +202,27 @@ class Geospatial { : m_value(polygon) { } - Geospatial(GeoCenterSphere centerSphere) - : m_value(centerSphere) + Geospatial(GeoCircle circle) + : m_value(circle) { } - Geospatial(const Geospatial&) = default; - Geospatial& operator=(const Geospatial&) = default; + Geospatial(const Geospatial& other) + : m_value(other.m_value) + { + } + Geospatial& operator=(const Geospatial& other) + { + if (this != &other) { + m_value = other.m_value; + } + return *this; + } Geospatial(Geospatial&& other) = default; Geospatial& operator=(Geospatial&&) = default; - static Geospatial from_obj(const Obj& obj, ColKey type_col = {}, ColKey coords_col = {}); + static std::optional point_from_obj(const Obj& obj, ColKey type_col = {}, ColKey coords_col = {}); static Geospatial from_link(const Obj& obj); static bool is_geospatial(const TableRef table, ColKey link_col); void assign_to(Obj& link) const; @@ -206,12 +233,10 @@ class Geospatial { template const T& get() const noexcept; - bool is_valid() const noexcept - { - return get_type() != Type::Invalid; - } + Status is_valid() const noexcept; + + bool contains(const GeoPoint& point) const noexcept; - bool is_within(const Geospatial& bounds) const noexcept; std::string to_string() const; bool operator==(const Geospatial& other) const @@ -232,26 +257,18 @@ class Geospatial { private: // Must be in the same order as the Type enum - mpark::variant m_value; + mpark::variant m_value; friend class GeoRegion; -}; - -class GeoRegion { -public: - GeoRegion(const Geospatial& geo); - ~GeoRegion(); - bool contains(const GeoPoint& point) const noexcept; - -private: - std::unique_ptr m_region; + mutable std::unique_ptr m_region; + GeoRegion& get_region() const; }; template <> -inline const GeoCenterSphere& Geospatial::get() const noexcept +inline const GeoCircle& Geospatial::get() const noexcept { - return mpark::get(m_value); + return mpark::get(m_value); } template <> diff --git a/src/realm/group.cpp b/src/realm/group.cpp index 8822df25bb6..63237c14e46 100644 --- a/src/realm/group.cpp +++ b/src/realm/group.cpp @@ -904,6 +904,14 @@ Obj Group::get_object(ObjLink link) return ct->get(key); } +Obj Group::try_get_object(ObjLink link) noexcept +{ + auto target_table = get_table(link.get_table_key()); + ObjKey key = link.get_obj_key(); + ClusterTree* ct = key.is_unresolved() ? target_table->m_tombstones.get() : &target_table->m_clusters; + return ct->try_get_obj(key); +} + void Group::validate(ObjLink link) const { if (auto tk = link.get_table_key()) { diff --git a/src/realm/group.hpp b/src/realm/group.hpp index 9d90501c0d5..907b607a71b 100644 --- a/src/realm/group.hpp +++ b/src/realm/group.hpp @@ -326,6 +326,7 @@ class Group : public ArrayParent { void rename_table(StringData name, StringData new_name, bool require_unique_name = true); Obj get_object(ObjLink link); + Obj try_get_object(ObjLink link) noexcept; void validate(ObjLink link) const; //@} @@ -1326,7 +1327,9 @@ class CascadeState { { // Nullify immediately if we don't need to send cascade notifications if (!notification_handler()) { - src_table.get_object(origin_key).nullify_link(src_col_key, target_link); + if (Obj obj = src_table.try_get_object(origin_key)) { + std::move(obj).nullify_link(src_col_key, target_link); + } return; } diff --git a/src/realm/metrics/query_info.cpp b/src/realm/metrics/query_info.cpp index 5b5e75816a9..4ff00370ead 100644 --- a/src/realm/metrics/query_info.cpp +++ b/src/realm/metrics/query_info.cpp @@ -20,7 +20,6 @@ #include #include #include -#include using namespace realm; using namespace realm::metrics; diff --git a/src/realm/metrics/query_info.hpp b/src/realm/metrics/query_info.hpp index 7bc9795db5f..1334a076880 100644 --- a/src/realm/metrics/query_info.hpp +++ b/src/realm/metrics/query_info.hpp @@ -21,7 +21,6 @@ #include #include -#include #include #include diff --git a/src/realm/mixed.cpp b/src/realm/mixed.cpp index 4707723074c..f292cb5a411 100644 --- a/src/realm/mixed.cpp +++ b/src/realm/mixed.cpp @@ -617,7 +617,12 @@ size_t Mixed::hash() const hash = murmur2_or_cityhash(unsigned_data, 12); break; } - case type_Decimal: + case type_Decimal: { + auto value = get(); + auto unsigned_data = reinterpret_cast(value.raw()); + hash = murmur2_or_cityhash(unsigned_data, sizeof(Decimal128::Bid128)); + break; + } case type_Mixed: case type_Link: case type_LinkList: diff --git a/src/realm/mixed.hpp b/src/realm/mixed.hpp index 002dddef3d1..4d7378d28c8 100644 --- a/src/realm/mixed.hpp +++ b/src/realm/mixed.hpp @@ -168,8 +168,6 @@ class Mixed { { } - ~Mixed() noexcept {} - DataType get_type() const noexcept { REALM_ASSERT(m_type); @@ -301,6 +299,7 @@ class Mixed { return _is_numeric(head) && _is_numeric(tail...); } }; +static_assert(std::is_trivially_destructible_v); class OwnedMixed : public Mixed { public: diff --git a/src/realm/obj.cpp b/src/realm/obj.cpp index c53132fe9b0..bf70e821162 100644 --- a/src/realm/obj.cpp +++ b/src/realm/obj.cpp @@ -2036,7 +2036,11 @@ bool Obj::remove_backlink(ColKey col_key, ObjLink old_link, CascadeState& state) if (old_link && old_link.get_obj_key()) { REALM_ASSERT(m_table->valid_column(col_key)); ObjKey old_key = old_link.get_obj_key(); - auto target_obj = m_table->get_parent_group()->get_object(old_link); + auto target_obj = m_table->get_parent_group()->try_get_object(old_link); + REALM_ASSERT_DEBUG(target_obj); + if (!target_obj) { + return false; + } TableRef target_table = target_obj.get_table(); ColKey backlink_col_key; auto type = col_key.get_type(); diff --git a/src/realm/object-store/c_api/app.cpp b/src/realm/object-store/c_api/app.cpp index b3f0564770b..d8685faebb5 100644 --- a/src/realm/object-store/c_api/app.cpp +++ b/src/realm/object-store/c_api/app.cpp @@ -214,11 +214,6 @@ RLM_API void realm_app_config_set_default_request_timeout(realm_app_config_t* co config->default_request_timeout_ms = ms; } -RLM_API void realm_app_config_set_platform(realm_app_config_t* config, const char* platform) noexcept -{ - config->device_info.platform = std::string(platform); -} - RLM_API void realm_app_config_set_platform_version(realm_app_config_t* config, const char* platform_version) noexcept { config->device_info.platform_version = std::string(platform_version); @@ -234,11 +229,6 @@ RLM_API void realm_app_config_set_sdk(realm_app_config_t* config, const char* sd config->device_info.sdk = std::string(sdk); } -RLM_API void realm_app_config_set_cpu_arch(realm_app_config_t* config, const char* cpu_arch) noexcept -{ - config->device_info.cpu_arch = std::string(cpu_arch); -} - RLM_API void realm_app_config_set_device_name(realm_app_config_t* config, const char* device_name) noexcept { config->device_info.device_name = std::string(device_name); @@ -260,6 +250,11 @@ RLM_API void realm_app_config_set_framework_version(realm_app_config_t* config, config->device_info.framework_version = std::string(framework_version); } +RLM_API void realm_app_config_set_bundle_id(realm_app_config_t* config, const char* bundle_id) noexcept +{ + config->device_info.bundle_id = std::string(bundle_id); +} + RLM_API const char* realm_app_credentials_serialize_as_json(realm_app_credentials_t* app_credentials) noexcept { return wrap_err([&] { diff --git a/src/realm/object-store/impl/realm_coordinator.cpp b/src/realm/object-store/impl/realm_coordinator.cpp index d00a3ee06a6..7b5f60d7ae0 100644 --- a/src/realm/object-store/impl/realm_coordinator.cpp +++ b/src/realm/object-store/impl/realm_coordinator.cpp @@ -644,24 +644,26 @@ void RealmCoordinator::unregister_realm(Realm* realm) } } -// Thread-safety analsys doesn't reasonably handle calling functions on different +// Thread-safety analysis doesn't reasonably handle calling functions on different // instances of this type void RealmCoordinator::clear_cache() NO_THREAD_SAFETY_ANALYSIS { - std::vector> realms_to_close; std::vector> coordinators; { std::lock_guard lock(s_coordinator_mutex); - for (auto& weak_coordinator : s_coordinators_per_path) { - auto coordinator = weak_coordinator.second.lock(); - if (!coordinator) { - continue; + if (auto coordinator = weak_coordinator.second.lock()) { + coordinators.push_back(coordinator); } - coordinators.push_back(coordinator); + } + s_coordinators_per_path.clear(); + } - coordinator->m_notifier = nullptr; + for (auto& coordinator : coordinators) { + coordinator->m_notifier = nullptr; + std::vector> realms_to_close; + { // Gather a list of all of the realms which will be removed util::CheckedLockGuard lock(coordinator->m_realm_mutex); for (auto& weak_realm_notifier : coordinator->m_weak_realm_notifiers) { @@ -671,14 +673,11 @@ void RealmCoordinator::clear_cache() NO_THREAD_SAFETY_ANALYSIS } } - s_coordinators_per_path.clear(); + // Close all of the previously cached Realms. This can't be done while + // locks are held as it may try to re-lock them. + for (auto& realm : realms_to_close) + realm->close(); } - coordinators.clear(); - - // Close all of the previously cached Realms. This can't be done while - // s_coordinator_mutex is held as it may try to re-lock it. - for (auto& realm : realms_to_close) - realm->close(); } void RealmCoordinator::clear_all_caches() diff --git a/src/realm/object-store/results.cpp b/src/realm/object-store/results.cpp index dd23ad7ced4..8d96b7ba1b2 100644 --- a/src/realm/object-store/results.cpp +++ b/src/realm/object-store/results.cpp @@ -116,15 +116,17 @@ bool Results::is_valid() const m_realm->verify_thread(); } - if (m_collection) - return m_collection->is_attached(); - // Here we cannot just use if (m_table) as it combines a check if the // reference contains a value and if that value is valid. // First we check if a table is referenced ... if (m_table.unchecked_ptr() != nullptr) return bool(m_table); // ... and then we check if it is valid + if (m_collection) + // Since m_table was not set, this is a collection of primitives + // and the results validity depend directly on the collection + return m_collection->is_attached(); + return true; } @@ -425,16 +427,21 @@ Mixed Results::get_any(size_t ndx) switch (m_mode) { case Mode::Empty: break; - case Mode::Table: - if (ndx < m_table->size()) - return m_table_iterator.get(*m_table, ndx); + case Mode::Table: { + // Validity of m_table is checked in validate_read() above, so we + // can skip all the checks here (which requires not using the + // Mixed(Obj()) constructor) + auto table = m_table.unchecked_ptr(); + if (ndx < table->size()) + return ObjLink(table->get_key(), m_table_iterator.get(*table, ndx).get_key()); break; + } case Mode::Collection: if (auto actual = actual_index(ndx); actual < m_collection->size()) return m_collection->get_any(actual); break; case Mode::Query: - REALM_UNREACHABLE(); + REALM_UNREACHABLE(); // should always be in TV mode case Mode::TableView: { if (ndx >= m_table_view.size()) break; @@ -937,7 +944,7 @@ Results Results::distinct(std::vector const& keypaths) const return distinct({std::move(column_keys)}); } -SectionedResults Results::sectioned_results(SectionedResults::SectionKeyFunc section_key_func) REQUIRES(m_mutex) +SectionedResults Results::sectioned_results(SectionedResults::SectionKeyFunc&& section_key_func) REQUIRES(m_mutex) { return SectionedResults(*this, std::move(section_key_func)); } @@ -945,7 +952,7 @@ SectionedResults Results::sectioned_results(SectionedResults::SectionKeyFunc sec SectionedResults Results::sectioned_results(SectionedResultsOperator op, util::Optional prop_name) REQUIRES(m_mutex) { - return SectionedResults(*this, op, prop_name); + return SectionedResults(*this, op, prop_name.value_or(StringData())); } Results Results::snapshot() const& @@ -1087,7 +1094,12 @@ Results Results::import_copy_into_realm(std::shared_ptr const& realm) case Mode::Table: return Results(realm, realm->import_copy_of(m_table)); case Mode::Collection: - return Results(realm, realm->import_copy_of(*m_collection), m_descriptor_ordering); + if (std::shared_ptr collection = realm->import_copy_of(*m_collection)) { + return Results(realm, collection, m_descriptor_ordering); + } + // If collection is gone, fallback to empty selection on table. + return Results(realm, TableView(realm->import_copy_of(m_table))); + break; case Mode::Query: return Results(realm, *realm->import_copy_of(m_query, PayloadPolicy::Copy), m_descriptor_ordering); case Mode::TableView: { diff --git a/src/realm/object-store/results.hpp b/src/realm/object-store/results.hpp index ca5315decb4..1f1ef51d620 100644 --- a/src/realm/object-store/results.hpp +++ b/src/realm/object-store/results.hpp @@ -63,7 +63,7 @@ class Results { Results& operator=(const Results&); // Get the Realm - std::shared_ptr get_realm() const + const std::shared_ptr& get_realm() const { return m_realm; } @@ -78,7 +78,7 @@ class Results { // Returned query will not be valid if the current mode is Empty Query get_query() const REQUIRES(!m_mutex); - // Get ordering for thr query associated with the result + // Get ordering for the query associated with the result const DescriptorOrdering& get_ordering() const; // Get the Collection this Results is derived from, if any @@ -289,13 +289,13 @@ class Results { * Creates a SectionedResults object by using a user defined sectioning algorithm to project the key for each * section. * - * @param section_key_func The callback to be itterated on each value in the underlying Results. + * @param section_key_func The callback to be iterated on each value in the underlying Results. * This callback must return a value which defines the section key * - * @return A SectionedResults object using a user defined sectioning algoritm. + * @return A SectionedResults object using a user defined sectioning algorithm. */ - SectionedResults - sectioned_results(util::UniqueFunction realm)> section_key_func); + SectionedResults sectioned_results( + util::UniqueFunction& realm)>&& section_key_func); enum class SectionedResultsOperator { FirstLetter // Section by the first letter of each string element. Note that col must be a string. }; diff --git a/src/realm/object-store/sectioned_results.cpp b/src/realm/object-store/sectioned_results.cpp index d5ffc9e1e53..fa24983a449 100644 --- a/src/realm/object-store/sectioned_results.cpp +++ b/src/realm/object-store/sectioned_results.cpp @@ -23,20 +23,20 @@ namespace realm { static SectionedResults::SectionKeyFunc builtin_comparison(Results& results, Results::SectionedResultsOperator op, - util::Optional prop_name) + StringData prop_name) { switch (op) { case Results::SectionedResultsOperator::FirstLetter: if (results.get_type() == PropertyType::Object) { - auto col_key = results.get_table()->get_column_key(*prop_name); - return [col_key](Mixed value, SharedRealm realm) { + auto col_key = results.get_table()->get_column_key(prop_name); + return [col_key](Mixed value, const SharedRealm& realm) { auto link = value.get_link(); auto v = realm->read_group().get_object(link).get(col_key); return v.size() > 0 ? v.prefix(1) : ""; }; } else { - return [](Mixed value, SharedRealm) { + return [](Mixed value, const SharedRealm&) { auto v = value.get_string(); return v.size() > 0 ? v.prefix(1) : ""; }; @@ -46,9 +46,44 @@ static SectionedResults::SectionKeyFunc builtin_comparison(Results& results, Res } } +namespace { +template +T& at(std::vector& vec, size_t index) +{ + if (index >= vec.size()) { + if (vec.capacity() <= index) + vec.reserve(index * 2 + 1); + vec.resize(index + 1); + } + return vec[index]; +} + +struct IndexSetAdder { + IndexSet* set; + IndexSetAdder& operator=(size_t value) + { + set->add(value); + return *this; + } + IndexSetAdder& operator++() + { + return *this; + } + IndexSetAdder& operator++(int) + { + return *this; + } + IndexSetAdder& operator*() + { + return *this; + } +}; +} // anonymous namespace + struct SectionedResultsNotificationHandler { public: - SectionedResultsNotificationHandler(SectionedResults& sectioned_results, SectionedResultsNotificatonCallback cb, + SectionedResultsNotificationHandler(SectionedResults& sectioned_results, + SectionedResultsNotificationCallback&& cb, util::Optional section_filter = util::none) : m_cb(std::move(cb)) , m_sectioned_results(sectioned_results) @@ -59,161 +94,182 @@ struct SectionedResultsNotificationHandler { void operator()(CollectionChangeSet const& c) { - auto convert_indices = [&](const IndexSet& indices, - const std::vector>& rows_to_index_path) { - std::map ret; - for (auto index : indices.as_indexes()) { - auto& index_path = rows_to_index_path[index]; - ret[index_path.first].add(index_path.second); - } - return ret; - }; - util::CheckedUniqueLock lock(m_sectioned_results.m_mutex); m_sectioned_results.calculate_sections_if_required(); + section_initial_changes(c); + m_prev_row_to_index_path = m_sectioned_results.m_row_to_index_path; - auto converted_insertions = convert_indices(c.insertions, m_sectioned_results.m_row_to_index_path); - auto converted_modifications = convert_indices(c.modifications, m_prev_row_to_index_path); - auto converted_modifications_new = - convert_indices(c.modifications_new, m_sectioned_results.m_row_to_index_path); - auto converted_deletions = convert_indices(c.deletions, m_prev_row_to_index_path); - - std::map modifications_to_keep, modifications_to_keep_new; - auto section_changes = calculate_sections_to_insert_and_delete(); - - for (auto [section_old, indexes_old] : converted_modifications) { - auto it = m_sectioned_results.m_prev_section_index_to_key.find(section_old); - REALM_ASSERT(it != m_sectioned_results.m_prev_section_index_to_key.end()); - auto section_identifier = m_sectioned_results.m_sections.find(it->second); - // This section still exists. - if (section_identifier != m_sectioned_results.m_sections.end()) { - auto old_indexes = indexes_old.as_indexes(); - auto new_indexes = converted_modifications_new[section_identifier->second.index].as_indexes(); - std::vector out_indexes; - std::set_intersection(old_indexes.begin(), old_indexes.end(), new_indexes.begin(), new_indexes.end(), - std::back_inserter(out_indexes)); - auto& old_modifications = converted_modifications[section_old]; - // These are the indexes which are still in the - // same position as they were in the old collection. - for (auto& i : out_indexes) { - modifications_to_keep[section_old].add(i); - modifications_to_keep_new[section_identifier->second.index].add(i); - // Anything remaining in converted_modifications will be added to deletions. - old_modifications.remove(i); - // Anything remaining in converted_modifications_new will be added to insertions. - converted_modifications_new[section_identifier->second.index].remove(i); - } - if (!old_modifications.empty()) - converted_deletions[section_old].add(converted_modifications[section_old]); - } - } - - for (auto [section, indexes] : converted_modifications_new) { - if (!indexes.empty()) - converted_insertions[section].add(indexes); - } - - // Cocoa only requires the index of the deleted sections to remove all deleted rows. - // There is no need to pass back each individual deletion IndexPath. - for (auto section : section_changes.second.as_indexes()) { - converted_deletions.erase(section); - } - - converted_modifications = modifications_to_keep; - converted_modifications_new = modifications_to_keep_new; - - if (m_section_filter) { - std::map filtered_insertions, filtered_modifications, filtered_deletions; - - auto current_section = m_sectioned_results.m_sections.find(*m_section_filter); - - auto previous_index_of_section_key = - m_sectioned_results.m_previous_key_to_index_lookup.find(*m_section_filter); - auto current_key_exists = current_section != m_sectioned_results.m_sections.end(); - auto previous_key_exists = - previous_index_of_section_key != m_sectioned_results.m_previous_key_to_index_lookup.end(); + // Add source to target[i], expanding target if needed + auto add = [](auto& source, auto& target, size_t i) { + if (source.empty()) + return; + if (i >= target.size()) + target.resize(i + 1); + target[i].add(source); + }; - bool has_insertions = - current_key_exists && converted_insertions.count(current_section->second.index) != 0; - if (has_insertions) { - filtered_insertions[current_section->second.index] = - converted_insertions[current_section->second.index]; - } - bool has_modifications = - previous_key_exists && converted_modifications.count(previous_index_of_section_key->second) != 0; - if (has_modifications) { - filtered_modifications[previous_index_of_section_key->second] = - converted_modifications[previous_index_of_section_key->second]; + // Modifications to rows in the unsectioned results may result in rows + // moving between sections, which need to be reported as a delete+insert + // instead. We don't have enough information at this point to produce a + // correct minimal diff, so we err on the side of producing deletes and + // inserts for everything that isn't marked as modified in both the old + // and new versions. + + // Looping backwards here ensures that we have to resize the output + // arrays at most once, as we encounter the back element that needs to + // be present first. + for (size_t i = m_change.modifications.size(); i > 0; --i) { + auto& indexes_old = m_change.modifications[i - 1]; + auto key = m_sectioned_results.m_previous_index_to_key[i - 1]; + auto it = m_sectioned_results.m_current_key_to_index.find(key); + if (it == m_sectioned_results.m_current_key_to_index.end()) { + // Section was removed due to all of the rows being moved to + // other sections. No need to report the individual rows as deleted. + indexes_old.clear(); + continue; } + size_t old_section = i - 1; + size_t new_section = it->second; - bool has_deletions = - previous_key_exists && converted_deletions.count(previous_index_of_section_key->second) != 0; - if (has_deletions) { - filtered_deletions[previous_index_of_section_key->second] = - converted_deletions[previous_index_of_section_key->second]; + // Extract the intersection of the two sets + IndexSet still_present; + if (new_section < m_new_modifications.size()) { + auto old_indexes = indexes_old.as_indexes(); + auto new_indexes = m_new_modifications[new_section].as_indexes(); + std::set_intersection(old_indexes.begin(), old_indexes.end(), new_indexes.begin(), new_indexes.end(), + IndexSetAdder{&still_present}); + m_new_modifications[new_section].remove(still_present); + m_change.modifications[old_section].remove(still_present); } - IndexSet filtered_sections_to_insert, filtered_sections_to_delete; + // Anything in old modifications but not new gets added to deletions + add(m_change.modifications[old_section], m_change.deletions, old_section); - if (current_key_exists) { - if (section_changes.first.contains(current_section->second.index)) - filtered_sections_to_insert.add(current_section->second.index); - } + // Any positions marked as modified in both the old and new collections + // stay marked as modified. + m_change.modifications[old_section] = std::move(still_present); + } - if (previous_key_exists) { - if (section_changes.second.contains(previous_index_of_section_key->second)) - filtered_sections_to_delete.add(previous_index_of_section_key->second); - } + // Anything remaining in new_modifications is now an insertion. This is + // once again a reverse loop to ensure we only resize once. + for (size_t i = m_new_modifications.size(); i > 0; --i) + add(m_new_modifications[i - 1], m_change.insertions, i - 1); - bool should_notify = has_insertions || has_modifications || has_deletions || - !filtered_sections_to_insert.empty() || !filtered_sections_to_delete.empty(); + // The modifications array may now be longer than needed. This isn't + // strictly needed but makes writing tests awkward. + while (!m_change.modifications.empty() && m_change.modifications.back().empty()) + m_change.modifications.pop_back(); - if (should_notify || m_section_filter_should_deliver_initial_notification) { - m_cb(SectionedResultsChangeSet{filtered_insertions, filtered_modifications, filtered_deletions, - filtered_sections_to_insert, filtered_sections_to_delete}); + // If we have a section filter we might have been called when there were + // no changes to the section we care about, in which case we should skip + // calling the callback unless it's the initial notification + if (m_section_filter) { + bool no_changes = m_change.insertions.empty() && m_change.deletions.empty() && + m_change.modifications.empty() && m_change.sections_to_insert.empty() && + m_change.sections_to_delete.empty(); + if (m_section_filter_should_deliver_initial_notification) m_section_filter_should_deliver_initial_notification = false; - } - } - else { - m_cb(SectionedResultsChangeSet{converted_insertions, converted_modifications, converted_deletions, - section_changes.first, section_changes.second}); + else if (no_changes) + return; } - REALM_ASSERT(m_sectioned_results.m_results.is_valid()); - m_prev_row_to_index_path = m_sectioned_results.m_row_to_index_path; + m_cb(m_change); } - std::pair calculate_sections_to_insert_and_delete() REQUIRES(m_sectioned_results.m_mutex) - { - IndexSet sections_to_insert, sections_to_remove; +private: + SectionedResultsNotificationCallback m_cb; + SectionedResults& m_sectioned_results; + std::vector> m_prev_row_to_index_path; + SectionedResultsChangeSet m_change; + std::vector m_new_modifications; + // When set change notifications will be filtered to only deliver + // change indices referring to the supplied section key. + util::Optional m_section_filter; + bool m_section_filter_should_deliver_initial_notification = true; - for (auto& [key, section] : m_sectioned_results.m_sections) { - if (m_sectioned_results.m_previous_key_to_index_lookup.find(key) == - m_sectioned_results.m_previous_key_to_index_lookup.end()) { - sections_to_insert.add(section.index); + // Group the changes in the changeset by the section + void section_initial_changes(CollectionChangeSet const& c) REQUIRES(m_sectioned_results.m_mutex) + { + m_change.insertions.clear(); + m_change.modifications.clear(); + m_change.deletions.clear(); + m_change.sections_to_insert.clear(); + m_change.sections_to_delete.clear(); + m_new_modifications.clear(); + + // If we have a section filter, just check if it was added or removed + // and for changes within that specific section + if (m_section_filter) { + auto get_index = [&](auto& map) -> size_t { + if (auto it = map.find(*m_section_filter); it != map.end()) + return it->second; + return npos; + }; + + size_t old_index = get_index(m_sectioned_results.m_previous_key_to_index); + size_t new_index = get_index(m_sectioned_results.m_current_key_to_index); + if (old_index == npos && new_index == npos) + return; + if (old_index == npos && new_index != npos) + m_change.sections_to_insert.add(new_index); + else if (old_index != npos && new_index == npos) + m_change.sections_to_delete.add(old_index); + + auto populate = [](auto& src, auto& mapping, size_t filter, auto& dst) { + for (auto index : src.as_indexes()) { + auto [section, row] = mapping[index]; + if (section == filter) + at(dst, section).add(row); + } + }; + populate(c.insertions, m_sectioned_results.m_row_to_index_path, new_index, m_change.insertions); + populate(c.modifications, m_prev_row_to_index_path, old_index, m_change.modifications); + populate(c.modifications_new, m_sectioned_results.m_row_to_index_path, new_index, m_new_modifications); + + // Only report deletions inside the section if it still exists + if (new_index != npos) { + populate(c.deletions, m_prev_row_to_index_path, old_index, m_change.deletions); } + return; } - for (auto& [key, index] : m_sectioned_results.m_previous_key_to_index_lookup) { - if (m_sectioned_results.m_sections.find(key) == m_sectioned_results.m_sections.end()) { - sections_to_remove.add(index); + // Symmetrical diff of new and old sections + for (auto& section : m_sectioned_results.m_sections) { + if (!m_sectioned_results.m_previous_key_to_index.count(section.key)) { + m_change.sections_to_insert.add(section.index); + } + } + for (auto& [key, index] : m_sectioned_results.m_previous_key_to_index) { + if (!m_sectioned_results.m_current_key_to_index.count(key)) { + m_change.sections_to_delete.add(index); } } - return {sections_to_insert, sections_to_remove}; + // Group the change indexes by section + for (auto index : c.insertions.as_indexes()) { + auto [section, row] = m_sectioned_results.m_row_to_index_path[index]; + at(m_change.insertions, section).add(row); + } + for (auto index : c.modifications.as_indexes()) { + auto [section, row] = m_prev_row_to_index_path[index]; + at(m_change.modifications, section).add(row); + } + for (auto index : c.modifications_new.as_indexes()) { + auto [section, row] = m_sectioned_results.m_row_to_index_path[index]; + at(m_new_modifications, section).add(row); + } + for (auto index : c.deletions.as_indexes()) { + auto [section, row] = m_prev_row_to_index_path[index]; + // If the section has been deleted that's the only information we + // need and we can skip reporting the rows inside the section + if (!m_change.sections_to_delete.contains(section)) + at(m_change.deletions, section).add(row); + } } - -private: - SectionedResultsNotificatonCallback m_cb; - SectionedResults& m_sectioned_results; - std::vector> m_prev_row_to_index_path; - // When set change notifications will be filtered to only deliver - // change indices refering to the supplied section key. - util::Optional m_section_filter; - bool m_section_filter_should_deliver_initial_notification = true; }; +namespace { template void create_buffered_key(Mixed& key, std::unique_ptr& buffer, StringType value) { @@ -227,25 +283,38 @@ void create_buffered_key(Mixed& key, std::unique_ptr& buffer, StringType } } -ResultsSection::ResultsSection() - : m_parent(nullptr) - , m_key(Mixed()) +template +void create_buffered_key(Mixed& key, std::list& buffer, StringType value) +{ + if (value.size() == 0) { + key = StringType("", 0); + } + else { + key = buffer.emplace_back(value.data(), value.size()); + } +} + +template +void create_buffered_key(Mixed& key, Buffer& buffer) { + if (key.is_null()) + return; + if (key.is_type(type_String)) + create_buffered_key(key, buffer, key.get_string()); + else if (key.is_type(type_Binary)) + create_buffered_key(key, buffer, key.get_binary()); } +} // anonymous namespace + ResultsSection::ResultsSection(SectionedResults* parent, Mixed key) : m_parent(parent) + , m_key(key) { // Give the ResultsSection its own copy of the string data // to counter the event that the m_previous_str_buffers, m_current_str_buffers // no longer hold a reference to the data. - if (key.is_type(type_String, type_Binary)) { - key.is_type(type_String) ? create_buffered_key(m_key, m_key_buffer, key.get_string()) - : create_buffered_key(m_key, m_key_buffer, key.get_binary()); - } - else { - m_key = key; - } + create_buffered_key(m_key, m_key_buffer); } bool ResultsSection::is_valid() const @@ -261,11 +330,10 @@ Section* ResultsSection::get_if_valid() const // See if we need to recalculate the sections before // searching for the key. m_parent->calculate_sections_if_required(); - auto it = m_parent->m_sections.find(m_key); - if (it == m_parent->m_sections.end()) + auto it = m_parent->m_current_key_to_index.find(m_key); + if (it == m_parent->m_current_key_to_index.end()) return nullptr; - else - return &it->second; + return &m_parent->m_sections[it->second]; } Section* ResultsSection::get_section() const @@ -281,7 +349,7 @@ Mixed ResultsSection::operator[](size_t idx) const Section* section = get_section(); auto size = section->indices.size(); if (idx >= size) - throw OutOfBounds(util::format("ResultsSection[]"), idx, size); + throw OutOfBounds("ResultsSection[]", idx, size); return m_parent->m_results.get_any(section->indices[idx]); } @@ -302,7 +370,7 @@ size_t ResultsSection::size() return get_section()->indices.size(); } -NotificationToken ResultsSection::add_notification_callback(SectionedResultsNotificatonCallback callback, +NotificationToken ResultsSection::add_notification_callback(SectionedResultsNotificationCallback&& callback, std::optional key_path_array) & { return m_parent->add_notification_callback_for_section(m_key, std::move(callback), key_path_array); @@ -314,8 +382,7 @@ SectionedResults::SectionedResults(Results results, SectionKeyFunc section_key_f { } -SectionedResults::SectionedResults(Results results, Results::SectionedResultsOperator op, - util::Optional prop_name) +SectionedResults::SectionedResults(Results results, Results::SectionedResultsOperator op, StringData prop_name) : m_results(results) , m_callback(builtin_comparison(results, op, prop_name)) { @@ -325,7 +392,7 @@ void SectionedResults::calculate_sections_if_required() { if (m_results.m_update_policy == Results::UpdatePolicy::Never) return; - if ((m_results.is_frozen() || !m_results.has_changed()) && m_has_performed_initial_evalutation) + if ((m_results.is_frozen() || !m_results.has_changed()) && m_has_performed_initial_evaluation) return; { @@ -336,17 +403,6 @@ void SectionedResults::calculate_sections_if_required() calculate_sections(); } -template -void create_buffered_key(Mixed& key, std::list& buffer, StringType value) -{ - if (value.size() == 0) { - key = StringType("", 0); - } - else { - key = buffer.emplace_back(value.data(), value.size()); - } -} - // This method will run in the following scenarios: // - SectionedResults is performing its initial evaluation. // - The underlying Table in the Results collection has changed @@ -354,12 +410,11 @@ void SectionedResults::calculate_sections() { m_previous_str_buffers.clear(); m_previous_str_buffers.swap(m_current_str_buffers); - m_previous_key_to_index_lookup.clear(); - m_prev_section_index_to_key.clear(); - m_current_section_index_to_key_lookup.clear(); - for (auto& [key, section] : m_sections) { - m_previous_key_to_index_lookup[key] = section.index; - m_prev_section_index_to_key[section.index] = section.key; + m_previous_key_to_index.clear(); + m_previous_key_to_index.swap(m_current_key_to_index); + m_previous_index_to_key.clear(); + for (auto& section : m_sections) { + m_previous_index_to_key.push_back(section.key); } m_sections.clear(); @@ -375,37 +430,29 @@ void SectionedResults::calculate_sections() throw InvalidArgument("Links are not supported as section keys."); } - auto it = m_sections.find(key); - if (it == m_sections.end()) { - if (!key.is_null() && key.is_type(type_String, type_Binary)) { - (key.get_type() == type_String) ? create_buffered_key(key, m_current_str_buffers, key.get_string()) - : create_buffered_key(key, m_current_str_buffers, key.get_binary()); - } - + auto it = m_current_key_to_index.find(key); + if (it == m_current_key_to_index.end()) { + create_buffered_key(key, m_current_str_buffers); auto idx = m_sections.size(); - Section section; - section.key = key; - section.index = idx; - section.indices.push_back(i); - m_sections[key] = section; - m_row_to_index_path[i] = {idx, section.indices.size() - 1}; - m_current_section_index_to_key_lookup[idx] = key; + m_sections.push_back(Section{idx, key, {i}}); + m_current_key_to_index[key] = idx; + m_row_to_index_path[i] = {idx, 0}; } else { - auto& section = it->second; + auto& section = m_sections[it->second]; section.indices.push_back(i); m_row_to_index_path[i] = {section.index, section.indices.size() - 1}; } } - if (!m_has_performed_initial_evalutation) { - REALM_ASSERT_EX(m_previous_key_to_index_lookup.size() == 0, m_previous_key_to_index_lookup.size()); - REALM_ASSERT_EX(m_prev_section_index_to_key.size() == 0, m_prev_section_index_to_key.size()); - for (auto& [key, section] : m_sections) { - m_previous_key_to_index_lookup[key] = section.index; - m_prev_section_index_to_key[section.index] = section.key; + if (!m_has_performed_initial_evaluation) { + REALM_ASSERT_EX(m_previous_key_to_index.size() == 0, m_previous_key_to_index.size()); + REALM_ASSERT_EX(m_previous_index_to_key.size() == 0, m_previous_index_to_key.size()); + m_previous_key_to_index = m_current_key_to_index; + for (auto& section : m_sections) { + m_previous_index_to_key.push_back(section.key); } } - m_has_performed_initial_evalutation = true; + m_has_performed_initial_evaluation = true; } size_t SectionedResults::size() @@ -422,9 +469,7 @@ ResultsSection SectionedResults::operator[](size_t idx) if (idx >= s) throw OutOfBounds("SectionedResults[]", idx, s); util::CheckedUniqueLock lock(m_mutex); - auto it = m_current_section_index_to_key_lookup.find(idx); - REALM_ASSERT(it != m_current_section_index_to_key_lookup.end()); - auto& section = m_sections[it->second]; + auto& section = m_sections[idx]; return ResultsSection(this, section.key); } @@ -433,14 +478,13 @@ ResultsSection SectionedResults::operator[](Mixed key) util::CheckedUniqueLock lock(m_mutex); check_valid(); calculate_sections_if_required(); - if (auto it = m_sections.find(key); it != m_sections.end()) { - return ResultsSection(this, it->second.key); + if (!m_current_key_to_index.count(key)) { + throw InvalidArgument(util::format("Section key %1 not found.", key)); } - - throw InvalidArgument(util::format("Section key %1 not found.", key)); + return ResultsSection(this, key); } -NotificationToken SectionedResults::add_notification_callback(SectionedResultsNotificatonCallback callback, +NotificationToken SectionedResults::add_notification_callback(SectionedResultsNotificationCallback&& callback, std::optional key_path_array) & { return m_results.add_notification_callback(SectionedResultsNotificationHandler(*this, std::move(callback)), @@ -448,40 +492,29 @@ NotificationToken SectionedResults::add_notification_callback(SectionedResultsNo } NotificationToken SectionedResults::add_notification_callback_for_section( - Mixed section_key, SectionedResultsNotificatonCallback callback, std::optional key_path_array) + Mixed section_key, SectionedResultsNotificationCallback&& callback, std::optional key_path_array) { return m_results.add_notification_callback( SectionedResultsNotificationHandler(*this, std::move(callback), section_key), std::move(key_path_array)); } -SectionedResults SectionedResults::copy(Results&& results) +// Thread-safety analysis doesn't work when creating a different instance of the +// same type +SectionedResults SectionedResults::copy(Results&& results) NO_THREAD_SAFETY_ANALYSIS { util::CheckedUniqueLock lock(m_mutex); calculate_sections_if_required(); // m_callback will never be run when using frozen results so we do // not need to set it. - std::list str_buffers; - std::map sections; - std::map current_section_index_to_key_lookup; - - for (auto& [key, section] : m_sections) { - Mixed new_key; - if (key.is_type(type_String, type_Binary)) { - key.is_type(type_String) ? create_buffered_key(new_key, str_buffers, key.get_string()) - : create_buffered_key(new_key, str_buffers, key.get_binary()); - } - else { - new_key = key; - } - Section new_section; - new_section.index = section.index; - new_section.key = new_key; - new_section.indices = section.indices; - sections[Mixed(new_key)] = new_section; - current_section_index_to_key_lookup[section.index] = new_key; + SectionedResults ret; + ret.m_has_performed_initial_evaluation = true; + ret.m_results = std::move(results); + ret.m_sections = m_sections; + for (auto& section : ret.m_sections) { + create_buffered_key(section.key, ret.m_current_str_buffers); + ret.m_current_key_to_index[section.key] = section.index; } - return SectionedResults(std::move(results), std::move(sections), std::move(current_section_index_to_key_lookup), - std::move(str_buffers)); + return ret; } SectionedResults SectionedResults::snapshot() @@ -513,11 +546,11 @@ void SectionedResults::reset_section_callback(SectionKeyFunc section_callback) { util::CheckedUniqueLock lock(m_mutex); m_callback = std::move(section_callback); - m_has_performed_initial_evalutation = false; + m_has_performed_initial_evaluation = false; m_sections.clear(); - m_current_section_index_to_key_lookup.clear(); + m_previous_index_to_key.clear(); + m_current_key_to_index.clear(); + m_previous_key_to_index.clear(); m_row_to_index_path.clear(); - m_previous_key_to_index_lookup.clear(); - m_prev_section_index_to_key.clear(); } } // namespace realm diff --git a/src/realm/object-store/sectioned_results.hpp b/src/realm/object-store/sectioned_results.hpp index a9db2027b92..bbd4c33cf05 100644 --- a/src/realm/object-store/sectioned_results.hpp +++ b/src/realm/object-store/sectioned_results.hpp @@ -22,6 +22,7 @@ #include #include +#include namespace realm { class Mixed; @@ -31,13 +32,12 @@ struct SectionedResultsChangeSet; /// For internal use only. Used to track the indices for a given section. struct Section { - Section() = default; size_t index = 0; Mixed key; std::vector indices; }; -using SectionedResultsNotificatonCallback = util::UniqueFunction; +using SectionedResultsNotificationCallback = util::UniqueFunction; /** * An instance of ResultsSection gives access to elements in the underlying collection that belong to a given section @@ -50,7 +50,7 @@ using SectionedResultsNotificatonCallback = util::UniqueFunction key_path_array = std::nullopt) &; bool is_valid() const; @@ -89,7 +89,7 @@ class ResultsSection { friend class SectionedResults; ResultsSection(SectionedResults* parent, Mixed key); - SectionedResults* m_parent; + SectionedResults* m_parent = nullptr; Mixed m_key; std::unique_ptr m_key_buffer; Section* get_if_valid() const; @@ -100,18 +100,18 @@ class ResultsSection { * An instance of `SectionedResults` allows access to elements from underlying `Results` collection * where elements are arranged into sections defined by a key either from a user defined sectioning algorithm * or a predefined built-in sectioning algorithm. Elements are then accessed through a `ResultsSection` which can be - * retreived through the subscript operator on `SectionedResults`. + * retrieved through the subscript operator on `SectionedResults`. */ class SectionedResults { public: SectionedResults() = default; - using SectionKeyFunc = util::UniqueFunction realm)>; + using SectionKeyFunc = util::UniqueFunction& realm)>; /** * Returns a `ResultsSection` which will be bound to a section key present at the given index in * `SectionedResults`. * - * NOTE: A `ResultsSection` is lazily retreived, meaning that the index it was retreived from + * NOTE: A `ResultsSection` is lazily retrieved, meaning that the index it was retreived from * is not guaranteed to be the index of this `ResultsSection` at the time of access. * For example if this `ResultsSection` is at index 1 and the `ResultsSection` * below this one is deleted, this `ResultsSection` will now be at index 0 @@ -133,13 +133,13 @@ class SectionedResults { * * @param callback The function to execute when a insertions, modification or deletion in this `SectionedResults` * was detected. - * @param key_path_array A filter that can be applied to make sure the `SectionedResultsNotificatonCallback` is + * @param key_path_array A filter that can be applied to make sure the `SectionedResultsNotificationCallback` is * only executed when the property in the filter is changed but not otherwise. * * @return A `NotificationToken` that is used to identify this callback. This token can be used to remove the * callback via `remove_callback`. */ - NotificationToken add_notification_callback(SectionedResultsNotificatonCallback callback, + NotificationToken add_notification_callback(SectionedResultsNotificationCallback&& callback, std::optional key_path_array = std::nullopt) &; /// Return a new instance of SectionedResults that uses a snapshot of the underlying `Results`. @@ -159,43 +159,35 @@ class SectionedResults { private: friend class Results; + friend class realm::ResultsSection; + /// SectionedResults should not be created directly and should only be instantiated from `Results`. SectionedResults(Results results, SectionKeyFunc section_key_func); - SectionedResults(Results results, Results::SectionedResultsOperator op, util::Optional prop_name); - - /// Used for creating a frozen or snapshot of SectionedResults. - SectionedResults(Results&& results, std::map&& sections, - std::map&& current_section_index_to_key_lookup, - std::list&& current_str_buffers) - : m_has_performed_initial_evalutation(true) - , m_results(std::move(results)) - , m_sections(std::move(sections)) - , m_current_section_index_to_key_lookup(std::move(current_section_index_to_key_lookup)) - , m_current_str_buffers(std::move(current_str_buffers)) - { - } + SectionedResults(Results results, Results::SectionedResultsOperator op, StringData prop_name); friend struct SectionedResultsNotificationHandler; util::CheckedOptionalMutex m_mutex; SectionedResults copy(Results&&) REQUIRES(!m_mutex); void calculate_sections_if_required() REQUIRES(m_mutex); void calculate_sections() REQUIRES(m_mutex); - bool m_has_performed_initial_evalutation = false; + bool m_has_performed_initial_evaluation = false; NotificationToken - add_notification_callback_for_section(Mixed section_key, SectionedResultsNotificatonCallback callback, + add_notification_callback_for_section(Mixed section_key, SectionedResultsNotificationCallback&& callback, std::optional key_path_array = std::nullopt); - friend class realm::ResultsSection; Results m_results; SectionKeyFunc m_callback; - std::map m_sections GUARDED_BY(m_mutex); + std::vector
m_sections GUARDED_BY(m_mutex); + // Returns the key of the current section from its index. - std::map m_current_section_index_to_key_lookup GUARDED_BY(m_mutex); + // Returns the key of the previous section from its index. + std::vector m_previous_index_to_key; + // Stores the Key, Section Index of the previous section // so we can efficiently calculate the collection change set. - std::map m_previous_key_to_index_lookup; - // Returns the key of the previous section from its index. - std::map m_prev_section_index_to_key; + std::unordered_map m_current_key_to_index; + std::unordered_map m_previous_key_to_index; + // By passing the index of the object from the underlying `Results`, // this will give a pair with the section index of the object, and the position of the object in that section. // This is used for parsing the indices in CollectionChangeSet to section indices. @@ -211,11 +203,11 @@ class SectionedResults { struct SectionedResultsChangeSet { /// Sections and indices in the _new_ collection which are new insertions - std::map insertions; + std::vector insertions; /// Sections and indices of objects in the _old_ collection which were modified - std::map modifications; + std::vector modifications; /// Sections and indices which were removed from the _old_ collection - std::map deletions; + std::vector deletions; /// Indexes of sections which are newly inserted. IndexSet sections_to_insert; /// Indexes of sections which are deleted from the _old_ collection. diff --git a/src/realm/object-store/shared_realm.cpp b/src/realm/object-store/shared_realm.cpp index c348161863e..1d6974cbb6c 100644 --- a/src/realm/object-store/shared_realm.cpp +++ b/src/realm/object-store/shared_realm.cpp @@ -789,6 +789,12 @@ void Realm::run_writes() // writes as we can't add commits while in that state return; } + if (is_in_transaction()) { + // This is scheduled asynchronously after acquiring the write lock, so + // in that time a synchronous transaction may have been started. If so, + // we'll be re-invoked when that transaction ends. + return; + } CountGuard running_writes(m_is_running_async_writes); int run_limit = 20; // max number of commits without full sync to disk diff --git a/src/realm/object-store/sync/app.cpp b/src/realm/object-store/sync/app.cpp index cc54746524c..6a6b2dd55ea 100644 --- a/src/realm/object-store/sync/app.cpp +++ b/src/realm/object-store/sync/app.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -187,6 +188,29 @@ std::mutex s_apps_mutex; namespace realm { namespace app { +App::Config::DeviceInfo::DeviceInfo() + : platform(util::get_library_platform()) + , cpu_arch(util::get_library_cpu_arch()) + , core_version(REALM_VERSION_STRING) +{ +} + +App::Config::DeviceInfo::DeviceInfo(std::string a_platform_version, std::string an_sdk_version, std::string an_sdk, + std::string a_device_name, std::string a_device_version, + std::string a_framework_name, std::string a_framework_version, + std::string a_bundle_id) + : DeviceInfo() +{ + platform_version = a_platform_version; + sdk_version = an_sdk_version; + sdk = an_sdk; + device_name = a_device_name; + device_version = a_device_version; + framework_name = a_framework_name; + framework_version = a_framework_version; + bundle_id = a_bundle_id; +} + SharedApp App::get_shared_app(const Config& config, const SyncClientConfig& sync_client_config) { std::lock_guard lock(s_apps_mutex); @@ -250,21 +274,18 @@ App::App(const Config& config) } #endif REALM_ASSERT(m_config.transport); - - if (m_config.device_info.platform.empty()) { - throw InvalidArgument("You must specify the Platform in App::Config::device"); - } + REALM_ASSERT(!m_config.device_info.platform.empty()); if (m_config.device_info.platform_version.empty()) { - throw InvalidArgument("You must specify the Platform Version in App::Config::device"); + throw InvalidArgument("You must specify the Platform Version in App::Config::device_info"); } if (m_config.device_info.sdk.empty()) { - throw InvalidArgument("You must specify the SDK Name in App::Config::device"); + throw InvalidArgument("You must specify the SDK Name in App::Config::device_info"); } if (m_config.device_info.sdk_version.empty()) { - throw InvalidArgument("You must specify the SDK Version in App::Config::device"); + throw InvalidArgument("You must specify the SDK Version in App::Config::device_info"); } // change the scheme in the base url to ws from http to satisfy the sync client @@ -280,9 +301,15 @@ void App::configure(const SyncClientConfig& sync_client_config) auto sync_route = make_sync_route(m_app_route); m_sync_manager->configure(shared_from_this(), sync_route, sync_client_config); if (auto metadata = m_sync_manager->app_metadata()) { - // If there is app metadata stored, then update the hostname/syncroute using that info + // If there is app metadata stored, then set up the initial hostname/syncroute + // using that info - it will be updated upon first request to the server update_hostname(metadata); } + { + std::lock_guard lock(*m_route_mutex); + // Always update the location after the app is configured/re-configured + m_location_updated = false; + } } bool App::init_logger() @@ -316,7 +343,7 @@ void App::log_error(const char* message, Params&&... params) std::string App::make_sync_route(const std::string& http_app_route) { - // change the scheme in the base url to ws from http to satisfy the sync client + // change the scheme in the base url from http to ws to satisfy the sync client URL auto sync_route = http_app_route + sync_path; size_t uri_scheme_start = sync_route.find("http"); if (uri_scheme_start == 0) @@ -592,7 +619,7 @@ void App::attach_auth_options(BsonDocument& body) log_debug("App: version info: platform: %1 version: %2 - sdk: %3 - sdk version: %4 - core version: %5", m_config.device_info.platform, m_config.device_info.platform_version, m_config.device_info.sdk, - m_config.device_info.sdk_version, REALM_VERSION_STRING); + m_config.device_info.sdk_version, m_config.device_info.core_version); options["appId"] = m_config.app_id; options["platform"] = m_config.device_info.platform; options["platformVersion"] = m_config.device_info.platform_version; @@ -603,7 +630,8 @@ void App::attach_auth_options(BsonDocument& body) options["deviceVersion"] = m_config.device_info.device_version; options["frameworkName"] = m_config.device_info.framework_name; options["frameworkVersion"] = m_config.device_info.framework_version; - options["coreVersion"] = REALM_VERSION_STRING; + options["coreVersion"] = m_config.device_info.core_version; + options["bundleId"] = m_config.device_info.bundle_id; body["options"] = BsonDocument({{"device", options}}); } @@ -637,35 +665,45 @@ void App::log_in_with_credentials( BsonDocument body = credentials.serialize_as_bson(); attach_auth_options(body); - do_request({HttpMethod::post, route, m_request_timeout_ms, - get_request_headers(linking_user, RequestTokenType::AccessToken), Bson(body).to_string()}, - [completion = std::move(completion), credentials, linking_user, - self = shared_from_this()](const Response& response) mutable { - if (auto error = AppUtils::check_for_errors(response)) { - self->log_error("App: log_in_with_credentials failed: %1 message: %2", - response.http_status_code, error->what()); - return completion(nullptr, std::move(error)); - } + // To ensure the location metadata is always kept up to date, requery the location info before + // logging in the user. Since some SDK network transports (e.g. for JS) automatically handle + // redirects, the location is not updated when a redirect response from the server is received. + // This is especially necessary when the deployment model is changed, where the redirect response + // provides information about the new location of the server and a location info update is + // triggered. If the App never receives a redirect response from the server (because it was + // automatically handled) after the deployment model was changed and the user was logged out, + // the HTTP and websocket URL values will never be updated with the new server location. + do_request( + {HttpMethod::post, route, m_request_timeout_ms, + get_request_headers(linking_user, RequestTokenType::AccessToken), Bson(body).to_string()}, + [completion = std::move(completion), credentials, linking_user, + self = shared_from_this()](const Response& response) mutable { + if (auto error = AppUtils::check_for_errors(response)) { + self->log_error("App: log_in_with_credentials failed: %1 message: %2", response.http_status_code, + error->what()); + return completion(nullptr, std::move(error)); + } - std::shared_ptr sync_user = linking_user; - try { - auto json = parse(response.body); - if (linking_user) { - linking_user->update_access_token(get(json, "access_token")); - } - else { - sync_user = self->m_sync_manager->get_user( - get(json, "user_id"), get(json, "refresh_token"), - get(json, "access_token"), credentials.provider_as_string(), - get(json, "device_id")); - } - } - catch (const AppError& e) { - return completion(nullptr, e); - } + std::shared_ptr sync_user = linking_user; + try { + auto json = parse(response.body); + if (linking_user) { + linking_user->update_access_token(get(json, "access_token")); + } + else { + sync_user = self->m_sync_manager->get_user( + get(json, "user_id"), get(json, "refresh_token"), + get(json, "access_token"), credentials.provider_as_string(), + get(json, "device_id")); + } + } + catch (const AppError& e) { + return completion(nullptr, e); + } - self->get_profile(sync_user, std::move(completion)); - }); + self->get_profile(sync_user, std::move(completion)); + }, + false); } void App::log_in_with_credentials( @@ -843,20 +881,25 @@ void App::init_app_metadata(UniqueFunction&)>&& co { std::string route; - if (!new_hostname && m_location_updated) { + { + std::unique_lock lock(*m_route_mutex); // Skip if the app_metadata/location data has already been initialized and a new hostname is not provided - return completion(util::none); // early return - } - else { - std::lock_guard lock(*m_route_mutex); - route = util::format("%1/location", new_hostname ? get_app_route(new_hostname) : get_app_route()); + if (!new_hostname && m_location_updated) { + // Release the lock before calling the completion function + lock.unlock(); + return completion(util::none); // early return + } + else { + route = util::format("%1/location", new_hostname ? get_app_route(new_hostname) : get_app_route()); + } } - Request req; req.method = HttpMethod::get; req.url = route; req.timeout_ms = m_request_timeout_ms; + log_debug("App: request location: %1", route); + m_config.transport->send_request_to_server(req, [self = shared_from_this(), completion = std::move(completion)](const Response& response) { // If the response contains an error, then pass it up @@ -880,7 +923,10 @@ void App::init_app_metadata(UniqueFunction&)>&& co // No metadata in use, update the hostname and sync route directly self->update_hostname(hostname, ws_hostname); } - self->m_location_updated = true; + { + std::lock_guard lock(*self->m_route_mutex); + self->m_location_updated = true; + } } catch (const AppError&) { // Pass the response back to completion @@ -928,23 +974,34 @@ void App::update_metadata_and_resend(Request&& request, UniqueFunction&& completion) +void App::do_request(Request&& request, UniqueFunction&& completion, bool update_location) { - request.timeout_ms = default_timeout_ms; + // Make sure the timeout value is set to the configured request timeout value + request.timeout_ms = m_request_timeout_ms; - // Refresh the location metadata every time an app is created to ensure the http and - // websocket URL information is up to date. - if (m_location_updated) { - m_config.transport->send_request_to_server( - std::move(request), [self = shared_from_this(), completion = std::move(completion)]( - Request&& request, const Response& response) mutable { - self->handle_possible_redirect_response(std::move(request), response, std::move(completion)); - }); - return; // early return + // Refresh the location metadata every time an app is created (or when requested) to ensure the http + // and websocket URL information is up to date. + { + std::unique_lock lock(*m_route_mutex); + if (update_location) { + // Force the location to be updated before sending the request. + m_location_updated = false; + } + if (!m_location_updated) { + lock.unlock(); + // Location metadata has not yet been received after the App was created, update the location + // info and then send the request + update_metadata_and_resend(std::move(request), std::move(completion)); + return; // early return + } } - // if we do not have metadata yet, update the metadata and resend the request - update_metadata_and_resend(std::move(request), std::move(completion)); + // location info has already been received after App was created + m_config.transport->send_request_to_server( + std::move(request), [self = shared_from_this(), completion = std::move(completion)]( + Request&& request, const Response& response) mutable { + self->handle_possible_redirect_response(std::move(request), response, std::move(completion)); + }); } void App::handle_possible_redirect_response(Request&& request, const Response& response, @@ -1074,33 +1131,29 @@ void App::refresh_access_token(const std::shared_ptr& sync_user, bool route = util::format("%1/auth/session", m_base_route); } - log_debug("App: refresh_access_token: email: %1", sync_user->user_profile().email()); + log_debug("App: refresh_access_token: email: %1 %2", sync_user->user_profile().email(), + update_location ? "(updating location)" : ""); - Request request{HttpMethod::post, std::move(route), m_request_timeout_ms, - get_request_headers(sync_user, RequestTokenType::RefreshToken)}; - auto handler = [completion = std::move(completion), sync_user](const Response& response) { - if (auto error = AppUtils::check_for_errors(response)) { - return completion(std::move(error)); - } - - try { - auto json = parse(response.body); - sync_user->update_access_token(get(json, "access_token")); - } - catch (AppError& err) { - return completion(std::move(err)); - } + // If update_location is set, force the location info to be updated before sending the request + do_request( + {HttpMethod::post, std::move(route), m_request_timeout_ms, + get_request_headers(sync_user, RequestTokenType::RefreshToken)}, + [completion = std::move(completion), sync_user](const Response& response) { + if (auto error = AppUtils::check_for_errors(response)) { + return completion(std::move(error)); + } - return completion(util::none); - }; + try { + auto json = parse(response.body); + sync_user->update_access_token(get(json, "access_token")); + } + catch (AppError& err) { + return completion(std::move(err)); + } - if (update_location) { - // If update_location, update the location metadata before sending the request - update_metadata_and_resend(std::move(request), std::move(handler)); - } - else { - do_request(std::move(request), std::move(handler)); - } + return completion(util::none); + }, + update_location); } std::string App::function_call_url_path() const diff --git a/src/realm/object-store/sync/app.hpp b/src/realm/object-store/sync/app.hpp index 661cc0d59a5..5e1eb61aaa1 100644 --- a/src/realm/object-store/sync/app.hpp +++ b/src/realm/object-store/sync/app.hpp @@ -59,17 +59,25 @@ class App : public std::enable_shared_from_this, struct Config { // Information about the device where the app is running struct DeviceInfo { - std::string platform; // json: platform std::string platform_version; // json: platformVersion std::string sdk_version; // json: sdkVersion std::string sdk; // json: sdk - std::string cpu_arch; // json: cpuArch std::string device_name; // json: deviceName std::string device_version; // json: deviceVersion std::string framework_name; // json: frameworkName std::string framework_version; // json: frameworkVersion - // Other parameters provided to server no included here: - // * CoreVersion - populated by Sync when the device info is sent + std::string bundle_id; // json: bundleId + + DeviceInfo(); + DeviceInfo(std::string, std::string, std::string, std::string, std::string, std::string, std::string, + std::string); + + private: + friend App; + + std::string platform; // json: platform + std::string cpu_arch; // json: cpuArch + std::string core_version; // json: coreVersion }; std::string app_id; @@ -395,13 +403,17 @@ class App : public std::enable_shared_from_this, friend class OnlyForTesting; Config m_config; - mutable std::unique_ptr m_route_mutex = std::make_unique(); + + // mutable to allow locking for reads in const functions + // this is a shared pointer to support the App move constructor + mutable std::shared_ptr m_route_mutex = std::make_shared(); std::string m_base_url; std::string m_base_route; std::string m_app_route; std::string m_auth_route; - uint64_t m_request_timeout_ms; bool m_location_updated = false; + + uint64_t m_request_timeout_ms; std::shared_ptr m_sync_manager; std::shared_ptr m_logger_ptr; @@ -455,15 +467,15 @@ class App : public std::enable_shared_from_this, void update_metadata_and_resend(Request&& request, util::UniqueFunction&& completion, const util::Optional& new_hostname = util::none); - void basic_request(std::string&& route, std::string&& body, - util::UniqueFunction)>&& completion); void post(std::string&& route, util::UniqueFunction)>&& completion, const bson::BsonDocument& body); /// Performs a request to the Stitch server. This request does not contain authentication state. /// @param request The request to be performed /// @param completion Returns the response from the server - void do_request(Request&& request, util::UniqueFunction&& completion); + /// @param update_location Force the location metadata to be updated prior to sending the request + void do_request(Request&& request, util::UniqueFunction&& completion, + bool update_location = false); /// Check to see if hte response is a redirect and handle, otherwise pass the response to compleetion /// @param request The request to be performed (in case it needs to be sent again) diff --git a/src/realm/object-store/sync/sync_session.cpp b/src/realm/object-store/sync/sync_session.cpp index f5610715929..1a339154065 100644 --- a/src/realm/object-store/sync/sync_session.cpp +++ b/src/realm/object-store/sync/sync_session.cpp @@ -763,13 +763,16 @@ void SyncSession::handle_error(sync::SessionErrorInfo error) } } else if (error_code.category() == sync::websocket::websocket_error_category()) { + using WebSocketError = sync::websocket::WebSocketError; + auto websocket_error = static_cast(error_code.value()); + // The server replies with '401: unauthorized' if the access token is invalid, expired, revoked, or the user // is disabled. In this scenario we attempt an automatic token refresh and if that succeeds continue as // normal. If the refresh request also fails with 401 then we need to stop retrying and pass along the error; // see handle_refresh(). - bool redirect_occurred = error_code == sync::websocket::WebSocketError::websocket_moved_permanently; - if (redirect_occurred || error_code == sync::websocket::WebSocketError::websocket_unauthorized || - error_code == sync::websocket::WebSocketError::websocket_abnormal_closure) { + bool redirect_occurred = websocket_error == WebSocketError::websocket_moved_permanently; + if (redirect_occurred || websocket_error == WebSocketError::websocket_unauthorized || + websocket_error == WebSocketError::websocket_abnormal_closure) { if (auto u = user()) { // If a redirection occurred, the location metadata will be updated before refreshing the access // token. @@ -780,14 +783,13 @@ void SyncSession::handle_error(sync::SessionErrorInfo error) // If the websocket was closed cleanly or if the socket disappeared, don't notify the user as an error // since the sync client will retry. - if (error_code == sync::websocket::WebSocketError::websocket_read_error || - error_code == sync::websocket::WebSocketError::websocket_write_error) { + if (websocket_error == WebSocketError::websocket_read_error || + websocket_error == WebSocketError::websocket_write_error) { return; } // Surface a simplified websocket error to the user. - auto simplified_error = sync::websocket::get_simplified_websocket_error( - static_cast(error_code.value())); + auto simplified_error = sync::websocket::get_simplified_websocket_error(websocket_error); std::error_code new_error_code(simplified_error, sync::websocket::websocket_error_category()); error = sync::SessionErrorInfo(new_error_code, error.message, error.try_again); } diff --git a/src/realm/object-store/util/apple/scheduler.hpp b/src/realm/object-store/util/apple/scheduler.hpp index 023c24617f2..25a9c2e4689 100644 --- a/src/realm/object-store/util/apple/scheduler.hpp +++ b/src/realm/object-store/util/apple/scheduler.hpp @@ -128,6 +128,29 @@ bool RunLoopScheduler::can_invoke() const noexcept return false; } +class MainRunLoopScheduler : public RunLoopScheduler { +public: + MainRunLoopScheduler() + : RunLoopScheduler(CFRunLoopGetMain()) + { + } + + bool is_on_thread() const noexcept override + { + return pthread_main_np(); + } + + bool is_same_as(const Scheduler* other) const noexcept override + { + return typeid(*other) == typeid(MainRunLoopScheduler); + } + + bool can_invoke() const noexcept override + { + return true; + } +}; + class DispatchQueueScheduler : public util::Scheduler { public: DispatchQueueScheduler(dispatch_queue_t queue); diff --git a/src/realm/object-store/util/scheduler.cpp b/src/realm/object-store/util/scheduler.cpp index 9cac5eca39d..ac0a3f55f2e 100644 --- a/src/realm/object-store/util/scheduler.cpp +++ b/src/realm/object-store/util/scheduler.cpp @@ -154,7 +154,11 @@ std::shared_ptr Scheduler::make_dummy() #if REALM_PLATFORM_APPLE std::shared_ptr Scheduler::make_runloop(CFRunLoopRef run_loop) { - return std::make_shared(run_loop ?: CFRunLoopGetCurrent()); + if (!run_loop) + run_loop = CFRunLoopGetCurrent(); + if (run_loop == CFRunLoopGetMain()) + return std::make_shared(); + return std::make_shared(run_loop); } std::shared_ptr Scheduler::make_dispatch(void* queue) diff --git a/src/realm/parser/driver.cpp b/src/realm/parser/driver.cpp index 9e928d9323a..f1b75b4d3a8 100644 --- a/src/realm/parser/driver.cpp +++ b/src/realm/parser/driver.cpp @@ -771,10 +771,16 @@ Query GeoWithinNode::visit(ParserDriver* drv) get_data_type_name(right_type))); } Geospatial geo = drv->m_args.geospatial_for_argument(arg_no); - if (!geo.is_valid()) { + + if (geo.get_type() == Geospatial::Type::Invalid) { throw InvalidQueryError( util::format("The right hand side of 'geoWithin' must be a valid Geospatial value, got '%1'", geo)); } + Status geo_status = geo.is_valid(); + if (!geo_status.is_ok()) { + throw InvalidQueryError( + util::format("The Geospatial query argument region is invalid: '%1'", geo_status.reason())); + } return link_column->geo_within(geo); } @@ -1355,8 +1361,8 @@ GeospatialNode::GeospatialNode(GeospatialNode::Box, GeoPoint& p1, GeoPoint& p2) { } -GeospatialNode::GeospatialNode(Sphere, GeoPoint& p, double radius) - : m_geo{Geospatial{GeoCenterSphere{radius, p}}} +GeospatialNode::GeospatialNode(Circle, GeoPoint& p, double radius) + : m_geo{Geospatial{GeoCircle{radius, p}}} { } @@ -1383,7 +1389,7 @@ void GeospatialNode::add_loop_to_polygon(GeospatialNode* node) std::unique_ptr GeospatialNode::visit(ParserDriver*, DataType) { std::unique_ptr ret; - if (m_geo.is_valid()) { + if (m_geo.get_type() != Geospatial::Type::Invalid) { ret = std::make_unique(m_geo); } else { @@ -1569,7 +1575,7 @@ ParserDriver::~ParserDriver() yylex_destroy(m_yyscanner); } -Mixed ParserDriver::get_arg_for_index(std::string i) +Mixed ParserDriver::get_arg_for_index(const std::string& i) { REALM_ASSERT(i[0] == '$'); size_t arg_no = size_t(strtol(i.substr(1).c_str(), nullptr, 10)); @@ -1580,15 +1586,38 @@ Mixed ParserDriver::get_arg_for_index(std::string i) switch (type) { case type_Int: return int64_t(m_args.long_for_argument(arg_no)); - break; case type_String: return m_args.string_for_argument(arg_no); - break; default: throw InvalidQueryError("Invalid index type"); } } +double ParserDriver::get_arg_for_coordinate(const std::string& str) +{ + REALM_ASSERT(str[0] == '$'); + size_t arg_no = size_t(strtol(str.substr(1).c_str(), nullptr, 10)); + if (m_args.is_argument_null(arg_no)) { + throw InvalidQueryError(util::format("NULL cannot be used in coordinate at argument '%1'", str)); + } + if (m_args.is_argument_list(arg_no)) { + throw InvalidQueryError(util::format("A list cannot be used in a coordinate at argument '%1'", str)); + } + + auto type = m_args.type_for_argument(arg_no); + switch (type) { + case type_Int: + return double(m_args.long_for_argument(arg_no)); + case type_Double: + return m_args.double_for_argument(arg_no); + case type_Float: + return double(m_args.float_for_argument(arg_no)); + default: + throw InvalidQueryError(util::format("Invalid parameter '%1' used in coordinate at argument '%2'", + get_data_type_name(type), str)); + } +} + auto ParserDriver::cmp(const std::vector& values) -> std::pair { SubexprPtr left; diff --git a/src/realm/parser/driver.hpp b/src/realm/parser/driver.hpp index 2b1ad1e9be7..5a7828452ca 100644 --- a/src/realm/parser/driver.hpp +++ b/src/realm/parser/driver.hpp @@ -201,10 +201,10 @@ class GeospatialNode : public ValueNode { struct Box {}; struct Polygon {}; struct Loop {}; - struct Sphere {}; + struct Circle {}; #if REALM_ENABLE_GEOSPATIAL GeospatialNode(Box, GeoPoint& p1, GeoPoint& p2); - GeospatialNode(Sphere, GeoPoint& p, double radius); + GeospatialNode(Circle, GeoPoint& p, double radius); GeospatialNode(Polygon, GeoPoint& p); GeospatialNode(Loop, GeoPoint& p); void add_point_to_loop(GeoPoint& p); @@ -623,7 +623,8 @@ class ParserDriver { parse_error = true; } - Mixed get_arg_for_index(std::string); + Mixed get_arg_for_index(const std::string&); + double get_arg_for_coordinate(const std::string&); template Query simple_query(int op, ColKey col_key, T val, bool case_sensitive); diff --git a/src/realm/parser/generated/query_bison.cpp b/src/realm/parser/generated/query_bison.cpp index 2fb312ffd81..68d82c85e36 100644 --- a/src/realm/parser/generated/query_bison.cpp +++ b/src/realm/parser/generated/query_bison.cpp @@ -883,7 +883,7 @@ namespace yy { { yyo << "<>"; } break; - case symbol_kind::SYM_GEOSPHERE: // "geosphere" + case symbol_kind::SYM_GEOCIRCLE: // "geocircle" { yyo << "<>"; } break; @@ -1728,171 +1728,175 @@ namespace yy { { yylhs.value.as < double > () = double(strtoll(yystack_[0].value.as < std::string > ().c_str(), nullptr, 0)); } break; - case 37: // geopoint: '[' coordinate ',' coordinate ']' + case 37: // coordinate: "argument" + { yylhs.value.as < double > () = drv.get_arg_for_coordinate(yystack_[0].value.as < std::string > ()); } + break; + + case 38: // geopoint: '[' coordinate ',' coordinate ']' { yylhs.value.as < std::optional > () = GeoPoint{yystack_[3].value.as < double > (), yystack_[1].value.as < double > ()}; } break; - case 38: // geopoint: '[' coordinate ',' coordinate ',' "float" ']' + case 39: // geopoint: '[' coordinate ',' coordinate ',' "float" ']' { yylhs.value.as < std::optional > () = GeoPoint{yystack_[5].value.as < double > (), yystack_[3].value.as < double > (), strtod(yystack_[1].value.as < std::string > ().c_str(), nullptr)}; } break; - case 39: // geoloop_content: geopoint + case 40: // geoloop_content: geopoint { yylhs.value.as < GeospatialNode* > () = drv.m_parse_nodes.create(GeospatialNode::Loop{}, *yystack_[0].value.as < std::optional > ()); } break; - case 40: // geoloop_content: geoloop_content ',' geopoint + case 41: // geoloop_content: geoloop_content ',' geopoint { yystack_[2].value.as < GeospatialNode* > ()->add_point_to_loop(*yystack_[0].value.as < std::optional > ()); yylhs.value.as < GeospatialNode* > () = yystack_[2].value.as < GeospatialNode* > (); } break; - case 41: // geoloop: '{' geoloop_content '}' + case 42: // geoloop: '{' geoloop_content '}' { yylhs.value.as < GeospatialNode* > () = yystack_[1].value.as < GeospatialNode* > (); } break; - case 42: // geopoly_content: geoloop + case 43: // geopoly_content: geoloop { yylhs.value.as < GeospatialNode* > () = yystack_[0].value.as < GeospatialNode* > (); } break; - case 43: // geopoly_content: geopoly_content ',' geoloop + case 44: // geopoly_content: geopoly_content ',' geoloop { yystack_[2].value.as < GeospatialNode* > ()->add_loop_to_polygon(yystack_[0].value.as < GeospatialNode* > ()); yylhs.value.as < GeospatialNode* > () = yystack_[2].value.as < GeospatialNode* > (); } break; - case 44: // geospatial: "geobox" '(' geopoint ',' geopoint ')' + case 45: // geospatial: "geobox" '(' geopoint ',' geopoint ')' { yylhs.value.as < GeospatialNode* > () = drv.m_parse_nodes.create(GeospatialNode::Box{}, *yystack_[3].value.as < std::optional > (), *yystack_[1].value.as < std::optional > ()); } break; - case 45: // geospatial: "geosphere" '(' geopoint ',' coordinate ')' - { yylhs.value.as < GeospatialNode* > () = drv.m_parse_nodes.create(GeospatialNode::Sphere{}, *yystack_[3].value.as < std::optional > (), yystack_[1].value.as < double > ()); } + case 46: // geospatial: "geocircle" '(' geopoint ',' coordinate ')' + { yylhs.value.as < GeospatialNode* > () = drv.m_parse_nodes.create(GeospatialNode::Circle{}, *yystack_[3].value.as < std::optional > (), yystack_[1].value.as < double > ()); } break; - case 46: // geospatial: "geopolygon" '(' geopoly_content ')' + case 47: // geospatial: "geopolygon" '(' geopoly_content ')' { yylhs.value.as < GeospatialNode* > () = yystack_[1].value.as < GeospatialNode* > (); } break; - case 47: // post_query: %empty + case 48: // post_query: %empty { yylhs.value.as < DescriptorOrderingNode* > () = drv.m_parse_nodes.create();} break; - case 48: // post_query: post_query sort + case 49: // post_query: post_query sort { yystack_[1].value.as < DescriptorOrderingNode* > ()->add_descriptor(yystack_[0].value.as < DescriptorNode* > ()); yylhs.value.as < DescriptorOrderingNode* > () = yystack_[1].value.as < DescriptorOrderingNode* > (); } break; - case 49: // post_query: post_query distinct + case 50: // post_query: post_query distinct { yystack_[1].value.as < DescriptorOrderingNode* > ()->add_descriptor(yystack_[0].value.as < DescriptorNode* > ()); yylhs.value.as < DescriptorOrderingNode* > () = yystack_[1].value.as < DescriptorOrderingNode* > (); } break; - case 50: // post_query: post_query limit + case 51: // post_query: post_query limit { yystack_[1].value.as < DescriptorOrderingNode* > ()->add_descriptor(yystack_[0].value.as < DescriptorNode* > ()); yylhs.value.as < DescriptorOrderingNode* > () = yystack_[1].value.as < DescriptorOrderingNode* > (); } break; - case 51: // distinct: "distinct" '(' distinct_param ')' + case 52: // distinct: "distinct" '(' distinct_param ')' { yylhs.value.as < DescriptorNode* > () = yystack_[1].value.as < DescriptorNode* > (); } break; - case 52: // distinct_param: path + case 53: // distinct_param: path { yylhs.value.as < DescriptorNode* > () = drv.m_parse_nodes.create(DescriptorNode::DISTINCT); yylhs.value.as < DescriptorNode* > ()->add(yystack_[0].value.as < PathNode* > ());} break; - case 53: // distinct_param: distinct_param ',' path + case 54: // distinct_param: distinct_param ',' path { yystack_[2].value.as < DescriptorNode* > ()->add(yystack_[0].value.as < PathNode* > ()); yylhs.value.as < DescriptorNode* > () = yystack_[2].value.as < DescriptorNode* > (); } break; - case 54: // sort: "sort" '(' sort_param ')' + case 55: // sort: "sort" '(' sort_param ')' { yylhs.value.as < DescriptorNode* > () = yystack_[1].value.as < DescriptorNode* > (); } break; - case 55: // sort_param: path direction + case 56: // sort_param: path direction { yylhs.value.as < DescriptorNode* > () = drv.m_parse_nodes.create(DescriptorNode::SORT); yylhs.value.as < DescriptorNode* > ()->add(yystack_[1].value.as < PathNode* > (), yystack_[0].value.as < bool > ());} break; - case 56: // sort_param: sort_param ',' path direction + case 57: // sort_param: sort_param ',' path direction { yystack_[3].value.as < DescriptorNode* > ()->add(yystack_[1].value.as < PathNode* > (), yystack_[0].value.as < bool > ()); yylhs.value.as < DescriptorNode* > () = yystack_[3].value.as < DescriptorNode* > (); } break; - case 57: // limit: "limit" '(' "natural0" ')' + case 58: // limit: "limit" '(' "natural0" ')' { yylhs.value.as < DescriptorNode* > () = drv.m_parse_nodes.create(DescriptorNode::LIMIT, yystack_[1].value.as < std::string > ()); } break; - case 58: // direction: "ascending" + case 59: // direction: "ascending" { yylhs.value.as < bool > () = true; } break; - case 59: // direction: "descending" + case 60: // direction: "descending" { yylhs.value.as < bool > () = false; } break; - case 60: // list: '{' list_content '}' + case 61: // list: '{' list_content '}' { yylhs.value.as < ListNode* > () = yystack_[1].value.as < ListNode* > (); } break; - case 61: // list: comp_type '{' list_content '}' + case 62: // list: comp_type '{' list_content '}' { yystack_[1].value.as < ListNode* > ()->set_comp_type(ExpressionComparisonType(yystack_[3].value.as < int > ())); yylhs.value.as < ListNode* > () = yystack_[1].value.as < ListNode* > (); } break; - case 62: // list_content: constant + case 63: // list_content: constant { yylhs.value.as < ListNode* > () = drv.m_parse_nodes.create(yystack_[0].value.as < ConstantNode* > ()); } break; - case 63: // list_content: %empty + case 64: // list_content: %empty { yylhs.value.as < ListNode* > () = drv.m_parse_nodes.create(); } break; - case 64: // list_content: list_content ',' constant + case 65: // list_content: list_content ',' constant { yystack_[2].value.as < ListNode* > ()->add_element(yystack_[0].value.as < ConstantNode* > ()); yylhs.value.as < ListNode* > () = yystack_[2].value.as < ListNode* > (); } break; - case 65: // constant: primary_key + case 66: // constant: primary_key { yylhs.value.as < ConstantNode* > () = yystack_[0].value.as < ConstantNode* > (); } break; - case 66: // constant: "infinity" + case 67: // constant: "infinity" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::INFINITY_VAL, yystack_[0].value.as < std::string > ()); } break; - case 67: // constant: "NaN" + case 68: // constant: "NaN" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::NAN_VAL, yystack_[0].value.as < std::string > ()); } break; - case 68: // constant: "base64" + case 69: // constant: "base64" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::BASE64, yystack_[0].value.as < std::string > ()); } break; - case 69: // constant: "float" + case 70: // constant: "float" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::FLOAT, yystack_[0].value.as < std::string > ()); } break; - case 70: // constant: "date" + case 71: // constant: "date" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::TIMESTAMP, yystack_[0].value.as < std::string > ()); } break; - case 71: // constant: "link" + case 72: // constant: "link" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::LINK, yystack_[0].value.as < std::string > ()); } break; - case 72: // constant: "typed link" + case 73: // constant: "typed link" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::TYPED_LINK, yystack_[0].value.as < std::string > ()); } break; - case 73: // constant: "true" + case 74: // constant: "true" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::TRUE, ""); } break; - case 74: // constant: "false" + case 75: // constant: "false" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::FALSE, ""); } break; - case 75: // constant: "null" + case 76: // constant: "null" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::NULL_VAL, ""); } break; - case 76: // constant: "argument" + case 77: // constant: "argument" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::ARG, yystack_[0].value.as < std::string > ()); } break; - case 77: // constant: comp_type "argument" + case 78: // constant: comp_type "argument" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ExpressionComparisonType(yystack_[1].value.as < int > ()), yystack_[0].value.as < std::string > ()); } break; - case 78: // constant: "obj" '(' "string" ',' primary_key ')' + case 79: // constant: "obj" '(' "string" ',' primary_key ')' { auto tmp = yystack_[1].value.as < ConstantNode* > (); tmp->add_table(yystack_[3].value.as < std::string > ()); @@ -1900,199 +1904,199 @@ namespace yy { } break; - case 79: // primary_key: "natural0" + case 80: // primary_key: "natural0" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::NUMBER, yystack_[0].value.as < std::string > ()); } break; - case 80: // primary_key: "number" + case 81: // primary_key: "number" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::NUMBER, yystack_[0].value.as < std::string > ()); } break; - case 81: // primary_key: "string" + case 82: // primary_key: "string" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::STRING, yystack_[0].value.as < std::string > ()); } break; - case 82: // primary_key: "UUID" + case 83: // primary_key: "UUID" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::UUID_T, yystack_[0].value.as < std::string > ()); } break; - case 83: // primary_key: "ObjectId" + case 84: // primary_key: "ObjectId" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::OID, yystack_[0].value.as < std::string > ()); } break; - case 84: // boolexpr: "truepredicate" + case 85: // boolexpr: "truepredicate" { yylhs.value.as < TrueOrFalseNode* > () = drv.m_parse_nodes.create(true); } break; - case 85: // boolexpr: "falsepredicate" + case 86: // boolexpr: "falsepredicate" { yylhs.value.as < TrueOrFalseNode* > () = drv.m_parse_nodes.create(false); } break; - case 86: // comp_type: "any" + case 87: // comp_type: "any" { yylhs.value.as < int > () = int(ExpressionComparisonType::Any); } break; - case 87: // comp_type: "all" + case 88: // comp_type: "all" { yylhs.value.as < int > () = int(ExpressionComparisonType::All); } break; - case 88: // comp_type: "none" + case 89: // comp_type: "none" { yylhs.value.as < int > () = int(ExpressionComparisonType::None); } break; - case 89: // post_op: %empty + case 90: // post_op: %empty { yylhs.value.as < PostOpNode* > () = nullptr; } break; - case 90: // post_op: '.' "@size" + case 91: // post_op: '.' "@size" { yylhs.value.as < PostOpNode* > () = drv.m_parse_nodes.create(yystack_[0].value.as < std::string > (), PostOpNode::SIZE);} break; - case 91: // post_op: '.' "@type" + case 92: // post_op: '.' "@type" { yylhs.value.as < PostOpNode* > () = drv.m_parse_nodes.create(yystack_[0].value.as < std::string > (), PostOpNode::TYPE);} break; - case 92: // aggr_op: '.' "@max" + case 93: // aggr_op: '.' "@max" { yylhs.value.as < int > () = int(AggrNode::MAX);} break; - case 93: // aggr_op: '.' "@min" + case 94: // aggr_op: '.' "@min" { yylhs.value.as < int > () = int(AggrNode::MIN);} break; - case 94: // aggr_op: '.' "@sun" + case 95: // aggr_op: '.' "@sun" { yylhs.value.as < int > () = int(AggrNode::SUM);} break; - case 95: // aggr_op: '.' "@average" + case 96: // aggr_op: '.' "@average" { yylhs.value.as < int > () = int(AggrNode::AVG);} break; - case 96: // equality: "==" + case 97: // equality: "==" { yylhs.value.as < int > () = CompareNode::EQUAL; } break; - case 97: // equality: "!=" + case 98: // equality: "!=" { yylhs.value.as < int > () = CompareNode::NOT_EQUAL; } break; - case 98: // equality: "in" + case 99: // equality: "in" { yylhs.value.as < int > () = CompareNode::IN; } break; - case 99: // relational: "<" + case 100: // relational: "<" { yylhs.value.as < int > () = CompareNode::LESS; } break; - case 100: // relational: "<=" + case 101: // relational: "<=" { yylhs.value.as < int > () = CompareNode::LESS_EQUAL; } break; - case 101: // relational: ">" + case 102: // relational: ">" { yylhs.value.as < int > () = CompareNode::GREATER; } break; - case 102: // relational: ">=" + case 103: // relational: ">=" { yylhs.value.as < int > () = CompareNode::GREATER_EQUAL; } break; - case 103: // stringop: "beginswith" + case 104: // stringop: "beginswith" { yylhs.value.as < int > () = CompareNode::BEGINSWITH; } break; - case 104: // stringop: "endswith" + case 105: // stringop: "endswith" { yylhs.value.as < int > () = CompareNode::ENDSWITH; } break; - case 105: // stringop: "contains" + case 106: // stringop: "contains" { yylhs.value.as < int > () = CompareNode::CONTAINS; } break; - case 106: // stringop: "like" + case 107: // stringop: "like" { yylhs.value.as < int > () = CompareNode::LIKE; } break; - case 107: // path: path_elem + case 108: // path: path_elem { yylhs.value.as < PathNode* > () = drv.m_parse_nodes.create(yystack_[0].value.as < PathElem > ()); } break; - case 108: // path: path '.' path_elem + case 109: // path: path '.' path_elem { yystack_[2].value.as < PathNode* > ()->add_element(yystack_[0].value.as < PathElem > ()); yylhs.value.as < PathNode* > () = yystack_[2].value.as < PathNode* > (); } break; - case 109: // path_elem: id + case 110: // path_elem: id { yylhs.value.as < PathElem > () = PathElem{yystack_[0].value.as < std::string > ()}; } break; - case 110: // path_elem: id '[' "natural0" ']' + case 111: // path_elem: id '[' "natural0" ']' { yylhs.value.as < PathElem > () = PathElem{yystack_[3].value.as < std::string > (), int64_t(strtoll(yystack_[1].value.as < std::string > ().c_str(), nullptr, 0))}; } break; - case 111: // path_elem: id '[' "string" ']' + case 112: // path_elem: id '[' "string" ']' { yylhs.value.as < PathElem > () = PathElem{yystack_[3].value.as < std::string > (), yystack_[1].value.as < std::string > ().substr(1, yystack_[1].value.as < std::string > ().size() - 2)}; } break; - case 112: // path_elem: id '[' "argument" ']' + case 113: // path_elem: id '[' "argument" ']' { yylhs.value.as < PathElem > () = PathElem{yystack_[3].value.as < std::string > (), drv.get_arg_for_index(yystack_[1].value.as < std::string > ())}; } break; - case 113: // id: "identifier" + case 114: // id: "identifier" { yylhs.value.as < std::string > () = yystack_[0].value.as < std::string > (); } break; - case 114: // id: "@links" + case 115: // id: "@links" { yylhs.value.as < std::string > () = std::string("@links"); } break; - case 115: // id: "beginswith" + case 116: // id: "beginswith" { yylhs.value.as < std::string > () = yystack_[0].value.as < std::string > (); } break; - case 116: // id: "endswith" + case 117: // id: "endswith" { yylhs.value.as < std::string > () = yystack_[0].value.as < std::string > (); } break; - case 117: // id: "contains" + case 118: // id: "contains" { yylhs.value.as < std::string > () = yystack_[0].value.as < std::string > (); } break; - case 118: // id: "like" + case 119: // id: "like" { yylhs.value.as < std::string > () = yystack_[0].value.as < std::string > (); } break; - case 119: // id: "between" + case 120: // id: "between" { yylhs.value.as < std::string > () = yystack_[0].value.as < std::string > (); } break; - case 120: // id: "key or value" + case 121: // id: "key or value" { yylhs.value.as < std::string > () = yystack_[0].value.as < std::string > (); } break; - case 121: // id: "sort" + case 122: // id: "sort" { yylhs.value.as < std::string > () = yystack_[0].value.as < std::string > (); } break; - case 122: // id: "distinct" + case 123: // id: "distinct" { yylhs.value.as < std::string > () = yystack_[0].value.as < std::string > (); } break; - case 123: // id: "limit" + case 124: // id: "limit" { yylhs.value.as < std::string > () = yystack_[0].value.as < std::string > (); } break; - case 124: // id: "ascending" + case 125: // id: "ascending" { yylhs.value.as < std::string > () = yystack_[0].value.as < std::string > (); } break; - case 125: // id: "descending" + case 126: // id: "descending" { yylhs.value.as < std::string > () = yystack_[0].value.as < std::string > (); } break; - case 126: // id: "in" + case 127: // id: "in" { yylhs.value.as < std::string > () = yystack_[0].value.as < std::string > (); } break; - case 127: // id: "fulltext" + case 128: // id: "fulltext" { yylhs.value.as < std::string > () = yystack_[0].value.as < std::string > (); } break; @@ -2444,78 +2448,78 @@ namespace yy { } - const short parser::yypact_ninf_ = -192; + const short parser::yypact_ninf_ = -193; const signed char parser::yytable_ninf_ = -1; const short parser::yypact_[] = { - 106, -192, -192, -59, -192, -192, -192, -192, -192, -192, - -192, 106, -192, -192, -192, -192, -192, -192, -192, -192, - -192, -192, -192, -192, -192, -192, -192, -192, -192, -192, - -192, -192, -192, -24, -192, -192, -192, -192, -192, -192, - 106, 428, 14, 9, -192, 234, 49, 26, -192, -192, - -192, -192, -192, -192, 372, -2, -192, 18, 495, -192, - 71, -7, 12, -56, -192, 37, -192, 106, 106, 74, - -192, -192, -192, -192, -192, -192, -192, 223, 223, 223, - 223, 167, 223, -192, -192, -192, 340, -192, 11, 284, - 63, -192, 428, 38, 453, -192, 67, 1, 89, 97, - 101, -192, -192, 428, -192, -192, 146, 111, 127, 129, - -192, -192, -192, 223, 20, -192, -192, 20, -192, -192, - 223, 126, 126, -192, -192, 124, 340, -192, 131, 153, - 160, -192, -192, -53, 474, -192, -192, -192, -192, -192, - -192, -192, -192, 495, 156, 163, 164, 495, 495, 68, - -192, 495, 495, 200, 54, 126, -192, 168, 165, 168, - -192, -192, -192, -192, -192, 181, 184, 53, -3, 65, - 97, 185, 130, 206, 168, -192, 113, 213, 106, -192, - -192, 495, -192, -192, -192, -192, 495, -192, -192, -192, - 214, 168, -192, -29, -192, 165, 130, 13, -3, 97, - 130, 186, 168, -192, -192, 219, 220, -192, 123, -192, - -192, -192, 228, 256, -192, -192, 235, -192 + 106, -193, -193, -59, -193, -193, -193, -193, -193, -193, + -193, 106, -193, -193, -193, -193, -193, -193, -193, -193, + -193, -193, -193, -193, -193, -193, -193, -193, -193, -193, + -193, -193, -193, 19, -193, -193, -193, -193, -193, -193, + 106, 428, 33, -9, -193, 234, 49, 14, -193, -193, + -193, -193, -193, -193, 372, 10, -193, 36, 495, -193, + 71, -7, 12, -33, -193, 37, -193, 106, 106, 138, + -193, -193, -193, -193, -193, -193, -193, 223, 223, 223, + 223, 167, 223, -193, -193, -193, 340, -193, 11, 284, + 63, -193, 428, 66, 453, -193, 67, 1, 89, 97, + 101, -193, -193, 428, -193, -193, 146, 129, 130, 131, + -193, -193, -193, 223, 65, -193, -193, 65, -193, -193, + 223, 126, 126, -193, -193, 105, 340, -193, 153, 160, + 161, -193, -193, -12, 474, -193, -193, -193, -193, -193, + -193, -193, -193, 495, 163, 164, 165, 495, 495, 68, + -193, 495, 495, 201, 54, 126, -193, 168, 178, 168, + -193, -193, -193, -193, -193, 182, 185, -25, -3, 53, + 97, 186, -23, 206, 168, -193, 99, 213, 106, -193, + -193, 495, -193, -193, -193, -193, 495, -193, -193, -193, + -193, 214, 168, -193, 15, -193, 178, -23, 13, -3, + 97, -23, 219, 168, -193, -193, 220, 226, -193, 111, + -193, -193, -193, 229, 267, -193, -193, 235, -193 }; - const signed char + const unsigned char parser::yydefact_[] = { - 0, 84, 85, 0, 73, 74, 75, 86, 87, 88, - 114, 0, 113, 81, 68, 66, 67, 79, 80, 69, - 70, 82, 83, 71, 72, 76, 115, 116, 117, 127, - 118, 119, 126, 0, 121, 122, 123, 124, 125, 120, - 0, 63, 0, 47, 3, 0, 18, 25, 27, 28, - 26, 24, 65, 8, 0, 89, 107, 109, 0, 6, - 0, 0, 0, 0, 62, 0, 1, 0, 0, 2, - 96, 97, 99, 101, 102, 100, 98, 0, 0, 0, - 0, 0, 0, 103, 104, 105, 0, 106, 0, 0, - 0, 77, 63, 89, 0, 29, 32, 0, 0, 33, - 0, 7, 19, 0, 60, 5, 4, 0, 0, 0, - 49, 48, 50, 0, 22, 18, 25, 23, 20, 21, + 0, 85, 86, 0, 74, 75, 76, 87, 88, 89, + 115, 0, 114, 82, 69, 67, 68, 80, 81, 70, + 71, 83, 84, 72, 73, 77, 116, 117, 118, 128, + 119, 120, 127, 0, 122, 123, 124, 125, 126, 121, + 0, 64, 0, 48, 3, 0, 18, 25, 27, 28, + 26, 24, 66, 8, 0, 90, 108, 110, 0, 6, + 0, 0, 0, 0, 63, 0, 1, 0, 0, 2, + 97, 98, 100, 102, 103, 101, 99, 0, 0, 0, + 0, 0, 0, 104, 105, 106, 0, 107, 0, 0, + 0, 78, 64, 90, 0, 29, 32, 0, 0, 33, + 0, 7, 19, 0, 61, 5, 4, 0, 0, 0, + 50, 49, 51, 0, 22, 18, 25, 23, 20, 21, 0, 9, 11, 13, 15, 0, 0, 12, 0, 0, - 0, 17, 16, 0, 0, 30, 92, 93, 94, 95, - 90, 91, 108, 0, 0, 0, 0, 0, 0, 0, - 64, 0, 0, 0, 0, 10, 14, 0, 0, 0, - 61, 31, 111, 110, 112, 0, 0, 0, 0, 0, - 52, 0, 0, 0, 0, 42, 0, 0, 0, 78, - 54, 0, 58, 59, 55, 51, 0, 57, 36, 35, - 0, 0, 39, 0, 46, 0, 0, 0, 0, 53, - 0, 0, 0, 41, 43, 0, 0, 56, 0, 44, - 40, 45, 0, 0, 37, 34, 0, 38 + 0, 17, 16, 0, 0, 30, 93, 94, 95, 96, + 91, 92, 109, 0, 0, 0, 0, 0, 0, 0, + 65, 0, 0, 0, 0, 10, 14, 0, 0, 0, + 62, 31, 112, 111, 113, 0, 0, 0, 0, 0, + 53, 0, 0, 0, 0, 43, 0, 0, 0, 79, + 55, 0, 59, 60, 56, 52, 0, 58, 36, 35, + 37, 0, 0, 40, 0, 47, 0, 0, 0, 0, + 54, 0, 0, 0, 42, 44, 0, 0, 57, 0, + 45, 41, 46, 0, 0, 38, 34, 0, 39 }; const short parser::yypgoto_[] = { - -192, -192, -10, -192, -32, 0, 2, -192, -192, -192, - -191, -139, -192, 109, -192, -192, -192, -192, -192, -192, - -192, -192, 108, 221, 215, -31, 159, -192, -37, 217, - -192, -192, -192, -192, -51, -63, -87 + -193, -193, -10, -193, -32, 0, 2, -193, -193, -193, + -192, -140, -193, 110, -193, -193, -193, -193, -193, -193, + -193, -193, 108, 221, 216, -31, 162, -193, -37, 217, + -193, -193, -193, -193, -51, -63, -16 }; const unsigned char parser::yydefgoto_[] = { 0, 42, 43, 44, 45, 115, 116, 48, 98, 49, - 190, 173, 193, 175, 176, 132, 69, 110, 169, 111, + 191, 173, 194, 175, 176, 132, 69, 110, 169, 111, 167, 112, 184, 50, 63, 51, 52, 53, 54, 95, 96, 81, 82, 89, 55, 56, 57 }; @@ -2523,38 +2527,38 @@ namespace yy { const unsigned char parser::yytable_[] = { - 46, 59, 47, 93, 65, 205, 58, 99, 62, 208, - 64, 46, 103, 47, 66, 103, 104, 67, 68, 160, - 177, 70, 71, 72, 73, 74, 75, 7, 8, 9, - 61, 142, 144, 67, 68, 192, 145, 67, 68, 202, - 46, 60, 47, 203, 146, 114, 117, 118, 119, 121, - 122, 125, 201, 182, 183, 65, 161, 105, 106, 101, - 165, 64, 76, 210, 148, 94, 65, 46, 46, 47, - 47, 142, 150, 77, 78, 79, 80, 90, 102, 206, - 91, 154, 41, 79, 80, 142, 123, 97, 155, 127, + 46, 59, 47, 93, 65, 206, 58, 99, 62, 209, + 64, 46, 188, 47, 189, 67, 68, 67, 68, 177, + 190, 70, 71, 72, 73, 74, 75, 7, 8, 9, + 61, 142, 144, 66, 193, 103, 145, 67, 68, 104, + 46, 180, 47, 181, 146, 114, 117, 118, 119, 121, + 122, 125, 202, 182, 183, 65, 103, 105, 106, 101, + 160, 64, 76, 211, 148, 90, 65, 46, 46, 47, + 47, 142, 150, 77, 78, 79, 80, 94, 102, 207, + 91, 154, 41, 203, 60, 142, 123, 204, 155, 127, 128, 129, 130, 83, 84, 85, 86, 87, 88, 13, - 168, 170, 100, 17, 18, 134, 131, 21, 22, 1, - 2, 3, 4, 5, 6, 77, 78, 79, 80, 180, - 102, 181, 7, 8, 9, 10, 156, 107, 108, 109, - 198, 185, 11, 186, 143, 199, 12, 13, 14, 15, + 168, 170, 100, 17, 18, 97, 131, 21, 22, 1, + 2, 3, 4, 5, 6, 77, 78, 79, 80, 185, + 102, 186, 7, 8, 9, 10, 156, 161, 79, 80, + 199, 165, 11, 134, 143, 200, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 147, 33, 34, - 35, 36, 37, 38, 148, 188, 39, 189, 197, 149, - 67, 40, 3, 4, 5, 6, 151, 41, 46, 194, - 47, 195, 120, 7, 8, 9, 10, 77, 78, 79, - 80, 213, 152, 214, 153, 92, 157, 12, 13, 14, + 35, 36, 37, 38, 148, 195, 39, 196, 198, 149, + 67, 40, 3, 4, 5, 6, 92, 41, 46, 214, + 47, 215, 120, 7, 8, 9, 10, 77, 78, 79, + 80, 107, 108, 109, 151, 152, 153, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, - 25, 26, 27, 28, 29, 30, 31, 32, 158, 33, - 34, 35, 36, 37, 38, 159, 162, 39, 3, 4, - 5, 6, 113, 163, 164, 171, 174, 172, 41, 7, - 8, 9, 10, 70, 71, 72, 73, 74, 75, 178, - 179, 187, 209, 12, 13, 14, 15, 16, 17, 18, + 25, 26, 27, 28, 29, 30, 31, 32, 157, 33, + 34, 35, 36, 37, 38, 158, 159, 39, 3, 4, + 5, 6, 113, 162, 163, 164, 171, 172, 41, 7, + 8, 9, 10, 70, 71, 72, 73, 74, 75, 174, + 178, 179, 187, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, - 29, 30, 31, 32, 191, 33, 34, 35, 36, 37, - 38, 196, 200, 39, 76, 211, 215, 212, 113, 3, - 4, 5, 6, 216, 41, 77, 78, 79, 80, 126, - 7, 8, 9, 10, 204, 217, 207, 133, 166, 124, - 135, 0, 0, 0, 12, 13, 14, 15, 16, 17, + 29, 30, 31, 32, 192, 33, 34, 35, 36, 37, + 38, 197, 201, 39, 76, 210, 212, 216, 113, 3, + 4, 5, 6, 213, 41, 77, 78, 79, 80, 126, + 7, 8, 9, 10, 217, 218, 205, 208, 133, 124, + 135, 166, 0, 0, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 0, 33, 34, 35, 36, 37, 38, 0, 0, 39, 3, 4, 5, 6, 0, @@ -2584,38 +2588,38 @@ namespace yy { const short parser::yycheck_[] = { - 0, 11, 0, 54, 41, 196, 65, 58, 40, 200, - 41, 11, 68, 11, 0, 68, 72, 24, 25, 72, - 159, 9, 10, 11, 12, 13, 14, 16, 17, 18, - 40, 94, 31, 24, 25, 174, 35, 24, 25, 68, - 40, 65, 40, 72, 43, 77, 78, 79, 80, 81, - 82, 88, 191, 56, 57, 92, 143, 67, 68, 66, - 147, 92, 50, 202, 67, 67, 103, 67, 68, 67, - 68, 134, 103, 61, 62, 63, 64, 51, 66, 66, - 43, 113, 71, 63, 64, 148, 86, 69, 120, 89, + 0, 11, 0, 54, 41, 197, 65, 58, 40, 201, + 41, 11, 35, 11, 37, 24, 25, 24, 25, 159, + 43, 9, 10, 11, 12, 13, 14, 16, 17, 18, + 40, 94, 31, 0, 174, 68, 35, 24, 25, 72, + 40, 66, 40, 68, 43, 77, 78, 79, 80, 81, + 82, 88, 192, 56, 57, 92, 68, 67, 68, 66, + 72, 92, 50, 203, 67, 51, 103, 67, 68, 67, + 68, 134, 103, 61, 62, 63, 64, 67, 66, 66, + 43, 113, 71, 68, 65, 148, 86, 72, 120, 89, 27, 28, 29, 44, 45, 46, 47, 48, 49, 31, - 151, 152, 31, 35, 36, 67, 43, 39, 40, 3, + 151, 152, 31, 35, 36, 69, 43, 39, 40, 3, 4, 5, 6, 7, 8, 61, 62, 63, 64, 66, - 66, 68, 16, 17, 18, 19, 126, 53, 54, 55, - 181, 66, 26, 68, 67, 186, 30, 31, 32, 33, + 66, 68, 16, 17, 18, 19, 126, 143, 63, 64, + 181, 147, 26, 67, 67, 186, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 68, 52, 53, - 54, 55, 56, 57, 67, 35, 60, 37, 178, 68, - 24, 65, 5, 6, 7, 8, 65, 71, 178, 66, - 178, 68, 15, 16, 17, 18, 19, 61, 62, 63, - 64, 68, 65, 70, 65, 71, 65, 30, 31, 32, + 54, 55, 56, 57, 67, 66, 60, 68, 178, 68, + 24, 65, 5, 6, 7, 8, 71, 71, 178, 68, + 178, 70, 15, 16, 17, 18, 19, 61, 62, 63, + 64, 53, 54, 55, 65, 65, 65, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 65, 52, - 53, 54, 55, 56, 57, 65, 70, 60, 5, 6, - 7, 8, 65, 70, 70, 35, 71, 69, 71, 16, - 17, 18, 19, 9, 10, 11, 12, 13, 14, 68, - 66, 66, 66, 30, 31, 32, 33, 34, 35, 36, + 53, 54, 55, 56, 57, 65, 65, 60, 5, 6, + 7, 8, 65, 70, 70, 70, 35, 69, 71, 16, + 17, 18, 19, 9, 10, 11, 12, 13, 14, 71, + 68, 66, 66, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 68, 52, 53, 54, 55, 56, - 57, 68, 68, 60, 50, 66, 58, 67, 65, 5, - 6, 7, 8, 37, 71, 61, 62, 63, 64, 15, - 16, 17, 18, 19, 195, 70, 198, 92, 149, 88, - 93, -1, -1, -1, 30, 31, 32, 33, 34, 35, + 57, 68, 68, 60, 50, 66, 66, 58, 65, 5, + 6, 7, 8, 67, 71, 61, 62, 63, 64, 15, + 16, 17, 18, 19, 37, 70, 196, 199, 92, 88, + 93, 149, -1, -1, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, -1, 52, 53, 54, 55, 56, 57, -1, -1, 60, 5, 6, 7, 8, -1, @@ -2664,9 +2668,9 @@ namespace yy { 72, 109, 70, 70, 70, 109, 99, 93, 107, 91, 107, 35, 69, 84, 71, 86, 87, 84, 68, 66, 66, 68, 56, 57, 95, 66, 68, 66, 35, 37, - 83, 68, 84, 85, 66, 68, 68, 75, 107, 107, - 68, 84, 68, 72, 86, 83, 66, 95, 83, 66, - 84, 66, 67, 68, 70, 58, 37, 70 + 43, 83, 68, 84, 85, 66, 68, 68, 75, 107, + 107, 68, 84, 68, 72, 86, 83, 66, 95, 83, + 66, 84, 66, 67, 68, 70, 58, 37, 70 }; const signed char @@ -2675,16 +2679,16 @@ namespace yy { 0, 73, 74, 75, 75, 75, 75, 75, 75, 76, 76, 76, 76, 76, 76, 76, 76, 76, 77, 77, 77, 77, 77, 77, 78, 78, 78, 78, 78, 79, - 79, 80, 80, 81, 82, 83, 83, 84, 84, 85, - 85, 86, 87, 87, 88, 88, 88, 89, 89, 89, - 89, 90, 91, 91, 92, 93, 93, 94, 95, 95, - 96, 96, 97, 97, 97, 98, 98, 98, 98, 98, - 98, 98, 98, 98, 98, 98, 98, 98, 98, 99, - 99, 99, 99, 99, 100, 100, 101, 101, 101, 102, - 102, 102, 103, 103, 103, 103, 104, 104, 104, 105, - 105, 105, 105, 106, 106, 106, 106, 107, 107, 108, - 108, 108, 108, 109, 109, 109, 109, 109, 109, 109, - 109, 109, 109, 109, 109, 109, 109, 109 + 79, 80, 80, 81, 82, 83, 83, 83, 84, 84, + 85, 85, 86, 87, 87, 88, 88, 88, 89, 89, + 89, 89, 90, 91, 91, 92, 93, 93, 94, 95, + 95, 96, 96, 97, 97, 97, 98, 98, 98, 98, + 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, + 99, 99, 99, 99, 99, 100, 100, 101, 101, 101, + 102, 102, 102, 103, 103, 103, 103, 104, 104, 104, + 105, 105, 105, 105, 106, 106, 106, 106, 107, 107, + 108, 108, 108, 108, 109, 109, 109, 109, 109, 109, + 109, 109, 109, 109, 109, 109, 109, 109, 109 }; const signed char @@ -2693,16 +2697,16 @@ namespace yy { 0, 2, 2, 1, 3, 3, 2, 3, 1, 3, 4, 3, 3, 3, 4, 3, 3, 3, 1, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, 2, - 3, 4, 2, 1, 10, 1, 1, 5, 7, 1, - 3, 3, 1, 3, 6, 6, 4, 0, 2, 2, - 2, 4, 1, 3, 4, 2, 4, 4, 1, 1, - 3, 4, 1, 0, 3, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 2, 6, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, - 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, - 4, 4, 4, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1 + 3, 4, 2, 1, 10, 1, 1, 1, 5, 7, + 1, 3, 3, 1, 3, 6, 6, 4, 0, 2, + 2, 2, 4, 1, 3, 4, 2, 4, 4, 1, + 1, 3, 4, 1, 0, 3, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 6, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 2, 2, 2, 2, 2, 2, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, + 1, 4, 4, 4, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1 }; @@ -2717,7 +2721,7 @@ namespace yy { "\"null\"", "\"==\"", "\"!=\"", "\"<\"", "\">\"", "\">=\"", "\"<=\"", "\"[c]\"", "\"any\"", "\"all\"", "\"none\"", "\"@links\"", "\"@max\"", "\"@min\"", "\"@sun\"", "\"@average\"", "\"&&\"", "\"||\"", "\"!\"", - "\"geobox\"", "\"geopolygon\"", "\"geosphere\"", "\"identifier\"", + "\"geobox\"", "\"geopolygon\"", "\"geocircle\"", "\"identifier\"", "\"string\"", "\"base64\"", "\"infinity\"", "\"NaN\"", "\"natural0\"", "\"number\"", "\"float\"", "\"date\"", "\"UUID\"", "\"ObjectId\"", "\"link\"", "\"typed link\"", "\"argument\"", "\"beginswith\"", @@ -2744,16 +2748,16 @@ namespace yy { 0, 187, 187, 190, 191, 192, 193, 194, 195, 198, 199, 204, 205, 206, 207, 212, 213, 214, 217, 218, 219, 220, 221, 222, 225, 226, 227, 228, 229, 232, - 233, 236, 240, 246, 249, 252, 253, 256, 257, 260, - 261, 263, 266, 267, 270, 271, 272, 275, 276, 277, - 278, 280, 283, 284, 286, 289, 290, 292, 295, 296, - 298, 299, 302, 303, 304, 307, 308, 309, 310, 311, - 312, 313, 314, 315, 316, 317, 318, 319, 320, 328, - 329, 330, 331, 332, 335, 336, 339, 340, 341, 344, - 345, 346, 349, 350, 351, 352, 355, 356, 357, 360, - 361, 362, 363, 366, 367, 368, 369, 372, 373, 376, - 377, 378, 379, 382, 383, 384, 385, 386, 387, 388, - 389, 390, 391, 392, 393, 394, 395, 396 + 233, 236, 240, 246, 249, 252, 253, 254, 257, 258, + 261, 262, 264, 267, 268, 271, 272, 273, 276, 277, + 278, 279, 281, 284, 285, 287, 290, 291, 293, 296, + 297, 299, 300, 303, 304, 305, 308, 309, 310, 311, + 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, + 329, 330, 331, 332, 333, 336, 337, 340, 341, 342, + 345, 346, 347, 350, 351, 352, 353, 356, 357, 358, + 361, 362, 363, 364, 367, 368, 369, 370, 373, 374, + 377, 378, 379, 380, 383, 384, 385, 386, 387, 388, + 389, 390, 391, 392, 393, 394, 395, 396, 397 }; void diff --git a/src/realm/parser/generated/query_bison.hpp b/src/realm/parser/generated/query_bison.hpp index 6ad701c0822..84fe32a2c23 100644 --- a/src/realm/parser/generated/query_bison.hpp +++ b/src/realm/parser/generated/query_bison.hpp @@ -621,7 +621,7 @@ namespace yy { TOK_NOT = 281, // "!" TOK_GEOBOX = 282, // "geobox" TOK_GEOPOLYGON = 283, // "geopolygon" - TOK_GEOSPHERE = 284, // "geosphere" + TOK_GEOCIRCLE = 284, // "geocircle" TOK_ID = 285, // "identifier" TOK_STRING = 286, // "string" TOK_BASE64 = 287, // "base64" @@ -700,7 +700,7 @@ namespace yy { SYM_NOT = 26, // "!" SYM_GEOBOX = 27, // "geobox" SYM_GEOPOLYGON = 28, // "geopolygon" - SYM_GEOSPHERE = 29, // "geosphere" + SYM_GEOCIRCLE = 29, // "geocircle" SYM_ID = 30, // "identifier" SYM_STRING = 31, // "string" SYM_BASE64 = 32, // "base64" @@ -1467,7 +1467,7 @@ switch (yykind) { #if !defined _MSC_VER || defined __clang__ YY_ASSERT (tok == token::TOK_END - || (token::TOK_YYerror <= tok && tok <= token::TOK_GEOSPHERE) + || (token::TOK_YYerror <= tok && tok <= token::TOK_GEOCIRCLE) || tok == 43 || tok == 45 || tok == 42 @@ -1978,16 +1978,16 @@ switch (yykind) #if 201103L <= YY_CPLUSPLUS static symbol_type - make_GEOSPHERE () + make_GEOCIRCLE () { - return symbol_type (token::TOK_GEOSPHERE); + return symbol_type (token::TOK_GEOCIRCLE); } #else static symbol_type - make_GEOSPHERE () + make_GEOCIRCLE () { - return symbol_type (token::TOK_GEOSPHERE); + return symbol_type (token::TOK_GEOCIRCLE); } #endif #if 201103L <= YY_CPLUSPLUS @@ -2528,7 +2528,7 @@ switch (yykind) // YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM. // Performed when YYTABLE does not specify something else to do. Zero // means the default is an error. - static const signed char yydefact_[]; + static const unsigned char yydefact_[]; // YYPGOTO[NTERM-NUM]. static const short yypgoto_[]; diff --git a/src/realm/parser/generated/query_flex.cpp b/src/realm/parser/generated/query_flex.cpp index 7cc93b99d2f..a00bb565312 100644 --- a/src/realm/parser/generated/query_flex.cpp +++ b/src/realm/parser/generated/query_flex.cpp @@ -549,7 +549,7 @@ static const flex_int16_t yy_accept[418] = 66, 66, 66, 66, 66, 66, 0, 0, 0, 0, 66, 66, 20, 66, 28, 19, 66, 66, 66, 66, 49, 33, 66, 0, 0, 49, 0, 31, 66, 66, - 66, 66, 36, 24, 66, 0, 0, 0, 18, 32, + 66, 36, 66, 24, 66, 0, 0, 0, 18, 32, 66, 35, 66, 0, 0, 54, 66, 66, 0, 0, 0, 66, 66, 0, 0, 54, 66, 25, 0, 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -607,53 +607,53 @@ static const YY_CHAR yy_meta[87] = static const flex_int16_t yy_base[487] = { 0, - 0, 0, 798, 1947, 85, 781, 758, 82, 72, 764, - 85, 1947, 1947, 79, 83, 90, 111, 88, 92, 740, + 0, 0, 798, 1933, 85, 781, 758, 82, 72, 764, + 85, 1933, 1933, 79, 83, 90, 111, 88, 92, 740, 78, 109, 139, 120, 149, 134, 112, 154, 126, 168, 208, 195, 245, 215, 319, 705, 237, 257, 230, 267, 260, 330, 305, 323, 357, 342, 679, 677, 674, 673, - 116, 751, 1947, 122, 1947, 414, 275, 415, 172, 670, - 660, 655, 1947, 113, 1947, 441, 419, 435, 119, 159, - 443, 458, 487, 504, 513, 0, 1947, 1947, 1947, 1947, + 116, 751, 1933, 122, 1933, 414, 275, 415, 172, 670, + 660, 655, 1933, 113, 1933, 441, 419, 435, 119, 159, + 443, 458, 487, 504, 513, 0, 1933, 1933, 1933, 1933, 660, 664, 668, 650, 77, 113, 624, 647, 360, 485, 492, 427, 477, 506, 489, 282, 407, 513, 529, 541, 547, 551, 557, 601, 591, 576, 586, 500, 596, 646, 637, 621, 640, 655, 625, 536, 702, 705, 666, 682, 695, 647, 702, 717, 706, 724, 730, 751, 747, 721, - 1947, 743, 617, 609, 0, 605, 585, 0, 185, 191, - 676, 1947, 823, 827, 831, 835, 0, 596, 580, 575, + 1933, 743, 617, 609, 0, 605, 585, 0, 185, 191, + 676, 1933, 823, 827, 831, 835, 0, 596, 580, 575, 583, 572, 581, 567, 579, 575, 573, 777, 781, 786, 826, 806, 829, 820, 832, 866, 848, 855, 875, 882, - 920, 890, 856, 911, 928, 894, 900, 956, 904, 962, - 917, 968, 974, 979, 1015, 1053, 992, 997, 1031, 1947, - 1016, 1026, 1034, 1039, 1044, 1050, 555, 0, 528, 0, - - 214, 1947, 998, 1087, 1005, 1947, 540, 533, 540, 1947, - 1947, 545, 1947, 543, 526, 1073, 595, 1087, 1090, 1110, - 1100, 1114, 1128, 1137, 1148, 1166, 1174, 1140, 1156, 1177, - 1075, 1061, 1182, 1163, 1185, 1190, 1225, 1184, 1235, 1211, - 1231, 1235, 1252, 0, 1255, 0, 0, 215, 1261, 520, - 1947, 519, 1947, 1947, 527, 1269, 1947, 574, 1240, 1282, - 1265, 1290, 1293, 1299, 1316, 1319, 1307, 1327, 1336, 1342, - 1361, 1345, 1379, 1365, 0, 0, 0, 0, 223, 1271, - 1947, 486, 1371, 1384, 1408, 1413, 1422, 1425, 1438, 1432, - 1387, 1435, 1443, 1478, 1484, 1481, 1495, 0, 0, 221, - - 1524, 1947, 1507, 1518, 1499, 1504, 1542, 1548, 1552, 1556, - 1567, 1577, 1561, 1603, 1606, 1613, 0, 0, 223, 1423, - 1616, 1621, 1591, 1595, 1629, 1632, 1673, 1667, 1678, 1670, - 1658, 1681, 1715, 0, 0, 1947, 1727, 1687, 1707, 1728, - 1734, 1742, 1721, 1724, 1786, 0, 0, 1554, 1749, 1763, - 1792, 1769, 1776, 0, 0, 1845, 1803, 1822, 0, 0, - 1592, 1827, 1838, 0, 0, 1709, 1843, 1798, 0, 536, - 1832, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 916, 911, 856, 894, 928, 900, 904, 924, 919, 953, + 940, 945, 965, 974, 1014, 1048, 969, 988, 992, 1933, + 1011, 1022, 1018, 1026, 1031, 1034, 555, 0, 528, 0, + + 214, 1933, 1080, 1103, 1107, 1933, 540, 533, 540, 1933, + 1933, 545, 1933, 543, 526, 1089, 595, 1092, 1083, 1103, + 1100, 1117, 1113, 1140, 1151, 1110, 1155, 1164, 1174, 1178, + 1119, 1004, 1167, 1130, 1159, 1170, 1214, 1128, 1250, 1193, + 1223, 1230, 1235, 0, 1220, 0, 0, 215, 1266, 520, + 1933, 519, 1933, 1933, 527, 1244, 1933, 574, 1257, 1265, + 1282, 1271, 1292, 1299, 1311, 1316, 1286, 1335, 1306, 1328, + 1340, 1345, 1358, 1362, 0, 0, 0, 0, 223, 1418, + 1933, 486, 1370, 1379, 1400, 1405, 1419, 1422, 1413, 1397, + 1408, 1442, 1458, 1462, 1466, 1476, 1470, 0, 0, 221, + + 1513, 1933, 1484, 1510, 1487, 1513, 1522, 1530, 1525, 1559, + 1551, 1567, 1578, 1570, 1581, 1587, 0, 0, 223, 1561, + 1595, 1622, 1540, 1607, 1616, 1625, 1629, 1643, 1646, 1664, + 1636, 1652, 1659, 0, 0, 1933, 1733, 1672, 1706, 1709, + 1712, 1682, 1720, 1693, 1754, 0, 0, 1665, 1717, 1729, + 1758, 1733, 1772, 0, 0, 1802, 1781, 1795, 0, 0, + 1811, 1799, 1802, 0, 0, 1857, 1829, 1806, 0, 536, + 1810, 0, 0, 0, 0, 0, 0, 0, 0, 0, 534, 0, 0, 0, 0, 0, 0, 0, 0, 0, 526, 0, 0, 0, 0, 0, 0, 0, 0, 526, - 512, 1947, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 498, 1947, 1947, 1929, 1932, 1937, - 484, 481, 480, 478, 469, 1941, 468, 466, 465, 461, + 512, 1933, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 498, 1933, 1933, 1915, 1918, 1923, + 484, 481, 480, 478, 469, 1927, 468, 466, 465, 461, 445, 444, 425, 421, 420, 418, 417, 415, 408, 407, 398, 393, 392, 383, 381, 380, 378, 377, 376, 371, 370, 369, 367, 351, 350, 344, 343, 322, 321, 315, @@ -719,7 +719,7 @@ static const flex_int16_t yy_def[487] = 417, 417, 417, 417, 417, 417 } ; -static const flex_int16_t yy_nxt[2034] = +static const flex_int16_t yy_nxt[2020] = { 0, 4, 5, 6, 5, 7, 8, 9, 10, 11, 12, 12, 13, 14, 12, 14, 15, 13, 16, 17, 17, @@ -822,132 +822,130 @@ static const flex_int16_t yy_nxt[2034] = 59, 417, 417, 60, 61, 62, 220, 59, 417, 60, 61, 62, 60, 61, 62, 60, 61, 62, 59, 224, - 417, 417, 222, 221, 230, 417, 225, 59, 223, 417, + 417, 417, 222, 221, 232, 417, 225, 59, 223, 417, 417, 60, 61, 62, 59, 417, 417, 417, 60, 61, - 62, 232, 59, 417, 417, 417, 59, 226, 225, 60, - 61, 62, 59, 230, 417, 225, 59, 417, 60, 61, - 62, 227, 233, 59, 228, 60, 61, 62, 229, 59, - 232, 417, 59, 60, 61, 62, 226, 60, 61, 62, - 59, 417, 417, 60, 61, 62, 234, 60, 61, 62, - 227, 233, 417, 228, 60, 61, 62, 229, 160, 235, - - 60, 61, 62, 60, 61, 62, 417, 417, 59, 417, - 417, 60, 61, 62, 59, 204, 204, 204, 204, 236, - 59, 237, 249, 249, 249, 249, 59, 241, 235, 417, - 417, 59, 185, 185, 185, 185, 186, 240, 417, 60, - 61, 62, 417, 244, 59, 60, 61, 62, 236, 59, - 237, 60, 61, 62, 417, 417, 242, 60, 61, 62, - 225, 242, 60, 61, 62, 238, 240, 238, 59, 417, - 239, 239, 239, 239, 242, 60, 61, 62, 59, 417, - 60, 61, 62, 59, 417, 234, 59, 417, 417, 243, - 242, 59, 231, 231, 231, 231, 59, 417, 160, 60, - - 61, 62, 59, 241, 204, 204, 204, 204, 245, 60, - 61, 62, 256, 59, 60, 61, 62, 60, 61, 62, - 260, 417, 60, 61, 62, 59, 259, 60, 61, 62, - 262, 417, 417, 60, 61, 62, 261, 417, 417, 59, - 417, 256, 59, 417, 60, 61, 62, 142, 263, 260, - 417, 417, 59, 417, 417, 259, 60, 61, 62, 262, - 417, 417, 59, 417, 417, 261, 59, 265, 417, 417, - 60, 61, 62, 60, 61, 62, 264, 263, 266, 417, - 59, 269, 417, 60, 61, 62, 417, 417, 417, 59, - 270, 417, 59, 60, 61, 62, 266, 60, 61, 62, - - 59, 239, 239, 239, 239, 264, 267, 266, 59, 417, - 269, 60, 61, 62, 268, 59, 271, 417, 59, 270, - 60, 61, 62, 60, 61, 62, 59, 272, 417, 59, - 417, 60, 61, 62, 59, 267, 417, 59, 417, 60, - 61, 62, 59, 268, 417, 271, 60, 61, 62, 60, - 61, 62, 239, 239, 239, 239, 272, 60, 61, 62, - 60, 61, 62, 59, 276, 60, 61, 62, 60, 61, - 62, 273, 274, 60, 61, 62, 274, 59, 249, 249, - 249, 249, 266, 59, 284, 417, 280, 59, 301, 301, - 301, 301, 59, 417, 60, 61, 62, 417, 283, 286, - - 273, 274, 417, 417, 59, 274, 280, 59, 60, 61, - 62, 265, 285, 284, 60, 61, 62, 59, 60, 61, - 62, 59, 417, 60, 61, 62, 417, 283, 286, 287, - 417, 417, 288, 289, 59, 60, 61, 62, 60, 61, - 62, 285, 59, 417, 417, 59, 417, 417, 60, 61, - 62, 59, 60, 61, 62, 417, 291, 290, 287, 59, - 290, 288, 289, 417, 292, 60, 61, 62, 59, 293, - 417, 59, 417, 60, 61, 62, 60, 61, 62, 59, - 417, 417, 60, 61, 62, 291, 290, 294, 59, 290, - 60, 61, 62, 292, 59, 295, 417, 59, 293, 60, - - 61, 62, 60, 61, 62, 303, 417, 417, 297, 296, - 60, 61, 62, 59, 417, 417, 294, 59, 417, 60, - 61, 62, 417, 59, 295, 60, 61, 62, 60, 61, - 62, 59, 304, 417, 303, 417, 59, 297, 296, 59, - 337, 337, 337, 337, 60, 61, 62, 305, 60, 61, - 62, 307, 306, 308, 60, 61, 62, 417, 417, 417, - 59, 304, 60, 61, 62, 59, 417, 60, 61, 62, - 60, 61, 62, 312, 59, 310, 305, 59, 417, 417, - 307, 306, 308, 309, 59, 311, 417, 59, 417, 417, - 59, 60, 61, 62, 417, 59, 60, 61, 62, 417, - - 417, 417, 312, 417, 310, 60, 61, 62, 60, 61, - 62, 313, 309, 417, 311, 60, 61, 62, 60, 61, - 62, 60, 61, 62, 315, 316, 60, 61, 62, 314, - 59, 417, 417, 59, 417, 417, 59, 417, 417, 417, - 313, 301, 301, 301, 301, 320, 321, 59, 323, 417, - 417, 59, 322, 315, 316, 417, 59, 417, 314, 59, - 417, 60, 61, 62, 60, 61, 62, 60, 61, 62, - 59, 356, 356, 356, 356, 321, 324, 323, 60, 61, - 62, 322, 60, 61, 62, 326, 327, 60, 61, 62, - 60, 61, 62, 325, 59, 330, 417, 417, 417, 328, - - 59, 60, 61, 62, 59, 324, 417, 417, 59, 366, - 366, 366, 366, 59, 326, 327, 417, 417, 417, 59, - 329, 417, 325, 417, 330, 60, 61, 62, 328, 59, - 417, 60, 61, 62, 340, 60, 61, 62, 417, 60, - 61, 62, 333, 59, 60, 61, 62, 59, 338, 329, - 60, 61, 62, 331, 417, 59, 332, 417, 59, 417, - 60, 61, 62, 340, 417, 59, 339, 417, 59, 417, - 417, 333, 417, 59, 60, 61, 62, 338, 60, 61, - 62, 59, 331, 417, 59, 332, 60, 61, 62, 60, - 61, 62, 417, 417, 417, 339, 60, 61, 62, 60, - - 61, 62, 341, 417, 60, 61, 62, 342, 343, 344, - 59, 417, 60, 61, 62, 60, 61, 62, 417, 59, - 417, 417, 59, 417, 417, 59, 366, 366, 366, 366, - 59, 341, 417, 59, 417, 417, 342, 343, 344, 59, - 349, 60, 61, 62, 337, 337, 337, 337, 348, 345, - 60, 61, 62, 60, 61, 62, 60, 61, 62, 59, - 350, 60, 61, 62, 60, 61, 62, 59, 351, 349, - 60, 61, 62, 59, 417, 417, 59, 417, 345, 417, - 59, 352, 417, 417, 417, 417, 59, 417, 417, 350, - 60, 61, 62, 417, 59, 417, 417, 351, 60, 61, - - 62, 59, 358, 417, 60, 61, 62, 60, 61, 62, - 352, 60, 61, 62, 353, 59, 417, 60, 61, 62, - 357, 59, 417, 417, 417, 60, 61, 62, 59, 362, - 417, 358, 60, 61, 62, 417, 417, 417, 59, 417, - 417, 417, 417, 353, 59, 417, 60, 61, 62, 357, - 59, 417, 60, 61, 62, 59, 417, 417, 362, 60, - 61, 62, 356, 356, 356, 356, 361, 363, 368, 60, - 61, 62, 367, 371, 59, 60, 61, 62, 417, 59, - 417, 60, 61, 62, 59, 417, 60, 61, 62, 417, - 59, 417, 417, 417, 417, 59, 363, 368, 417, 417, - - 417, 367, 371, 417, 417, 60, 61, 62, 417, 417, - 60, 61, 62, 417, 417, 60, 61, 62, 417, 417, - 417, 60, 61, 62, 417, 417, 60, 61, 62, 54, - 54, 54, 54, 54, 57, 57, 57, 64, 64, 64, - 64, 64, 258, 417, 258, 258, 3, 417, 417, 417, + 62, 417, 417, 226, 227, 230, 59, 417, 225, 60, + 61, 62, 59, 232, 234, 225, 59, 228, 60, 61, + 62, 417, 233, 59, 229, 60, 61, 62, 59, 417, + 417, 59, 226, 227, 230, 160, 59, 60, 61, 62, + 59, 417, 417, 60, 61, 62, 228, 60, 61, 62, + 235, 233, 59, 229, 60, 61, 62, 59, 417, 60, + + 61, 62, 60, 61, 62, 59, 417, 60, 61, 62, + 236, 60, 61, 62, 240, 417, 237, 59, 241, 235, + 417, 59, 242, 60, 61, 62, 59, 244, 60, 61, + 62, 185, 185, 185, 185, 186, 60, 61, 62, 236, + 59, 417, 417, 240, 59, 237, 417, 242, 60, 61, + 62, 242, 60, 61, 62, 225, 59, 60, 61, 62, + 238, 242, 238, 59, 417, 239, 239, 239, 239, 417, + 59, 60, 61, 62, 59, 60, 61, 62, 59, 417, + 417, 234, 417, 59, 243, 160, 59, 60, 61, 62, + 241, 417, 245, 417, 60, 61, 62, 204, 204, 204, + + 204, 60, 61, 62, 417, 60, 61, 62, 417, 60, + 61, 62, 417, 260, 60, 61, 62, 60, 61, 62, + 204, 204, 204, 204, 249, 249, 249, 249, 256, 261, + 262, 259, 417, 417, 417, 59, 231, 231, 231, 231, + 417, 59, 260, 417, 59, 239, 239, 239, 239, 417, + 267, 263, 59, 417, 417, 59, 417, 256, 261, 262, + 259, 264, 59, 142, 417, 59, 60, 61, 62, 59, + 265, 417, 60, 61, 62, 60, 61, 62, 417, 267, + 263, 266, 59, 60, 61, 62, 60, 61, 62, 268, + 264, 417, 59, 60, 61, 62, 60, 61, 62, 266, + + 60, 61, 62, 59, 269, 417, 417, 59, 270, 417, + 266, 59, 272, 60, 61, 62, 59, 271, 268, 59, + 417, 417, 59, 60, 61, 62, 59, 417, 417, 276, + 59, 417, 417, 269, 60, 61, 62, 270, 60, 61, + 62, 272, 60, 61, 62, 59, 271, 60, 61, 62, + 60, 61, 62, 60, 61, 62, 417, 60, 61, 62, + 273, 60, 61, 62, 274, 266, 59, 239, 239, 239, + 239, 274, 59, 283, 417, 59, 60, 61, 62, 417, + 417, 417, 59, 249, 249, 249, 249, 59, 417, 273, + 417, 280, 417, 274, 265, 285, 59, 60, 61, 62, + + 274, 284, 283, 60, 61, 62, 60, 61, 62, 59, + 287, 280, 417, 60, 61, 62, 286, 59, 60, 61, + 62, 417, 417, 59, 285, 417, 417, 60, 61, 62, + 284, 288, 417, 289, 59, 291, 417, 417, 59, 287, + 60, 61, 62, 293, 59, 286, 417, 417, 60, 61, + 62, 59, 290, 417, 60, 61, 62, 290, 59, 417, + 288, 417, 289, 59, 291, 60, 61, 62, 59, 60, + 61, 62, 293, 294, 295, 60, 61, 62, 292, 417, + 59, 290, 60, 61, 62, 417, 290, 59, 296, 60, + 61, 62, 59, 417, 60, 61, 62, 59, 417, 60, + + 61, 62, 294, 295, 303, 297, 417, 292, 417, 417, + 59, 60, 61, 62, 59, 417, 417, 296, 60, 61, + 62, 417, 59, 60, 61, 62, 417, 304, 60, 61, + 62, 59, 417, 303, 297, 301, 301, 301, 301, 305, + 310, 60, 61, 62, 306, 60, 61, 62, 307, 59, + 308, 417, 59, 60, 61, 62, 304, 59, 309, 417, + 59, 417, 60, 61, 62, 59, 417, 417, 305, 310, + 311, 59, 417, 306, 59, 417, 417, 307, 417, 308, + 60, 61, 62, 60, 61, 62, 417, 309, 60, 61, + 62, 60, 61, 62, 59, 313, 60, 61, 62, 311, + + 316, 417, 60, 61, 62, 60, 61, 62, 312, 417, + 59, 314, 417, 417, 59, 417, 417, 417, 59, 315, + 417, 417, 59, 321, 313, 60, 61, 62, 59, 316, + 301, 301, 301, 301, 320, 417, 59, 312, 417, 59, + 314, 60, 61, 62, 322, 60, 61, 62, 315, 60, + 61, 62, 321, 60, 61, 62, 324, 323, 326, 60, + 61, 62, 59, 417, 417, 59, 417, 60, 61, 62, + 60, 61, 62, 322, 59, 325, 417, 59, 337, 337, + 337, 337, 59, 417, 417, 324, 323, 326, 328, 327, + 417, 417, 59, 60, 61, 62, 60, 61, 62, 329, + + 417, 417, 417, 59, 325, 60, 61, 62, 60, 61, + 62, 59, 330, 60, 61, 62, 333, 328, 327, 59, + 331, 417, 59, 60, 61, 62, 417, 338, 329, 417, + 59, 332, 417, 59, 60, 61, 62, 417, 417, 59, + 417, 330, 60, 61, 62, 333, 340, 59, 417, 331, + 60, 61, 62, 60, 61, 62, 338, 417, 341, 59, + 332, 60, 61, 62, 60, 61, 62, 339, 59, 417, + 60, 61, 62, 342, 59, 340, 417, 59, 60, 61, + 62, 59, 356, 356, 356, 356, 343, 341, 59, 417, + 60, 61, 62, 345, 417, 59, 339, 417, 59, 60, + + 61, 62, 342, 344, 59, 60, 61, 62, 60, 61, + 62, 59, 60, 61, 62, 343, 59, 417, 417, 60, + 61, 62, 345, 417, 59, 417, 60, 61, 62, 60, + 61, 62, 344, 417, 59, 60, 61, 62, 417, 349, + 417, 350, 60, 61, 62, 59, 351, 60, 61, 62, + 337, 337, 337, 337, 348, 60, 61, 62, 59, 352, + 417, 59, 417, 417, 59, 60, 61, 62, 349, 59, + 350, 417, 59, 417, 417, 351, 60, 61, 62, 417, + 417, 59, 353, 417, 417, 59, 357, 417, 352, 60, + 61, 62, 60, 61, 62, 60, 61, 62, 358, 417, + + 60, 61, 62, 60, 61, 62, 59, 362, 417, 417, + 59, 353, 60, 61, 62, 357, 60, 61, 62, 356, + 356, 356, 356, 361, 59, 417, 417, 358, 366, 366, + 366, 366, 368, 59, 417, 417, 362, 60, 61, 62, + 363, 60, 61, 62, 367, 417, 417, 59, 417, 417, + 417, 59, 417, 417, 59, 60, 61, 62, 59, 371, + 417, 368, 59, 417, 60, 61, 62, 417, 417, 363, + 417, 417, 417, 367, 366, 366, 366, 366, 60, 61, + 62, 59, 60, 61, 62, 60, 61, 62, 371, 60, + 61, 62, 417, 60, 61, 62, 417, 417, 417, 417, + 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, + 417, 417, 60, 61, 62, 54, 54, 54, 54, 54, + 57, 57, 57, 64, 64, 64, 64, 64, 258, 417, + 258, 258, 3, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, - 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, + 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, - 417, 417, 417 + 417, 417, 417, 417, 417, 417, 417, 417, 417 } ; -static const flex_int16_t yy_chk[2034] = +static const flex_int16_t yy_chk[2020] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -1050,129 +1048,127 @@ static const flex_int16_t yy_chk[2034] = 167, 0, 0, 164, 164, 164, 165, 168, 0, 161, 161, 161, 163, 163, 163, 165, 165, 165, 166, 169, - 0, 0, 167, 166, 172, 0, 170, 169, 168, 0, + 0, 0, 167, 166, 174, 0, 170, 169, 168, 0, 0, 167, 167, 167, 170, 0, 0, 0, 168, 168, - 168, 174, 172, 0, 0, 0, 176, 171, 169, 166, - 166, 166, 177, 172, 0, 170, 179, 0, 169, 169, - 169, 171, 175, 174, 171, 170, 170, 170, 171, 181, - 174, 0, 171, 172, 172, 172, 171, 176, 176, 176, - 175, 0, 0, 177, 177, 177, 178, 179, 179, 179, - 171, 175, 0, 171, 174, 174, 174, 171, 182, 180, - - 181, 181, 181, 171, 171, 171, 0, 0, 178, 0, - 0, 175, 175, 175, 180, 203, 203, 203, 203, 183, - 182, 184, 205, 205, 205, 205, 183, 188, 180, 0, - 0, 184, 185, 185, 185, 185, 185, 187, 0, 178, - 178, 178, 0, 193, 187, 180, 180, 180, 183, 188, - 184, 182, 182, 182, 0, 0, 188, 183, 183, 183, - 191, 189, 184, 184, 184, 186, 187, 186, 191, 0, - 186, 186, 186, 186, 195, 187, 187, 187, 192, 0, - 188, 188, 188, 189, 0, 192, 193, 0, 0, 191, - 189, 194, 231, 231, 231, 231, 195, 0, 194, 191, - - 191, 191, 196, 195, 204, 204, 204, 204, 196, 192, - 192, 192, 216, 232, 189, 189, 189, 193, 193, 193, - 219, 0, 194, 194, 194, 216, 218, 195, 195, 195, - 221, 0, 0, 196, 196, 196, 220, 0, 0, 218, - 0, 216, 219, 0, 232, 232, 232, 204, 222, 219, - 0, 0, 221, 0, 0, 218, 216, 216, 216, 221, - 0, 0, 220, 0, 0, 220, 222, 224, 0, 0, - 218, 218, 218, 219, 219, 219, 223, 222, 225, 0, - 223, 228, 0, 221, 221, 221, 0, 0, 0, 224, - 229, 0, 228, 220, 220, 220, 224, 222, 222, 222, - - 225, 238, 238, 238, 238, 223, 226, 225, 229, 0, - 228, 223, 223, 223, 227, 234, 230, 0, 226, 229, - 224, 224, 224, 228, 228, 228, 227, 233, 0, 230, - 0, 225, 225, 225, 233, 226, 0, 235, 0, 229, - 229, 229, 236, 227, 0, 230, 234, 234, 234, 226, - 226, 226, 239, 239, 239, 239, 233, 227, 227, 227, - 230, 230, 230, 240, 245, 233, 233, 233, 235, 235, - 235, 237, 241, 236, 236, 236, 242, 237, 249, 249, - 249, 249, 243, 241, 259, 0, 249, 242, 280, 280, - 280, 280, 259, 0, 240, 240, 240, 0, 256, 261, - - 237, 241, 0, 0, 243, 242, 249, 245, 237, 237, - 237, 243, 260, 259, 241, 241, 241, 261, 242, 242, - 242, 256, 0, 259, 259, 259, 0, 256, 261, 262, - 0, 0, 263, 264, 260, 243, 243, 243, 245, 245, - 245, 260, 262, 0, 0, 263, 0, 0, 261, 261, - 261, 264, 256, 256, 256, 0, 267, 265, 262, 267, - 266, 263, 264, 0, 268, 260, 260, 260, 265, 269, - 0, 266, 0, 262, 262, 262, 263, 263, 263, 268, - 0, 0, 264, 264, 264, 267, 265, 270, 269, 266, - 267, 267, 267, 268, 270, 271, 0, 272, 269, 265, - - 265, 265, 266, 266, 266, 283, 0, 0, 274, 273, - 268, 268, 268, 271, 0, 0, 270, 274, 0, 269, - 269, 269, 0, 283, 271, 270, 270, 270, 272, 272, - 272, 273, 284, 0, 283, 0, 284, 274, 273, 291, - 320, 320, 320, 320, 271, 271, 271, 285, 274, 274, - 274, 287, 286, 288, 283, 283, 283, 0, 0, 0, - 285, 284, 273, 273, 273, 286, 0, 284, 284, 284, - 291, 291, 291, 293, 287, 290, 285, 288, 0, 0, - 287, 286, 288, 289, 290, 292, 0, 292, 0, 0, - 289, 285, 285, 285, 0, 293, 286, 286, 286, 0, - - 0, 0, 293, 0, 290, 287, 287, 287, 288, 288, - 288, 294, 289, 0, 292, 290, 290, 290, 292, 292, - 292, 289, 289, 289, 296, 297, 293, 293, 293, 295, - 294, 0, 0, 296, 0, 0, 295, 0, 0, 0, - 294, 301, 301, 301, 301, 301, 303, 297, 306, 0, - 0, 305, 304, 296, 297, 0, 306, 0, 295, 303, - 0, 294, 294, 294, 296, 296, 296, 295, 295, 295, - 304, 348, 348, 348, 348, 303, 307, 306, 297, 297, - 297, 304, 305, 305, 305, 309, 310, 306, 306, 306, - 303, 303, 303, 308, 307, 313, 0, 0, 0, 311, - - 308, 304, 304, 304, 309, 307, 0, 0, 310, 361, - 361, 361, 361, 313, 309, 310, 0, 0, 0, 311, - 312, 0, 308, 0, 313, 307, 307, 307, 311, 312, - 0, 308, 308, 308, 324, 309, 309, 309, 0, 310, - 310, 310, 316, 323, 313, 313, 313, 324, 321, 312, - 311, 311, 311, 314, 0, 314, 315, 0, 315, 0, - 312, 312, 312, 324, 0, 316, 322, 0, 321, 0, - 0, 316, 0, 322, 323, 323, 323, 321, 324, 324, - 324, 325, 314, 0, 326, 315, 314, 314, 314, 315, - 315, 315, 0, 0, 0, 322, 316, 316, 316, 321, - - 321, 321, 327, 0, 322, 322, 322, 328, 329, 330, - 331, 0, 325, 325, 325, 326, 326, 326, 0, 328, - 0, 0, 330, 0, 0, 327, 366, 366, 366, 366, - 329, 327, 0, 332, 0, 0, 328, 329, 330, 338, - 339, 331, 331, 331, 337, 337, 337, 337, 337, 333, - 328, 328, 328, 330, 330, 330, 327, 327, 327, 339, - 340, 329, 329, 329, 332, 332, 332, 333, 341, 339, - 338, 338, 338, 343, 0, 0, 344, 0, 333, 0, - 340, 342, 0, 0, 0, 0, 341, 0, 0, 340, - 339, 339, 339, 0, 342, 0, 0, 341, 333, 333, - - 333, 349, 353, 0, 343, 343, 343, 344, 344, 344, - 342, 340, 340, 340, 345, 350, 0, 341, 341, 341, - 351, 352, 0, 0, 0, 342, 342, 342, 353, 357, - 0, 353, 349, 349, 349, 0, 0, 0, 345, 0, - 0, 0, 0, 345, 351, 0, 350, 350, 350, 351, - 368, 0, 352, 352, 352, 357, 0, 0, 357, 353, - 353, 353, 356, 356, 356, 356, 356, 358, 363, 345, - 345, 345, 362, 367, 358, 351, 351, 351, 0, 362, - 0, 368, 368, 368, 371, 0, 357, 357, 357, 0, - 363, 0, 0, 0, 0, 367, 358, 363, 0, 0, - - 0, 362, 367, 0, 0, 358, 358, 358, 0, 0, - 362, 362, 362, 0, 0, 371, 371, 371, 0, 0, - 0, 363, 363, 363, 0, 0, 367, 367, 367, 418, - 418, 418, 418, 418, 419, 419, 419, 420, 420, 420, - 420, 420, 426, 0, 426, 426, 417, 417, 417, 417, - 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, + 168, 0, 0, 171, 171, 172, 174, 0, 169, 166, + 166, 166, 176, 174, 178, 170, 177, 171, 169, 169, + 169, 0, 175, 172, 171, 170, 170, 170, 171, 0, + 0, 179, 171, 171, 172, 182, 178, 174, 174, 174, + 175, 0, 0, 176, 176, 176, 171, 177, 177, 177, + 180, 175, 181, 171, 172, 172, 172, 182, 0, 171, + + 171, 171, 179, 179, 179, 180, 0, 178, 178, 178, + 183, 175, 175, 175, 187, 0, 184, 183, 188, 180, + 0, 187, 189, 181, 181, 181, 184, 193, 182, 182, + 182, 185, 185, 185, 185, 185, 180, 180, 180, 183, + 188, 0, 0, 187, 189, 184, 0, 188, 183, 183, + 183, 189, 187, 187, 187, 191, 232, 184, 184, 184, + 186, 195, 186, 191, 0, 186, 186, 186, 186, 0, + 193, 188, 188, 188, 192, 189, 189, 189, 194, 0, + 0, 192, 0, 195, 191, 194, 196, 232, 232, 232, + 195, 0, 196, 0, 191, 191, 191, 203, 203, 203, + + 203, 193, 193, 193, 0, 192, 192, 192, 0, 194, + 194, 194, 0, 219, 195, 195, 195, 196, 196, 196, + 204, 204, 204, 204, 205, 205, 205, 205, 216, 220, + 221, 218, 0, 0, 0, 219, 231, 231, 231, 231, + 0, 216, 219, 0, 218, 238, 238, 238, 238, 0, + 226, 222, 221, 0, 0, 220, 0, 216, 220, 221, + 218, 223, 226, 204, 0, 223, 219, 219, 219, 222, + 224, 0, 216, 216, 216, 218, 218, 218, 0, 226, + 222, 225, 234, 221, 221, 221, 220, 220, 220, 227, + 223, 0, 224, 226, 226, 226, 223, 223, 223, 224, + + 222, 222, 222, 225, 228, 0, 0, 227, 229, 0, + 225, 235, 233, 234, 234, 234, 228, 230, 227, 233, + 0, 0, 236, 224, 224, 224, 229, 0, 0, 245, + 230, 0, 0, 228, 225, 225, 225, 229, 227, 227, + 227, 233, 235, 235, 235, 240, 230, 228, 228, 228, + 233, 233, 233, 236, 236, 236, 0, 229, 229, 229, + 237, 230, 230, 230, 241, 243, 237, 239, 239, 239, + 239, 242, 245, 256, 0, 241, 240, 240, 240, 0, + 0, 0, 242, 249, 249, 249, 249, 243, 0, 237, + 0, 249, 0, 241, 243, 260, 256, 237, 237, 237, + + 242, 259, 256, 245, 245, 245, 241, 241, 241, 259, + 262, 249, 0, 242, 242, 242, 261, 260, 243, 243, + 243, 0, 0, 262, 260, 0, 0, 256, 256, 256, + 259, 263, 0, 264, 261, 267, 0, 0, 267, 262, + 259, 259, 259, 269, 263, 261, 0, 0, 260, 260, + 260, 264, 265, 0, 262, 262, 262, 266, 269, 0, + 263, 0, 264, 265, 267, 261, 261, 261, 266, 267, + 267, 267, 269, 270, 271, 263, 263, 263, 268, 0, + 270, 265, 264, 264, 264, 0, 266, 268, 273, 269, + 269, 269, 271, 0, 265, 265, 265, 272, 0, 266, + + 266, 266, 270, 271, 283, 274, 0, 268, 0, 0, + 273, 270, 270, 270, 274, 0, 0, 273, 268, 268, + 268, 0, 283, 271, 271, 271, 0, 284, 272, 272, + 272, 284, 0, 283, 274, 280, 280, 280, 280, 285, + 290, 273, 273, 273, 286, 274, 274, 274, 287, 290, + 288, 0, 285, 283, 283, 283, 284, 286, 289, 0, + 291, 0, 284, 284, 284, 289, 0, 0, 285, 290, + 292, 287, 0, 286, 288, 0, 0, 287, 0, 288, + 290, 290, 290, 285, 285, 285, 0, 289, 286, 286, + 286, 291, 291, 291, 292, 294, 289, 289, 289, 292, + + 297, 0, 287, 287, 287, 288, 288, 288, 293, 0, + 293, 295, 0, 0, 294, 0, 0, 0, 295, 296, + 0, 0, 297, 303, 294, 292, 292, 292, 296, 297, + 301, 301, 301, 301, 301, 0, 303, 293, 0, 305, + 295, 293, 293, 293, 304, 294, 294, 294, 296, 295, + 295, 295, 303, 297, 297, 297, 307, 306, 309, 296, + 296, 296, 304, 0, 0, 306, 0, 303, 303, 303, + 305, 305, 305, 304, 307, 308, 0, 309, 320, 320, + 320, 320, 308, 0, 0, 307, 306, 309, 311, 310, + 0, 0, 323, 304, 304, 304, 306, 306, 306, 312, + + 0, 0, 0, 311, 308, 307, 307, 307, 309, 309, + 309, 310, 313, 308, 308, 308, 316, 311, 310, 312, + 314, 0, 314, 323, 323, 323, 0, 321, 312, 0, + 313, 315, 0, 315, 311, 311, 311, 0, 0, 316, + 0, 313, 310, 310, 310, 316, 324, 321, 0, 314, + 312, 312, 312, 314, 314, 314, 321, 0, 327, 324, + 315, 313, 313, 313, 315, 315, 315, 322, 325, 0, + 316, 316, 316, 328, 322, 324, 0, 326, 321, 321, + 321, 327, 348, 348, 348, 348, 329, 327, 331, 0, + 324, 324, 324, 333, 0, 328, 322, 0, 329, 325, + + 325, 325, 328, 330, 332, 322, 322, 322, 326, 326, + 326, 333, 327, 327, 327, 329, 330, 0, 0, 331, + 331, 331, 333, 0, 338, 0, 328, 328, 328, 329, + 329, 329, 330, 0, 342, 332, 332, 332, 0, 339, + 0, 340, 333, 333, 333, 344, 341, 330, 330, 330, + 337, 337, 337, 337, 337, 338, 338, 338, 339, 343, + 0, 340, 0, 0, 341, 342, 342, 342, 339, 349, + 340, 0, 343, 0, 0, 341, 344, 344, 344, 0, + 0, 350, 345, 0, 0, 352, 351, 0, 343, 339, + 339, 339, 340, 340, 340, 341, 341, 341, 353, 0, + + 349, 349, 349, 343, 343, 343, 345, 357, 0, 0, + 351, 345, 350, 350, 350, 351, 352, 352, 352, 356, + 356, 356, 356, 356, 353, 0, 0, 353, 361, 361, + 361, 361, 363, 357, 0, 0, 357, 345, 345, 345, + 358, 351, 351, 351, 362, 0, 0, 358, 0, 0, + 0, 362, 0, 0, 363, 353, 353, 353, 368, 367, + 0, 363, 371, 0, 357, 357, 357, 0, 0, 358, + 0, 0, 0, 362, 366, 366, 366, 366, 358, 358, + 358, 367, 362, 362, 362, 363, 363, 363, 367, 368, + 368, 368, 0, 371, 371, 371, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 367, 367, 367, 418, 418, 418, 418, 418, + 419, 419, 419, 420, 420, 420, 420, 420, 426, 0, + 426, 426, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, - 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, + 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, - 417, 417, 417 + 417, 417, 417, 417, 417, 417, 417, 417, 417 } ; static const flex_int16_t yy_rule_linenum[68] = @@ -1842,7 +1838,7 @@ return yy::parser::make_GEOPOLYGON(); YY_BREAK case 36: YY_RULE_SETUP -return yy::parser::make_GEOSPHERE(); +return yy::parser::make_GEOCIRCLE(); YY_BREAK case 37: YY_RULE_SETUP diff --git a/src/realm/parser/query_bison.yy b/src/realm/parser/query_bison.yy index a10a3fe2ccd..810733ae4f1 100644 --- a/src/realm/parser/query_bison.yy +++ b/src/realm/parser/query_bison.yy @@ -101,7 +101,7 @@ using namespace realm::query_parser; NOT "!" GEOBOX "geobox" GEOPOLYGON "geopolygon" - GEOSPHERE "geosphere" + GEOCIRCLE "geocircle" ; %token ID "identifier" @@ -251,6 +251,7 @@ subquery coordinate : FLOAT { $$ = strtod($1.c_str(), nullptr); } | NATURAL0 { $$ = double(strtoll($1.c_str(), nullptr, 0)); } + | ARG { $$ = drv.get_arg_for_coordinate($1); } geopoint : '[' coordinate ',' coordinate ']' { $$ = GeoPoint{$2, $4}; } @@ -268,7 +269,7 @@ geopoly_content geospatial : GEOBOX '(' geopoint ',' geopoint ')' { $$ = drv.m_parse_nodes.create(GeospatialNode::Box{}, *$3, *$5); } - | GEOSPHERE '(' geopoint ',' coordinate ')' { $$ = drv.m_parse_nodes.create(GeospatialNode::Sphere{}, *$3, $5); } + | GEOCIRCLE '(' geopoint ',' coordinate ')' { $$ = drv.m_parse_nodes.create(GeospatialNode::Circle{}, *$3, $5); } | GEOPOLYGON '(' geopoly_content ')' { $$ = $3; } post_query diff --git a/src/realm/parser/query_flex.ll b/src/realm/parser/query_flex.ll index f5bb595cc8c..313d0c10ae0 100644 --- a/src/realm/parser/query_flex.ll +++ b/src/realm/parser/query_flex.ll @@ -65,7 +65,7 @@ blank [ \t\r] (?i:subquery) return yy::parser::make_SUBQUERY(); (?i:geobox) return yy::parser::make_GEOBOX(); (?i:geopolygon) return yy::parser::make_GEOPOLYGON(); -(?i:geosphere) return yy::parser::make_GEOSPHERE(); +(?i:geocircle) return yy::parser::make_GEOCIRCLE(); ("@size"|"@count") return yy::parser::make_SIZE(yytext); "@max" return yy::parser::make_MAX (); "@min" return yy::parser::make_MIN (); diff --git a/src/realm/query.cpp b/src/realm/query.cpp index 156ef5ee753..4222fc3fd26 100644 --- a/src/realm/query.cpp +++ b/src/realm/query.cpp @@ -19,7 +19,7 @@ #include #include -#include +#include #include #include #include @@ -27,7 +27,6 @@ #include #include #include -#include #include @@ -1056,6 +1055,20 @@ size_t Query::find_best_node(ParentNode* pn) const void Query::aggregate_internal(ParentNode* pn, QueryStateBase* st, size_t start, size_t end, ArrayPayload* source_column) const { + // Number of matches to find in best condition loop before breaking out to probe other conditions. Too low value + // gives too many constant time overheads everywhere in the query engine. Too high value makes it adapt less + // rapidly to changes in match frequencies. + constexpr size_t findlocals = 64; + + // Average match distance in linear searches where further increase in distance no longer increases query speed + // (because time spent on handling each match becomes insignificant compared to time spent on the search). + constexpr size_t bestdist = 512; + + // Minimum number of matches required in a certain condition before it can be used to compute statistics. Too high + // value can spent too much time in a bad node (with high match frequency). Too low value gives inaccurate + // statistics. + constexpr size_t probe_matches = 4; + while (start < end) { // Executes start...end range of a query and will stay inside the condition loop of the node it was called // on. Can be called on any node; yields same result, but different performance. Returns prematurely if diff --git a/src/realm/query.hpp b/src/realm/query.hpp index ef0f17e18b0..9648daf8161 100644 --- a/src/realm/query.hpp +++ b/src/realm/query.hpp @@ -35,28 +35,27 @@ #endif #include -#include -#include #include -#include +#include #include -#include +#include +#include #include -#include +#include namespace realm { // Pre-declarations -class ParentNode; -class Table; -class TableView; -class TableView; class Array; class Expression; class Group; -class Transaction; class LinkMap; +class ParentNode; +class Table; +class TableView; +class Timestamp; +class Transaction; namespace metrics { class QueryInfo; diff --git a/src/realm/query_engine.cpp b/src/realm/query_engine.cpp index 910bf2706d8..855709f4150 100644 --- a/src/realm/query_engine.cpp +++ b/src/realm/query_engine.cpp @@ -189,7 +189,7 @@ size_t MixedNode::find_first_local(size_t start, size_t end) return m_index_evaluator->do_search_index(m_cluster, start, end); } else { - return m_leaf_ptr->find_first(m_value, start, end); + return m_leaf->find_first(m_value, start, end); } return not_found; @@ -246,7 +246,7 @@ size_t MixedNode::find_first_local(size_t start, size_t end) EqualIns cond; if (m_value.is_type(type_String)) { for (size_t i = start; i < end; i++) { - QueryValue val(m_leaf_ptr->get(i)); + QueryValue val(m_leaf->get(i)); StringData val_as_str; if (val.is_type(type_String)) { val_as_str = val.get(); @@ -261,7 +261,7 @@ size_t MixedNode::find_first_local(size_t start, size_t end) } else { for (size_t i = start; i < end; i++) { - QueryValue val(m_leaf_ptr->get(i)); + QueryValue val(m_leaf->get(i)); if (cond(val, m_value)) return i; } @@ -416,13 +416,13 @@ bool StringNode::do_consume_condition(ParentNode& node) size_t StringNode::_find_first_local(size_t start, size_t end) { if (m_needles.empty()) { - return m_leaf_ptr->find_first(m_value, start, end); + return m_leaf->find_first(m_value, start, end); } else { if (end == npos) - end = m_leaf_ptr->size(); + end = m_leaf->size(); REALM_ASSERT_3(start, <=, end); - return find_first_haystack<20>(*m_leaf_ptr, m_needles, start, end); + return find_first_haystack<20>(*m_leaf, m_needles, start, end); } } @@ -826,7 +826,7 @@ size_t LinksToNode::find_first_local(size_t start, size_t end) BPlusTree links(m_table.unchecked_ptr()->get_alloc()); for (size_t i = start; i < end; i++) { - if (ref_type ref = LinkMap::get_ref(m_leaf_ptr, m_column_type, i)) { + if (ref_type ref = get_ref(i)) { links.init_from_ref(ref); for (auto& key : m_target_keys) { if (key) { @@ -837,9 +837,9 @@ size_t LinksToNode::find_first_local(size_t start, size_t end) } } } - else if (m_column_type == col_type_Link) { + else if (m_list) { for (auto& key : m_target_keys) { - auto pos = static_cast(m_leaf_ptr)->find_first(key, start, end); + auto pos = m_list->find_first(key, start, end); if (pos != realm::npos) { return pos; } @@ -859,7 +859,7 @@ size_t LinksToNode::find_first_local(size_t start, size_t end) if (m_column_type == col_type_LinkList || m_condition_column_key.is_set()) { BPlusTree links(m_table.unchecked_ptr()->get_alloc()); for (size_t i = start; i < end; i++) { - if (ref_type ref = LinkMap::get_ref(m_leaf_ptr, m_column_type, i)) { + if (ref_type ref = get_ref(i)) { links.init_from_ref(ref); auto sz = links.size(); for (size_t j = 0; j < sz; j++) { @@ -870,10 +870,9 @@ size_t LinksToNode::find_first_local(size_t start, size_t end) } } } - else if (m_column_type == col_type_Link) { - auto leaf = static_cast(m_leaf_ptr); + else if (m_list) { for (size_t i = start; i < end; i++) { - if (leaf->get(i) != key) { + if (m_list->get(i) != key) { return i; } } diff --git a/src/realm/query_engine.hpp b/src/realm/query_engine.hpp index 33645154646..af97159c359 100644 --- a/src/realm/query_engine.hpp +++ b/src/realm/query_engine.hpp @@ -61,24 +61,6 @@ Template arguments in methods: TConditionFunction: Each node has a condition from query_conditions.c such as Equal, GreaterEqual, etc TConditionValue: Type of values in condition column. That is, int64_t, float, int, bool, etc - -TAction: What to do with each search result, from the enums act_ReturnFirst, act_Count, act_Sum, etc - -TResult: Type of result of actions - float, double, int64_t, etc. Special notes: For act_Count it's - int64_t, for RLM_FIND_ALL it's int64_t which points at destination array. - -TSourceColumn: Type of source column used in actions, or *ignored* if no source column is used (like for - act_Count, act_ReturnFirst) - - -There are two important classes used in queries: ----------------------------------------------------------------------------------------------------- -SequentialGetter Column iterator used to get successive values with leaf caching. Used both for condition columns - and aggregate source column - -AggregateState State of the aggregate - contains a state variable that stores intermediate sum, max, min, - etc, etc. - */ #ifndef REALM_QUERY_ENGINE_HPP @@ -124,24 +106,6 @@ namespace realm { class IndexEvaluator; -// Number of matches to find in best condition loop before breaking out to probe other conditions. Too low value gives -// too many constant time overheads everywhere in the query engine. Too high value makes it adapt less rapidly to -// changes in match frequencies. -const size_t findlocals = 64; - -// Average match distance in linear searches where further increase in distance no longer increases query speed -// (because time spent on handling each match becomes insignificant compared to time spent on the search). -const size_t bestdist = 512; - -// Minimum number of matches required in a certain condition before it can be used to compute statistics. Too high -// value can spent too much time in a bad node (with high match frequency). Too low value gives inaccurate statistics. -const size_t probe_matches = 4; - -const size_t bitwidth_time_unit = 64; - -typedef bool (*CallbackDummy)(int64_t); -using Evaluator = util::FunctionRef; - class ParentNode { typedef ParentNode ThisType; @@ -174,6 +138,7 @@ class ParentNode { double cost() const { + constexpr size_t bitwidth_time_unit = 64; // dt = 1/64 to 1. Match dist is 8 times more important than bitwidth return 8 * bitwidth_time_unit / m_dD + m_dT; } @@ -341,9 +306,6 @@ class ColumnNodeBase : public ParentNode { class IndexEvaluator { public: - IndexEvaluator() {} - virtual ~IndexEvaluator() = default; - void init(StringIndex* index, Mixed value); void init(std::vector* storage); @@ -362,7 +324,7 @@ class IndexEvaluator { } private: - inline ObjKey get_internal(size_t ndx) const + ObjKey get_internal(size_t ndx) const { if (m_matching_keys) { return m_matching_keys->at(ndx); @@ -391,7 +353,6 @@ template class IntegerNodeBase : public ColumnNodeBase { public: using TConditionValue = typename LeafType::value_type; - // static const bool nullable = ColType::nullable; protected: IntegerNodeBase(TConditionValue value, ColKey column_key) @@ -408,15 +369,8 @@ class IntegerNodeBase : public ColumnNodeBase { void cluster_changed() override { - // Assigning nullptr will cause the Leaf destructor to be called. Must - // be done before assigning a new one. Otherwise the destructor will be - // called after the constructor is called and that is unfortunate if - // the object has the same address. (As in this case) - m_array_ptr = nullptr; - // Create new Leaf - m_array_ptr = LeafPtr(new (&m_leaf_cache_storage) LeafType(m_table.unchecked_ptr()->get_alloc())); - m_cluster->init_leaf(this->m_condition_column_key, m_array_ptr.get()); - m_leaf_ptr = m_array_ptr.get(); + m_leaf.emplace(m_table.unchecked_ptr()->get_alloc()); + m_cluster->init_leaf(this->m_condition_column_key, &*m_leaf); } void init(bool will_query_ranges) override @@ -432,21 +386,21 @@ class IntegerNodeBase : public ColumnNodeBase { return true; // Compare leafs to see if they are the same auto leaf = dynamic_cast(m_source_column); - return leaf ? leaf->get_ref() == m_leaf_ptr->get_ref() : false; + return leaf && leaf->get_ref() == m_leaf->get_ref(); } template size_t find_all_local(size_t start, size_t end) { if (run_single()) { - m_leaf_ptr->template find(m_value, start, end, m_state, nullptr); + m_leaf->template find(m_value, start, end, m_state, nullptr); } else { auto callback = [this](size_t index) { auto val = m_source_column->get_any(index); return m_state->match(index, val); }; - m_leaf_ptr->template find(m_value, start, end, m_state, callback); + m_leaf->template find(m_value, start, end, m_state, callback); } return end; @@ -463,11 +417,7 @@ class IntegerNodeBase : public ColumnNodeBase { TConditionValue m_value; // Leaf cache - using LeafCacheStorage = typename std::aligned_storage::type; - using LeafPtr = std::unique_ptr; - LeafCacheStorage m_leaf_cache_storage; - LeafPtr m_array_ptr; - const LeafType* m_leaf_ptr = nullptr; + std::optional m_leaf; }; @@ -490,7 +440,7 @@ class IntegerNode : public IntegerNodeBase { size_t find_first_local(size_t start, size_t end) override { - return this->m_leaf_ptr->template find_first(this->m_value, start, end); + return this->m_leaf->template find_first(this->m_value, start, end); } size_t find_all_local(size_t start, size_t end) override @@ -586,18 +536,18 @@ class IntegerNode : public IntegerNodeBase { if (start < end) { if (m_nb_needles) { - s = find_first_haystack<22>(*this->m_leaf_ptr, m_needles, start, end); + s = find_first_haystack<22>(*this->m_leaf, m_needles, start, end); } else if (m_index_evaluator) { return m_index_evaluator->do_search_index(BaseType::m_cluster, start, end); } else if (end - start == 1) { - if (this->m_leaf_ptr->get(start) == this->m_value) { + if (this->m_leaf->get(start) == this->m_value) { s = start; } } else { - s = this->m_leaf_ptr->template find_first(this->m_value, start, end); + s = this->m_leaf->template find_first(this->m_value, start, end); } } @@ -670,15 +620,8 @@ class FloatDoubleNode : public ParentNode { void cluster_changed() override { - // Assigning nullptr will cause the Leaf destructor to be called. Must - // be done before assigning a new one. Otherwise the destructor will be - // called after the constructor is called and that is unfortunate if - // the object has the same address. (As in this case) - m_array_ptr = nullptr; - // Create new Leaf - m_array_ptr = LeafPtr(new (&m_leaf_cache_storage) LeafType(m_table.unchecked_ptr()->get_alloc())); - m_cluster->init_leaf(this->m_condition_column_key, m_array_ptr.get()); - m_leaf_ptr = m_array_ptr.get(); + m_leaf.emplace(m_table.unchecked_ptr()->get_alloc()); + m_cluster->init_leaf(this->m_condition_column_key, &*m_leaf); } size_t find_first_local(size_t start, size_t end) override @@ -686,11 +629,11 @@ class FloatDoubleNode : public ParentNode { TConditionFunction cond; auto find = [&](bool nullability) { - bool m_value_nan = nullability ? null::is_null_float(m_value) : false; + bool value_nan = nullability ? null::is_null_float(m_value) : false; for (size_t s = start; s < end; ++s) { - TConditionValue v = m_leaf_ptr->get(s); + TConditionValue v = m_leaf->get(s); REALM_ASSERT(!(null::is_null_float(v) && !nullability)); - if (cond(v, m_value, nullability ? null::is_null_float(v) : false, m_value_nan)) + if (cond(v, m_value, nullability ? null::is_null_float(v) : false, value_nan)) return s; } return not_found; @@ -727,12 +670,7 @@ class FloatDoubleNode : public ParentNode { protected: TConditionValue m_value; - // Leaf cache - using LeafCacheStorage = typename std::aligned_storage::type; - using LeafPtr = std::unique_ptr; - LeafCacheStorage m_leaf_cache_storage; - LeafPtr m_array_ptr; - const LeafType* m_leaf_ptr = nullptr; + std::optional m_leaf; }; template @@ -747,21 +685,14 @@ class SizeNode : public ParentNode { void cluster_changed() override { - // Assigning nullptr will cause the Leaf destructor to be called. Must - // be done before assigning a new one. Otherwise the destructor will be - // called after the constructor is called and that is unfortunate if - // the object has the same address. (As in this case) - m_array_ptr = nullptr; - m_array_ptr = LeafPtr(new (&m_leaf_cache_storage) LeafType(m_table.unchecked_ptr()->get_alloc())); - m_cluster->init_leaf(this->m_condition_column_key, m_array_ptr.get()); - m_leaf_ptr = m_array_ptr.get(); + m_leaf.emplace(m_table.unchecked_ptr()->get_alloc()); + m_cluster->init_leaf(m_condition_column_key, &*m_leaf); } size_t find_first_local(size_t start, size_t end) override { for (size_t s = start; s < end; ++s) { - T v = m_leaf_ptr->get(s); - if (v) { + if (T v = m_leaf->get(s)) { int64_t sz = v.size(); if (TConditionFunction()(sz, m_value)) return s; @@ -782,14 +713,8 @@ class SizeNode : public ParentNode { } private: - // Leaf cache using LeafType = typename ColumnTypeTraits::cluster_leaf_type; - using LeafCacheStorage = typename std::aligned_storage::type; - using LeafPtr = std::unique_ptr; - LeafCacheStorage m_leaf_cache_storage; - LeafPtr m_array_ptr; - const LeafType* m_leaf_ptr = nullptr; - + std::optional m_leaf; int64_t m_value; }; @@ -814,14 +739,8 @@ class SizeListNode : public ParentNode { void cluster_changed() override { - // Assigning nullptr will cause the Leaf destructor to be called. Must - // be done before assigning a new one. Otherwise the destructor will be - // called after the constructor is called and that is unfortunate if - // the object has the same address. (As in this case) - m_array_ptr = nullptr; - m_array_ptr = LeafPtr(new (&m_leaf_cache_storage) ArrayList(m_table.unchecked_ptr()->get_alloc())); - m_cluster->init_leaf(this->m_condition_column_key, m_array_ptr.get()); - m_leaf_ptr = m_array_ptr.get(); + m_leaf.emplace(m_table.unchecked_ptr()->get_alloc()); + m_cluster->init_leaf(m_condition_column_key, &*m_leaf); reset_cache(); } @@ -835,8 +754,7 @@ class SizeListNode : public ParentNode { { Allocator& alloc = m_table.unchecked_ptr()->get_alloc(); for (size_t s = start; s < end; ++s) { - ref_type ref = m_leaf_ptr->get(s); - if (ref) { + if (ref_type ref = m_leaf->get(s)) { int64_t sz = size_of_list_from_ref(ref, alloc, m_cached_col_type, m_cached_nullable); if (TConditionFunction()(sz, m_value)) return s; @@ -857,12 +775,7 @@ class SizeListNode : public ParentNode { } private: - // Leaf cache - using LeafCacheStorage = typename std::aligned_storage::type; - using LeafPtr = std::unique_ptr; - LeafCacheStorage m_leaf_cache_storage; - LeafPtr m_array_ptr; - const ArrayList* m_leaf_ptr = nullptr; + std::optional m_leaf; int64_t m_value; @@ -891,24 +804,22 @@ class BinaryNode : public ParentNode { void cluster_changed() override { - m_array_ptr = nullptr; - m_array_ptr = LeafPtr(new (&m_leaf_cache_storage) ArrayBinary(m_table.unchecked_ptr()->get_alloc())); - m_cluster->init_leaf(this->m_condition_column_key, m_array_ptr.get()); - m_leaf_ptr = m_array_ptr.get(); + m_leaf.emplace(m_table.unchecked_ptr()->get_alloc()); + m_cluster->init_leaf(m_condition_column_key, &*m_leaf); } size_t find_first_local(size_t start, size_t end) override { TConditionFunction condition; for (size_t s = start; s < end; ++s) { - BinaryData value = m_leaf_ptr->get(s); + BinaryData value = m_leaf->get(s); if (condition(m_value.get(), value)) return s; } return not_found; } - virtual std::string describe(util::serializer::SerialisationState& state) const override + std::string describe(util::serializer::SerialisationState& state) const override { REALM_ASSERT(m_condition_column_key); return state.describe_column(ParentNode::m_table, m_condition_column_key) + " " + @@ -928,11 +839,7 @@ class BinaryNode : public ParentNode { private: OwnedBinaryData m_value; - using LeafCacheStorage = typename std::aligned_storage::type; - using LeafPtr = std::unique_ptr; - LeafCacheStorage m_leaf_cache_storage; - LeafPtr m_array_ptr; - const ArrayBinary* m_leaf_ptr = nullptr; + std::optional m_leaf; }; template @@ -986,10 +893,8 @@ class BoolNode : public ParentNode { void cluster_changed() override { - m_array_ptr = nullptr; - m_array_ptr = LeafPtr(new (&m_leaf_cache_storage) ArrayBoolNull(m_table.unchecked_ptr()->get_alloc())); - m_cluster->init_leaf(this->m_condition_column_key, m_array_ptr.get()); - m_leaf_ptr = m_array_ptr.get(); + m_leaf.emplace(m_table.unchecked_ptr()->get_alloc()); + m_cluster->init_leaf(m_condition_column_key, &*m_leaf); } size_t find_first_local(size_t start, size_t end) override @@ -1003,14 +908,14 @@ class BoolNode : public ParentNode { TConditionFunction condition; bool m_value_is_null = !m_value; for (size_t s = start; s < end; ++s) { - util::Optional value = m_leaf_ptr->get(s); + auto value = m_leaf->get(s); if (condition(value, m_value, !value, m_value_is_null)) return s; } return not_found; } - virtual std::string describe(util::serializer::SerialisationState& state) const override + std::string describe(util::serializer::SerialisationState& state) const override { return state.describe_column(ParentNode::m_table, m_condition_column_key) + " " + TConditionFunction::description() + " " + util::serializer::print_value(m_value); @@ -1022,12 +927,8 @@ class BoolNode : public ParentNode { } private: - util::Optional m_value; - using LeafCacheStorage = typename std::aligned_storage::type; - using LeafPtr = std::unique_ptr; - LeafCacheStorage m_leaf_cache_storage; - LeafPtr m_array_ptr; - const ArrayBoolNull* m_leaf_ptr = nullptr; + std::optional m_value; + std::optional m_leaf; std::optional m_index_evaluator; }; @@ -1050,10 +951,8 @@ class TimestampNodeBase : public ParentNode { void cluster_changed() override { - m_array_ptr = nullptr; - m_array_ptr = LeafPtr(new (&m_leaf_cache_storage) ArrayTimestamp(m_table.unchecked_ptr()->get_alloc())); - m_cluster->init_leaf(this->m_condition_column_key, m_array_ptr.get()); - m_leaf_ptr = m_array_ptr.get(); + m_leaf.emplace(m_table.unchecked_ptr()->get_alloc()); + m_cluster->init_leaf(m_condition_column_key, &*m_leaf); } protected: @@ -1064,11 +963,7 @@ class TimestampNodeBase : public ParentNode { } Timestamp m_value; - using LeafCacheStorage = typename std::aligned_storage::type; - using LeafPtr = std::unique_ptr; - LeafCacheStorage m_leaf_cache_storage; - LeafPtr m_array_ptr; - const ArrayTimestamp* m_leaf_ptr = nullptr; + std::optional m_leaf; }; template @@ -1101,7 +996,7 @@ class TimestampNode : public TimestampNodeBase { const IndexEvaluator* index_based_keys() override { - return m_index_evaluator ? &(*m_index_evaluator) : nullptr; + return m_index_evaluator ? &*m_index_evaluator : nullptr; } bool has_search_index() const override @@ -1116,7 +1011,7 @@ class TimestampNode : public TimestampNodeBase { return m_index_evaluator->do_search_index(this->m_cluster, start, end); } } - return m_leaf_ptr->find_first(m_value, start, end); + return m_leaf->find_first(m_value, start, end); } std::string describe(util::serializer::SerialisationState& state) const override @@ -1132,10 +1027,6 @@ class TimestampNode : public TimestampNodeBase { } protected: - TimestampNode(const TimestampNode& from, Transaction* tr) - : TimestampNodeBase(from, tr) - { - } std::optional m_index_evaluator; }; @@ -1157,10 +1048,8 @@ class DecimalNodeBase : public ParentNode { void cluster_changed() override { - m_array_ptr = nullptr; - m_array_ptr = LeafPtr(new (&m_leaf_cache_storage) ArrayDecimal128(m_table.unchecked_ptr()->get_alloc())); - m_cluster->init_leaf(this->m_condition_column_key, m_array_ptr.get()); - m_leaf_ptr = m_array_ptr.get(); + m_leaf.emplace(m_table.unchecked_ptr()->get_alloc()); + m_cluster->init_leaf(m_condition_column_key, &*m_leaf); } void init(bool will_query_ranges) override @@ -1178,11 +1067,7 @@ class DecimalNodeBase : public ParentNode { } Decimal128 m_value; - using LeafCacheStorage = typename std::aligned_storage::type; - using LeafPtr = std::unique_ptr; - LeafCacheStorage m_leaf_cache_storage; - LeafPtr m_array_ptr; - const ArrayDecimal128* m_leaf_ptr = nullptr; + std::optional m_leaf; }; template @@ -1195,7 +1080,7 @@ class DecimalNode : public DecimalNodeBase { TConditionFunction cond; bool value_is_null = m_value.is_null(); for (size_t i = start; i < end; i++) { - Decimal128 val = m_leaf_ptr->get(i); + Decimal128 val = m_leaf->get(i); if (cond(val, m_value, val.is_null(), value_is_null)) return i; } @@ -1213,12 +1098,6 @@ class DecimalNode : public DecimalNodeBase { { return std::unique_ptr(new DecimalNode(*this)); } - -protected: - DecimalNode(const DecimalNode& from, Transaction* tr) - : DecimalNodeBase(from, tr) - { - } }; template @@ -1241,10 +1120,8 @@ class FixedBytesNodeBase : public ParentNode { void cluster_changed() override { - m_array_ptr = nullptr; - m_array_ptr = LeafPtr(new (&m_leaf_cache_storage) ArrayType(m_table.unchecked_ptr()->get_alloc())); - m_cluster->init_leaf(this->m_condition_column_key, m_array_ptr.get()); - m_leaf_ptr = m_array_ptr.get(); + m_leaf.emplace(m_table.unchecked_ptr()->get_alloc()); + m_cluster->init_leaf(m_condition_column_key, &*m_leaf); } void init(bool will_query_ranges) override @@ -1263,12 +1140,8 @@ class FixedBytesNodeBase : public ParentNode { } ObjectType m_value; + std::optional m_leaf; bool m_value_is_null = false; - using LeafCacheStorage = typename std::aligned_storage::type; - using LeafPtr = std::unique_ptr; - LeafCacheStorage m_leaf_cache_storage; - LeafPtr m_array_ptr; - const ArrayType* m_leaf_ptr = nullptr; }; template @@ -1280,7 +1153,7 @@ class FixedBytesNode : public FixedBytesNodeBase { { TConditionFunction cond; for (size_t i = start; i < end; i++) { - util::Optional val = this->m_leaf_ptr->get(i); + util::Optional val = this->m_leaf->get(i); if (val) { if (cond(*val, this->m_value, false, this->m_value_is_null)) return i; @@ -1306,12 +1179,6 @@ class FixedBytesNode : public FixedBytesNodeBase { { return std::unique_ptr(new FixedBytesNode(*this)); } - -protected: - FixedBytesNode(const FixedBytesNode& from, Transaction* tr) - : FixedBytesNode(from, tr) - { - } }; template @@ -1363,12 +1230,12 @@ class FixedBytesNode : public FixedBytesNodeBasem_leaf_ptr->get(start) == m_optional_value) { + if (this->m_leaf->get(start) == m_optional_value) { s = start; } } else { - s = this->m_leaf_ptr->find_first(m_optional_value, start, end); + s = this->m_leaf->find_first(m_optional_value, start, end); } } @@ -1390,10 +1257,6 @@ class FixedBytesNode : public FixedBytesNodeBase m_optional_value; std::optional m_index_evaluator; }; @@ -1426,10 +1289,8 @@ class MixedNodeBase : public ParentNode { void cluster_changed() override { - m_array_ptr = nullptr; - m_array_ptr = LeafPtr(new (&m_leaf_cache_storage) ArrayMixed(m_table.unchecked_ptr()->get_alloc())); - m_cluster->init_leaf(this->m_condition_column_key, m_array_ptr.get()); - m_leaf_ptr = m_array_ptr.get(); + m_leaf.emplace(m_table.unchecked_ptr()->get_alloc()); + m_cluster->init_leaf(m_condition_column_key, &*m_leaf); } void init(bool will_query_ranges) override @@ -1479,12 +1340,8 @@ class MixedNodeBase : public ParentNode { QueryValue m_value; OwnedBinaryData m_buffer; + std::optional m_leaf; bool m_value_is_null = false; - using LeafCacheStorage = typename std::aligned_storage::type; - using LeafPtr = std::unique_ptr; - LeafCacheStorage m_leaf_cache_storage; - LeafPtr m_array_ptr; - const ArrayMixed* m_leaf_ptr = nullptr; }; template @@ -1496,7 +1353,7 @@ class MixedNode : public MixedNodeBase { { TConditionFunction cond; for (size_t i = start; i < end; i++) { - QueryValue val(m_leaf_ptr->get(i)); + QueryValue val(m_leaf->get(i)); if constexpr (realm::is_any_v) { // For some strange reason the parameters are swapped for string conditions @@ -1511,7 +1368,7 @@ class MixedNode : public MixedNodeBase { return realm::npos; } - virtual std::string describe_condition() const override + std::string describe_condition() const override { return TConditionFunction::description(); } @@ -1520,12 +1377,6 @@ class MixedNode : public MixedNodeBase { { return std::unique_ptr(new MixedNode(*this)); } - -protected: - MixedNode(const MixedNode& from, Transaction* tr) - : MixedNode(from, tr) - { - } }; template <> @@ -1564,7 +1415,7 @@ class MixedNode : public MixedNodeBase { size_t find_first_local(size_t start, size_t end) override; - virtual std::string describe_condition() const override + std::string describe_condition() const override { return Equal::description(); } @@ -1619,7 +1470,7 @@ class MixedNode : public MixedNodeBase { return bool(m_index_evaluator); } - virtual std::string describe_condition() const override + std::string describe_condition() const override { return EqualIns::description(); } @@ -1660,15 +1511,8 @@ class StringNodeBase : public ParentNode { void cluster_changed() override { - // Assigning nullptr will cause the Leaf destructor to be called. Must - // be done before assigning a new one. Otherwise the destructor will be - // called after the constructor is called and that is unfortunate if - // the object has the same address. (As in this case) - m_array_ptr = nullptr; - // Create new Leaf - m_array_ptr = LeafPtr(new (&m_leaf_cache_storage) ArrayString(m_table.unchecked_ptr()->get_alloc())); - m_cluster->init_leaf(this->m_condition_column_key, m_array_ptr.get()); - m_leaf_ptr = m_array_ptr.get(); + m_leaf.emplace(m_table.unchecked_ptr()->get_alloc()); + m_cluster->init_leaf(m_condition_column_key, &*m_leaf); } void init(bool will_query_ranges) override @@ -1684,7 +1528,7 @@ class StringNodeBase : public ParentNode { virtual void clear_leaf_state() { - m_array_ptr = nullptr; + m_leaf.reset(); } StringNodeBase(const StringNodeBase& from) @@ -1694,7 +1538,7 @@ class StringNodeBase : public ParentNode { { } - virtual std::string describe(util::serializer::SerialisationState& state) const override + std::string describe(util::serializer::SerialisationState& state) const override { REALM_ASSERT(m_condition_column_key); StringData sd; @@ -1706,13 +1550,8 @@ class StringNodeBase : public ParentNode { } protected: - util::Optional m_value; - - using LeafCacheStorage = typename std::aligned_storage::type; - using LeafPtr = std::unique_ptr; - LeafCacheStorage m_leaf_cache_storage; - LeafPtr m_array_ptr; - const ArrayString* m_leaf_ptr = nullptr; + std::optional m_value; + std::optional m_leaf; bool m_is_string_enum = false; @@ -1720,9 +1559,9 @@ class StringNodeBase : public ParentNode { size_t m_leaf_start = 0; size_t m_leaf_end = 0; - inline StringData get_string(size_t s) + StringData get_string(size_t s) { - return m_leaf_ptr->get(s); + return m_leaf->get(s); } }; @@ -1763,7 +1602,7 @@ class StringNode : public StringNodeBase { return not_found; } - virtual std::string describe_condition() const override + std::string describe_condition() const override { return TConditionFunction::description(); } @@ -1829,7 +1668,7 @@ class StringNode : public StringNodeBase { return not_found; } - virtual std::string describe_condition() const override + std::string describe_condition() const override { return Contains::description(); } @@ -1910,7 +1749,7 @@ class StringNode : public StringNodeBase { return not_found; } - virtual std::string describe_condition() const override + std::string describe_condition() const override { return ContainsIns::description(); } @@ -1971,7 +1810,7 @@ class StringNodeEqualBase : public StringNodeBase { size_t find_first_local(size_t start, size_t end) override; - virtual std::string describe_condition() const override + std::string describe_condition() const override { return Equal::description(); } @@ -2059,7 +1898,7 @@ class StringNode : public StringNodeEqualBase { void _search_index_init() override; - virtual std::string describe_condition() const override + std::string describe_condition() const override { return EqualIns::description(); } @@ -2104,7 +1943,7 @@ class StringNodeFulltext : public StringNodeEqualBase { return std::unique_ptr(new StringNodeFulltext(*this)); } - virtual std::string describe_condition() const override + std::string describe_condition() const override { return "FULLTEXT"; } @@ -2352,7 +2191,6 @@ class NotNode : public ParentNode { } } - std::unique_ptr clone() const override { return std::unique_ptr(new NotNode(*this)); @@ -2402,10 +2240,9 @@ class TwoColumnsNodeBase : public ParentNode { } } - ~TwoColumnsNodeBase() noexcept override {} void table_changed() override { - if (this->m_table) { + if (m_table) { ParentNode::m_table->check_column(m_condition_column_key1); ParentNode::m_table->check_column(m_condition_column_key2); } @@ -2415,19 +2252,19 @@ class TwoColumnsNodeBase : public ParentNode { const ColKey& col_key); void cluster_changed() override { - if (!m_leaf_ptr1) { - m_leaf_ptr1 = + if (!m_leaf1) { + m_leaf1 = update_cached_leaf_pointers_for_column(m_table.unchecked_ptr()->get_alloc(), m_condition_column_key1); } - if (!m_leaf_ptr2) { - m_leaf_ptr2 = + if (!m_leaf2) { + m_leaf2 = update_cached_leaf_pointers_for_column(m_table.unchecked_ptr()->get_alloc(), m_condition_column_key2); } - this->m_cluster->init_leaf(m_condition_column_key1, m_leaf_ptr1.get()); - this->m_cluster->init_leaf(m_condition_column_key2, m_leaf_ptr2.get()); + m_cluster->init_leaf(m_condition_column_key1, m_leaf1.get()); + m_cluster->init_leaf(m_condition_column_key2, m_leaf2.get()); } - virtual std::string describe(util::serializer::SerialisationState& state) const override + std::string describe(util::serializer::SerialisationState& state) const override { REALM_ASSERT(m_condition_column_key1 && m_condition_column_key2); return state.describe_column(ParentNode::m_table, m_condition_column_key1) + " " + describe_condition() + @@ -2442,10 +2279,10 @@ class TwoColumnsNodeBase : public ParentNode { } protected: - mutable ColKey m_condition_column_key1; - mutable ColKey m_condition_column_key2; - std::unique_ptr m_leaf_ptr1 = nullptr; - std::unique_ptr m_leaf_ptr2 = nullptr; + ColKey m_condition_column_key1; + ColKey m_condition_column_key2; + std::unique_ptr m_leaf1; + std::unique_ptr m_leaf2; }; @@ -2453,13 +2290,12 @@ template class TwoColumnsNode : public TwoColumnsNodeBase { public: using TwoColumnsNodeBase::TwoColumnsNodeBase; - ~TwoColumnsNode() noexcept override {} size_t find_first_local(size_t start, size_t end) override { size_t s = start; while (s < end) { - QueryValue v1(m_leaf_ptr1->get_any(s)); - QueryValue v2(m_leaf_ptr2->get_any(s)); + QueryValue v1(m_leaf1->get_any(s)); + QueryValue v2(m_leaf2->get_any(s)); if (TConditionFunction()(v1, v2)) return s; else @@ -2468,7 +2304,7 @@ class TwoColumnsNode : public TwoColumnsNodeBase { return not_found; } - virtual std::string describe_condition() const override + std::string describe_condition() const override { return TConditionFunction::description(); } @@ -2477,12 +2313,6 @@ class TwoColumnsNode : public TwoColumnsNodeBase { { return std::unique_ptr(new TwoColumnsNode(*this)); } - -protected: - TwoColumnsNode(const TwoColumnsNode& from, Transaction* tr) - : TwoColumnsNode(from, tr) - { - } }; @@ -2498,7 +2328,7 @@ class ExpressionNode : public ParentNode { void cluster_changed() override; void collect_dependencies(std::vector&) const override; - virtual std::string describe(util::serializer::SerialisationState& state) const override; + std::string describe(util::serializer::SerialisationState& state) const override; std::unique_ptr clone() const override; @@ -2528,18 +2358,18 @@ class LinksToNodeBase : public ParentNode { void cluster_changed() override { - m_array_ptr = nullptr; if (m_column_type == col_type_Link) { - m_array_ptr = LeafPtr(new (&m_storage.m_list) ArrayKey(m_table.unchecked_ptr()->get_alloc())); + m_list.emplace(m_table.unchecked_ptr()->get_alloc()); + m_leaf = &*m_list; } else if (m_column_type == col_type_LinkList) { - m_array_ptr = LeafPtr(new (&m_storage.m_linklist) ArrayList(m_table.unchecked_ptr()->get_alloc())); + m_linklist.emplace(m_table.unchecked_ptr()->get_alloc()); + m_leaf = &*m_linklist; } - m_cluster->init_leaf(this->m_condition_column_key, m_array_ptr.get()); - m_leaf_ptr = m_array_ptr.get(); + m_cluster->init_leaf(this->m_condition_column_key, m_leaf); } - virtual std::string describe(util::serializer::SerialisationState& state) const override + std::string describe(util::serializer::SerialisationState& state) const override { REALM_ASSERT(m_condition_column_key); if (m_target_keys.size() > 1) @@ -2552,14 +2382,9 @@ class LinksToNodeBase : public ParentNode { protected: std::vector m_target_keys; ColumnType m_column_type; - using LeafPtr = std::unique_ptr; - union Storage { - typename std::aligned_storage::type m_list; - typename std::aligned_storage::type m_linklist; - }; - Storage m_storage; - LeafPtr m_array_ptr; - const ArrayPayload* m_leaf_ptr = nullptr; + std::optional m_list; + std::optional m_linklist; + ArrayPayload* m_leaf = nullptr; LinksToNodeBase(const LinksToNodeBase& source) : ParentNode(source) @@ -2567,6 +2392,13 @@ class LinksToNodeBase : public ParentNode { , m_column_type(source.m_column_type) { } + + ref_type get_ref(size_t i) + { + if (m_list) + return m_list->get_as_ref(i); + return m_linklist->get(i); + } }; template diff --git a/src/realm/query_expression.cpp b/src/realm/query_expression.cpp index 2d466fe9616..9c4c763c718 100644 --- a/src/realm/query_expression.cpp +++ b/src/realm/query_expression.cpp @@ -73,9 +73,14 @@ std::string LinkMap::description(util::serializer::SerialisationState& state) co return s; } -void LinkMap::map_links(size_t column, ObjKey key, LinkMapFunction lm) const +bool LinkMap::map_links(size_t column, ObjKey key, LinkMapFunction lm) const { - bool last = (column + 1 == m_link_column_keys.size()); + if (!key || key.is_unresolved()) + return true; + if (column == m_link_column_keys.size()) { + return lm(key); + } + ColumnType type = m_link_types[column]; ColKey column_key = m_link_column_keys[column]; const Obj obj = m_tables[column]->get_object(key); @@ -83,131 +88,85 @@ void LinkMap::map_links(size_t column, ObjKey key, LinkMapFunction lm) const auto coll = obj.get_linkcollection_ptr(column_key); size_t sz = coll->size(); for (size_t t = 0; t < sz; t++) { - if (ObjKey k = coll->get_key(t)) { - // Unresolved links are filtered out - if (last) { - lm(k); - } - else - map_links(column + 1, k, lm); - } + if (!map_links(column + 1, coll->get_key(t), lm)) + return false; } } else if (type == col_type_Link) { - if (ObjKey k = obj.get(column_key)) { - if (!k.is_unresolved()) { - if (last) - lm(k); - else - map_links(column + 1, k, lm); - } - } + return map_links(column + 1, obj.get(column_key), lm); } else if (type == col_type_BackLink) { auto backlinks = obj.get_all_backlinks(column_key); for (auto k : backlinks) { - if (last) { - lm(k); - } - else - map_links(column + 1, k, lm); + if (!map_links(column + 1, k, lm)) + return false; } } else { - REALM_ASSERT(false); + REALM_TERMINATE("Invalid column type in LinkMap::map_links()"); } -} - -ref_type LinkMap::get_ref(const ArrayPayload* array_payload, ColumnType type, size_t row) -{ - if (type == col_type_LinkList) - return static_cast(array_payload)->get(row); - return static_cast(array_payload)->get_as_ref(row); + return true; } void LinkMap::map_links(size_t column, size_t row, LinkMapFunction lm) const { - REALM_ASSERT(m_leaf_ptr != nullptr); - - bool last = (column + 1 == m_link_column_keys.size()); ColumnType type = m_link_types[column]; ColKey column_key = m_link_column_keys[column]; if (type == col_type_Link && !column_key.is_set()) { if (column_key.is_dictionary()) { - auto leaf = static_cast(m_leaf_ptr); - if (leaf->get(row)) { + auto& leaf = mpark::get(m_leaf); + if (leaf.get(row)) { Allocator& alloc = get_base_table()->get_alloc(); Array top(alloc); - top.set_parent(const_cast(leaf), row); + top.set_parent(const_cast(&leaf), row); top.init_from_parent(); BPlusTree values(alloc); values.set_parent(&top, 1); values.init_from_parent(); // Iterate through values and insert all link values - values.traverse([&](BPlusTreeNode* node, size_t) { - auto bplustree_leaf = static_cast::LeafNode*>(node); - auto sz = bplustree_leaf->size(); - for (size_t i = 0; i < sz; i++) { - auto m = bplustree_leaf->get(i); - if (m.is_type(type_TypedLink)) { - auto link = m.get_link(); - REALM_ASSERT(link.get_table_key() == this->m_tables[column + 1]->get_key()); - auto k = link.get_obj_key(); - if (!k.is_unresolved()) { - if (last) - lm(k); - else - map_links(column + 1, k, lm); - } - } + values.for_all([&](Mixed m) { + if (m.is_type(type_TypedLink)) { + auto link = m.get_link(); + REALM_ASSERT(link.get_table_key() == this->m_tables[column + 1]->get_key()); + if (!map_links(column + 1, link.get_obj_key(), lm)) + return false; } - return IteratorControl::AdvanceToNext; + return true; }); } } else { REALM_ASSERT(!column_key.is_collection()); - if (ObjKey k = static_cast(m_leaf_ptr)->get(row)) { - if (!k.is_unresolved()) { - if (last) - lm(k); - else - map_links(column + 1, k, lm); - } - } + map_links(column + 1, mpark::get(m_leaf).get(row), lm); } } else if (type == col_type_LinkList || (type == col_type_Link && column_key.is_set())) { - if (ref_type ref = get_ref(m_leaf_ptr, type, row)) { + ref_type ref; + if (auto list = mpark::get_if(&m_leaf)) { + ref = list->get(row); + } + else { + ref = mpark::get(m_leaf).get_as_ref(row); + } + + if (ref) { BPlusTree links(get_base_table()->get_alloc()); links.init_from_ref(ref); size_t sz = links.size(); for (size_t t = 0; t < sz; t++) { - ObjKey k = links.get(t); - if (!k.is_unresolved()) { - if (last) { - if (!lm(k)) { - return; - } - } - else - map_links(column + 1, k, lm); - } + if (!map_links(column + 1, links.get(t), lm)) + break; } } } else if (type == col_type_BackLink) { - auto back_links = static_cast(m_leaf_ptr); - size_t sz = back_links->get_backlink_count(row); + auto& back_links = mpark::get(m_leaf); + size_t sz = back_links.get_backlink_count(row); for (size_t t = 0; t < sz; t++) { - ObjKey k = back_links->get_backlink(row, t); - if (last) { - if (!lm(k)) - return; - } - else - map_links(column + 1, k, lm); + ObjKey k = back_links.get_backlink(row, t); + if (!map_links(column + 1, k, lm)) + break; } } else { @@ -281,16 +240,12 @@ void ColumnDictionaryKey::init_key(Mixed key_value) void ColumnDictionaryKeys::set_cluster(const Cluster* cluster) { - m_leaf_ptr = nullptr; - m_array_ptr = nullptr; if (m_link_map.has_links()) { m_link_map.set_cluster(cluster); } else { - // Create new Leaf - m_array_ptr = LeafPtr(new (&m_leaf_cache_storage) ArrayInteger(m_link_map.get_base_table()->get_alloc())); - cluster->init_leaf(m_column_key, m_array_ptr.get()); - m_leaf_ptr = m_array_ptr.get(); + m_leaf.emplace(m_link_map.get_base_table()->get_alloc()); + cluster->init_leaf(m_column_key, &*m_leaf); } } @@ -298,7 +253,7 @@ void ColumnDictionaryKeys::set_cluster(const Cluster* cluster) void ColumnDictionaryKeys::evaluate(size_t index, ValueBase& destination) { if (m_link_map.has_links()) { - REALM_ASSERT(m_leaf_ptr == nullptr); + REALM_ASSERT(!m_leaf); std::vector links = m_link_map.get_links(index); auto sz = links.size(); @@ -321,10 +276,10 @@ void ColumnDictionaryKeys::evaluate(size_t index, ValueBase& destination) // Not a link column Allocator& alloc = get_base_table()->get_alloc(); - REALM_ASSERT(m_leaf_ptr != nullptr); - if (m_leaf_ptr->get(index)) { + REALM_ASSERT(m_leaf); + if (m_leaf->get(index)) { Array top(alloc); - top.set_parent(const_cast(m_leaf_ptr), index); + top.set_parent(&*m_leaf, index); top.init_from_parent(); BPlusTree keys(alloc); keys.set_parent(&top, 0); @@ -333,14 +288,9 @@ void ColumnDictionaryKeys::evaluate(size_t index, ValueBase& destination) destination.init(true, keys.size()); size_t n = 0; // Iterate through BPlusTree and insert all keys - keys.traverse([&](BPlusTreeNode* node, size_t) { - auto bplustree_leaf = static_cast::LeafNode*>(node); - auto sz = bplustree_leaf->size(); - for (size_t i = 0; i < sz; i++) { - destination.set(n, bplustree_leaf->get(i)); - n++; - } - return IteratorControl::AdvanceToNext; + keys.for_all([&](StringData str) { + destination.set(n, str); + n++; }); } } @@ -349,7 +299,7 @@ void ColumnDictionaryKeys::evaluate(size_t index, ValueBase& destination) void ColumnDictionaryKey::evaluate(size_t index, ValueBase& destination) { if (links_exist()) { - REALM_ASSERT(m_leaf_ptr == nullptr); + REALM_ASSERT(!m_leaf); std::vector links = m_link_map.get_links(index); auto sz = links.size(); @@ -377,10 +327,10 @@ void ColumnDictionaryKey::evaluate(size_t index, ValueBase& destination) // Not a link column Allocator& alloc = get_base_table()->get_alloc(); - REALM_ASSERT(m_leaf_ptr != nullptr); - if (m_leaf_ptr->get(index)) { + REALM_ASSERT(m_leaf); + if (m_leaf->get(index)) { Array top(alloc); - top.set_parent(const_cast(m_leaf_ptr), index); + top.set_parent(&*m_leaf, index); top.init_from_parent(); BPlusTree keys(alloc); keys.set_parent(&top, 0); @@ -442,7 +392,7 @@ SizeOperator Columns::size() void Columns::evaluate(size_t index, ValueBase& destination) { if (links_exist()) { - REALM_ASSERT(m_leaf_ptr == nullptr); + REALM_ASSERT(!m_leaf); std::vector links = m_link_map.get_links(index); auto sz = links.size(); @@ -465,10 +415,10 @@ void Columns::evaluate(size_t index, ValueBase& destination) // Not a link column Allocator& alloc = get_base_table()->get_alloc(); - REALM_ASSERT(m_leaf_ptr != nullptr); - if (m_leaf_ptr->get(index)) { + REALM_ASSERT(m_leaf); + if (m_leaf->get(index)) { Array top(alloc); - top.set_parent(const_cast(m_leaf_ptr), index); + top.set_parent(&*m_leaf, index); top.init_from_parent(); BPlusTree values(alloc); values.set_parent(&top, 1); @@ -477,14 +427,9 @@ void Columns::evaluate(size_t index, ValueBase& destination) destination.init(true, values.size()); size_t n = 0; // Iterate through BPlusTreee and insert all values - values.traverse([&](BPlusTreeNode* node, size_t) { - auto bplustree_leaf = static_cast::LeafNode*>(node); - auto sz = bplustree_leaf->size(); - for (size_t i = 0; i < sz; i++) { - destination.set(n, bplustree_leaf->get(i)); - n++; - } - return IteratorControl::AdvanceToNext; + values.for_all([&](Mixed val) { + destination.set(n, val); + n++; }); } } @@ -513,16 +458,12 @@ void Columns::evaluate(size_t index, ValueBase& destination) void ColumnListBase::set_cluster(const Cluster* cluster) { - m_leaf_ptr = nullptr; - m_array_ptr = nullptr; if (m_link_map.has_links()) { m_link_map.set_cluster(cluster); } else { - // Create new Leaf - m_array_ptr = LeafPtr(new (&m_leaf_cache_storage) ArrayInteger(m_link_map.get_base_table()->get_alloc())); - cluster->init_leaf(m_column_key, m_array_ptr.get()); - m_leaf_ptr = m_array_ptr.get(); + m_leaf.emplace(m_link_map.get_base_table()->get_alloc()); + cluster->init_leaf(m_column_key, &*m_leaf); } } @@ -551,12 +492,12 @@ void ColumnListBase::get_lists(size_t index, Value& destination, size_t } } else { - size_t rows = std::min(m_leaf_ptr->size() - index, nb_elements); + size_t rows = std::min(m_leaf->size() - index, nb_elements); destination.init(false, rows); for (size_t t = 0; t < rows; t++) { - destination.set(t, m_leaf_ptr->get(index + t)); + destination.set(t, m_leaf->get(index + t)); } } } diff --git a/src/realm/query_expression.hpp b/src/realm/query_expression.hpp index ae6968a1cba..d007353575d 100644 --- a/src/realm/query_expression.hpp +++ b/src/realm/query_expression.hpp @@ -152,6 +152,8 @@ The Columns class encapsulates all this into a simple class that, for any type T #include #include +#include + #include #include @@ -241,7 +243,7 @@ class ValueBase { public: using ValueType = QueryValue; - static const size_t chunk_size = 8; + static constexpr size_t chunk_size = 8; bool m_from_list = false; ValueBase() = default; @@ -646,8 +648,7 @@ class ValueBase { class Expression { public: - Expression() {} - virtual ~Expression() {} + virtual ~Expression() = default; virtual double init() { @@ -672,7 +673,7 @@ std::unique_ptr make_expression(Args&&... args) class Subexpr { public: - virtual ~Subexpr() {} + virtual ~Subexpr() = default; virtual std::unique_ptr clone() const = 0; @@ -1166,7 +1167,6 @@ class Subexpr2 : public Subexpr, public Overloads class Subexpr2 : public Subexpr, public Overloads { public: - // FIXME: Query geoWithin(const Geospatial& other); DataType get_type() const final { return type_Geospatial; @@ -1451,7 +1451,7 @@ class ConstantGeospatialValue : public Value { : Value() , m_geospatial(geo) { - if (geo.is_valid()) { + if (geo.get_type() != Geospatial::Type::Invalid) { set(0, Mixed{&m_geospatial}); } } @@ -1466,7 +1466,7 @@ class ConstantGeospatialValue : public Value { : Value() , m_geospatial(other.m_geospatial) { - if (m_geospatial.is_valid()) { + if (m_geospatial.get_type() != Geospatial::Type::Invalid) { set(0, Mixed{&m_geospatial}); } } @@ -1535,28 +1535,26 @@ class LinkMap final { void set_cluster(const Cluster* cluster) { Allocator& alloc = get_base_table()->get_alloc(); - m_array_ptr = nullptr; + ArrayPayload* array_ptr; switch (m_link_types[0]) { case col_type_Link: if (m_link_column_keys[0].is_dictionary()) { - m_array_ptr = LeafPtr(new (&m_storage.m_dict) ArrayInteger(alloc)); + array_ptr = &m_leaf.emplace(alloc); } else { - m_array_ptr = LeafPtr(new (&m_storage.m_list) ArrayKey(alloc)); + array_ptr = &m_leaf.emplace(alloc); } break; case col_type_LinkList: - m_array_ptr = LeafPtr(new (&m_storage.m_linklist) ArrayList(alloc)); + array_ptr = &m_leaf.emplace(alloc); break; case col_type_BackLink: - m_array_ptr = LeafPtr(new (&m_storage.m_backlink) ArrayBacklink(alloc)); + array_ptr = &m_leaf.emplace(alloc); break; default: - break; + REALM_UNREACHABLE(); } - // m_tables[0]->report_invalid_key(m_link_column_keys[0]); - cluster->init_leaf(m_link_column_keys[0], m_array_ptr.get()); - m_leaf_ptr = m_array_ptr.get(); + cluster->init_leaf(m_link_column_keys[0], array_ptr); } void collect_dependencies(std::vector& tables) const; @@ -1630,10 +1628,8 @@ class LinkMap final { return !m_link_column_keys.empty(); } - static ref_type get_ref(const ArrayPayload* array_payload, ColumnType type, size_t ndx); - private: - void map_links(size_t column, ObjKey key, LinkMapFunction lm) const; + bool map_links(size_t column, ObjKey key, LinkMapFunction lm) const; void map_links(size_t column, size_t row, LinkMapFunction lm) const; void get_links(size_t row, std::vector& result) const @@ -1648,17 +1644,8 @@ class LinkMap final { std::vector m_link_types; std::vector m_tables; bool m_only_unary_links = true; - // Leaf cache - using LeafPtr = std::unique_ptr; - union Storage { - typename std::aligned_storage::type m_list; - typename std::aligned_storage::type m_dict; - typename std::aligned_storage::type m_linklist; - typename std::aligned_storage::type m_backlink; - }; - Storage m_storage; - LeafPtr m_array_ptr; - const ArrayPayload* m_leaf_ptr = nullptr; + + mpark::variant m_leaf; template friend Query compare(const Subexpr2&, const Obj&); @@ -1839,23 +1826,19 @@ class SimpleQuerySupport : public ObjPropertyExpr { void set_cluster(const Cluster* cluster) override { - m_array_ptr = nullptr; - m_leaf_ptr = nullptr; if (links_exist()) { m_link_map.set_cluster(cluster); } else { - // Create new Leaf - m_array_ptr = LeafPtr(new (&m_leaf_cache_storage) LeafType(m_link_map.get_base_table()->get_alloc())); - cluster->init_leaf(m_column_key, m_array_ptr.get()); - m_leaf_ptr = m_array_ptr.get(); + m_leaf.emplace(m_link_map.get_base_table()->get_alloc()); + cluster->init_leaf(m_column_key, &*m_leaf); } } void evaluate(size_t index, ValueBase& destination) override { if (links_exist()) { - REALM_ASSERT(m_leaf_ptr == nullptr); + REALM_ASSERT(!m_leaf); if (m_link_map.only_unary_links()) { REALM_ASSERT(destination.size() == 1); @@ -1900,14 +1883,14 @@ class SimpleQuerySupport : public ObjPropertyExpr { } else { // Not a link column - REALM_ASSERT(m_leaf_ptr != nullptr); + REALM_ASSERT(m_leaf); REALM_ASSERT(destination.size() == 1); REALM_ASSERT(!destination.m_from_list); - if (m_leaf_ptr->is_null(index)) { + if (m_leaf->is_null(index)) { destination.set_null(0); } else { - destination.set(0, m_leaf_ptr->get(index)); + destination.set(0, m_leaf->get(index)); } } } @@ -1937,13 +1920,8 @@ class SimpleQuerySupport : public ObjPropertyExpr { using ObjPropertyExpr::m_link_map; using ObjPropertyExpr::m_column_key; - // Leaf cache using LeafType = typename ColumnTypeTraits::cluster_leaf_type; - using LeafCacheStorage = typename std::aligned_storage::type; - using LeafPtr = std::unique_ptr; - LeafCacheStorage m_leaf_cache_storage; - LeafPtr m_array_ptr; - LeafType* m_leaf_ptr = nullptr; + std::optional m_leaf; }; template <> @@ -2196,7 +2174,7 @@ class UnaryLinkCompare : public Expression { return not_found; } - virtual std::string description(util::serializer::SerialisationState& state) const override + std::string description(util::serializer::SerialisationState& state) const override { return state.describe_columns(m_link_map, ColKey()) + (has_links ? " != NULL" : " == NULL"); } @@ -2259,7 +2237,7 @@ class LinkCount : public Subexpr2 { destination = Value(count); } - virtual std::string description(util::serializer::SerialisationState& state) const override + std::string description(util::serializer::SerialisationState& state) const override { return state.describe_columns(m_link_map, ColKey()) + util::serializer::value_separator + "@count"; } @@ -2336,7 +2314,7 @@ class BacklinkCount : public Subexpr2 { destination = Value(count); } - virtual std::string description(util::serializer::SerialisationState& state) const override + std::string description(util::serializer::SerialisationState& state) const override { std::string s; if (m_link_map.links_exist()) { @@ -2355,24 +2333,45 @@ class BacklinkCount : public Subexpr2 { #if REALM_ENABLE_GEOSPATIAL class GeoWithinCompare : public Expression { public: - GeoWithinCompare(const LinkMap& lm, Geospatial bounds) + GeoWithinCompare(const LinkMap& lm, Geospatial&& bounds, util::Optional comp_type) : m_link_map(lm) - , m_bounds(bounds) + , m_bounds(std::move(bounds)) + , m_region(m_bounds) + , m_comp_type(comp_type) + { + Status status = m_region.get_conversion_status(); + if (!status.is_ok()) { + throw InvalidArgument(status.code(), + util::format("Invalid region in GEOWITHIN query for parameter '%1': '%2'", m_bounds, + status.reason())); + } + } + + GeoWithinCompare(const GeoWithinCompare& other) + : m_link_map(other.m_link_map) + , m_bounds(other.m_bounds) + , m_region(m_bounds) + , m_comp_type(other.m_comp_type) { } void set_base_table(ConstTableRef table) override { m_link_map.set_base_table(table); - ColKey coords_col = m_link_map.get_target_table()->get_column_key("coordinates"); - ColKey type_col = m_link_map.get_target_table()->get_column_key("type"); - if (!coords_col || !type_col || !coords_col.is_list() || - coords_col.get_type() != ColumnType(ColumnType::Type::Double) || - type_col.get_type() != ColumnType(ColumnType::Type::String) || type_col.is_collection()) { + m_coords_col = m_link_map.get_target_table()->get_column_key(Geospatial::c_geo_point_coords_col_name); + m_type_col = m_link_map.get_target_table()->get_column_key(Geospatial::c_geo_point_type_col_name); + if (!m_coords_col || !m_type_col || !m_coords_col.is_list() || + m_coords_col.get_type() != ColumnType(ColumnType::Type::Double) || + m_type_col.get_type() != ColumnType(ColumnType::Type::String) || m_type_col.is_collection()) { util::serializer::SerialisationState none; throw std::runtime_error(util::format( "Query '%1' links to data in the wrong format for a geoWithin query", this->description(none))); } + if (!m_link_map.get_target_table()->is_embedded()) { + throw std::runtime_error(util::format( + "A GEOWITHIN query can only operate on a link to an embedded class but '%1' is at the top level", + m_link_map.get_target_table()->get_class_name())); + } } void set_cluster(const Cluster* cluster) override @@ -2394,27 +2393,52 @@ class GeoWithinCompare : public Expression { size_t find_first(size_t start, size_t end) const override { - GeoRegion region(m_bounds); - bool found = false; auto table = m_link_map.get_target_table(); while (start < end) { - m_link_map.map_links(start, [&](ObjKey key) { - found = region.contains( - Geospatial::from_obj(table->get_object(key), m_type_col, m_coords_col).get()); - return found; - }); - if (found) - return start; + bool found = false; + switch (m_comp_type.value_or(ExpressionComparisonType::Any)) { + case ExpressionComparisonType::Any: { + m_link_map.map_links(start, [&](ObjKey key) { + found = m_region.contains( + Geospatial::point_from_obj(table->get_object(key), m_type_col, m_coords_col)); + return !found; // keep searching if not found, stop searching on first match + }); + if (found) + return start; + break; + } + case ExpressionComparisonType::All: { + m_link_map.map_links(start, [&](ObjKey key) { + found = m_region.contains( + Geospatial::point_from_obj(table->get_object(key), m_type_col, m_coords_col)); + return found; // keep searching until one the first non-match + }); + if (found) // all matched + return start; + break; + } + case ExpressionComparisonType::None: { + m_link_map.map_links(start, [&](ObjKey key) { + found = m_region.contains( + Geospatial::point_from_obj(table->get_object(key), m_type_col, m_coords_col)); + return !found; // keep searching until the first match + }); + if (!found) // none matched + return start; + break; + } + } start++; } return not_found; } - virtual std::string description(util::serializer::SerialisationState& state) const override + std::string description(util::serializer::SerialisationState& state) const override { - return state.describe_columns(m_link_map, ColKey()) + " GEOWITHIN " + util::serializer::print_value(m_bounds); + return state.describe_expression_type(m_comp_type) + state.describe_columns(m_link_map, ColKey()) + + " GEOWITHIN " + util::serializer::print_value(m_bounds); } std::unique_ptr clone() const override @@ -2425,8 +2449,10 @@ class GeoWithinCompare : public Expression { private: LinkMap m_link_map; Geospatial m_bounds; + GeoRegion m_region; ColKey m_type_col; ColKey m_coords_col; + util::Optional m_comp_type; }; #endif @@ -2591,7 +2617,7 @@ class KeyValue : public Subexpr2 { destination = Value(m_key); } - virtual std::string description(util::serializer::SerialisationState&) const override + std::string description(util::serializer::SerialisationState&) const override { return util::serializer::print_value(m_key); } @@ -2653,7 +2679,7 @@ class Columns : public Subexpr2 { #if REALM_ENABLE_GEOSPATIAL Query geo_within(Geospatial bounds) const { - return make_expression(m_link_map, bounds); + return make_expression(m_link_map, std::move(bounds), m_comparison_type); } #endif @@ -2799,13 +2825,8 @@ class ColumnListBase { mutable ColKey m_column_key; LinkMap m_link_map; - // Leaf cache - using LeafCacheStorage = typename std::aligned_storage::type; - using LeafPtr = std::unique_ptr; - LeafCacheStorage m_leaf_cache_storage; - LeafPtr m_array_ptr; - ArrayInteger* m_leaf_ptr = nullptr; - util::Optional m_comparison_type; + std::optional m_leaf; + std::optional m_comparison_type; }; template @@ -3192,15 +3213,8 @@ class ColumnDictionaryKeys : public Subexpr2 { DataType m_key_type; ColKey m_column_key; LinkMap m_link_map; - util::Optional m_comparison_type; - - - // Leaf cache - using LeafCacheStorage = typename std::aligned_storage::type; - using LeafPtr = std::unique_ptr; - LeafCacheStorage m_leaf_cache_storage; - LeafPtr m_array_ptr; - ArrayInteger* m_leaf_ptr = nullptr; + std::optional m_comparison_type; + std::optional m_leaf; }; template @@ -3414,7 +3428,7 @@ class CollectionColumnAggregate : public Subexpr2get(index)) { + if (auto ref = m_columns_collection.m_leaf->get(index)) { Allocator& alloc = m_columns_collection.get_base_table()->get_alloc(); Dictionary dict(alloc, m_columns_collection.m_column_key, to_ref(ref)); destination.set(0, do_dictionary_agg(dict)); @@ -3458,7 +3472,7 @@ class CollectionColumnAggregate : public Subexpr2 { void set_cluster(const Cluster* cluster) override { - m_array_ptr = nullptr; - m_leaf_ptr = nullptr; if (links_exist()) { m_link_map.set_cluster(cluster); + m_leaf = mpark::monostate(); } else if (requires_null_column && is_nullable()) { - // Create new Leaf - m_array_ptr = LeafPtr(new (&m_leaf_cache_storage) NullableLeafType(this->get_base_table()->get_alloc())); - cluster->init_leaf(m_column_key, m_array_ptr.get()); - m_leaf_ptr = m_array_ptr.get(); + auto& leaf = m_leaf.template emplace(this->get_base_table()->get_alloc()); + cluster->init_leaf(m_column_key, &leaf); } else { - // Create new Leaf - m_array_ptr = LeafPtr(new (&m_leaf_cache_storage) LeafType(this->get_base_table()->get_alloc())); - cluster->init_leaf(m_column_key, m_array_ptr.get()); - m_leaf_ptr = m_array_ptr.get(); + auto& leaf = m_leaf.template emplace(this->get_base_table()->get_alloc()); + cluster->init_leaf(m_column_key, &leaf); } } @@ -3601,7 +3610,7 @@ class Columns : public ObjPropertyExpr { using U = typename LeafType2::value_type; if (links_exist()) { - REALM_ASSERT(m_leaf_ptr == nullptr); + REALM_ASSERT(mpark::holds_alternative(m_leaf)); if (m_link_map.only_unary_links()) { destination.init(false, 1); destination.set_null(0); @@ -3626,32 +3635,26 @@ class Columns : public ObjPropertyExpr { } } else { - REALM_ASSERT(m_leaf_ptr != nullptr); - auto leaf = static_cast(m_leaf_ptr); + auto leaf = mpark::get_if(&m_leaf); + REALM_ASSERT(leaf); // Not a Link column size_t colsize = leaf->size(); + size_t rows = std::min(colsize - index, ValueBase::chunk_size); + // Now load `ValueBase::chunk_size` rows from from the leaf into m_storage. if constexpr (std::is_same_v) { // If it's an integer leaf, then it contains the method get_chunk() which copies - // these values in a super fast way (only feasible if more than chunk_size in column) - if (index + ValueBase::chunk_size <= colsize) { - // If you want to modify 'chunk_size' then update Array::get_chunk() - REALM_ASSERT_3(ValueBase::chunk_size, ==, 8); - - auto leaf_2 = static_cast(leaf); - int64_t res[ValueBase::chunk_size]; - leaf_2->get_chunk(index, res); + // these values in a super fast way. If you want to modify 'chunk_size' then update Array::get_chunk() + REALM_ASSERT_3(ValueBase::chunk_size, ==, 8); - destination.set(res, res + ValueBase::chunk_size); - return; - } + int64_t res[ValueBase::chunk_size]; + static_cast(leaf)->get_chunk(index, res); + destination.set(res, res + rows); + return; } - size_t rows = colsize - index; - if (rows > ValueBase::chunk_size) - rows = ValueBase::chunk_size; - destination.init(false, rows); + destination.init(false, rows); for (size_t t = 0; t < rows; t++) { if (leaf->is_null(index + t)) { destination.set_null(t); @@ -3663,7 +3666,7 @@ class Columns : public ObjPropertyExpr { } } - virtual std::string description(util::serializer::SerialisationState& state) const override + std::string description(util::serializer::SerialisationState& state) const override { return state.describe_expression_type(this->m_comparison_type) + state.describe_columns(m_link_map, m_column_key); @@ -3696,14 +3699,10 @@ class Columns : public ObjPropertyExpr { private: using ObjPropertyExpr::m_link_map; using ObjPropertyExpr::m_column_key; - - // Leaf cache - using LeafCacheStorage = - typename std::aligned_storage::type; - using LeafPtr = std::unique_ptr; - LeafCacheStorage m_leaf_cache_storage; - LeafPtr m_array_ptr; - const ArrayPayload* m_leaf_ptr = nullptr; + using LeafStorage = + std::conditional_t, + mpark::variant>; + LeafStorage m_leaf; }; template @@ -3759,7 +3758,7 @@ class SubColumns : public Subexpr, public SubColumnBase { REALM_ASSERT(false); } - virtual std::string description(util::serializer::SerialisationState&) const override + std::string description(util::serializer::SerialisationState&) const override { return ""; // by itself there are no conditions, see SubColumnAggregate } @@ -3888,7 +3887,7 @@ class SubColumnAggregate : public Subexpr2 { } } - virtual std::string description(util::serializer::SerialisationState& state) const override + std::string description(util::serializer::SerialisationState& state) const override { util::serializer::SerialisationState empty_state(state.group); return state.describe_columns(m_link_map, ColKey()) + util::serializer::value_separator + @@ -3948,7 +3947,7 @@ class SubQueryCount : public Subexpr2 { destination = Value(count); } - virtual std::string description(util::serializer::SerialisationState& state) const override + std::string description(util::serializer::SerialisationState& state) const override { REALM_ASSERT(m_link_map.get_base_table() != nullptr); std::string target = state.describe_columns(m_link_map, ColKey()); diff --git a/src/realm/sort_descriptor.cpp b/src/realm/sort_descriptor.cpp index 95eea93df21..a21313489fd 100644 --- a/src/realm/sort_descriptor.cpp +++ b/src/realm/sort_descriptor.cpp @@ -190,9 +190,7 @@ BaseDescriptor::Sorter::Sorter(std::vector> const& column_li m_columns.emplace_back(tables.back(), columns.back(), ascending[i]); auto& translated_keys = m_columns.back().translated_keys; - auto& is_null = m_columns.back().is_null; translated_keys.resize(translated_size); - is_null.resize(translated_size); for (const auto& index : indexes) { size_t index_in_view = index.index_in_view; @@ -200,11 +198,11 @@ BaseDescriptor::Sorter::Sorter(std::vector> const& column_li for (size_t j = 0; j + 1 < sz; ++j) { const Obj obj = tables[j]->get_object(translated_key); // type was checked when creating the ColumnsDescriptor - if (obj.is_null(columns[j])) { - is_null[index_in_view] = true; + translated_key = obj.get(columns[j]); + if (!translated_key || translated_key.is_unresolved()) { + translated_key = null_key; // normalize unresolve to null break; } - translated_key = obj.get(columns[j]); } translated_keys[index_in_view] = translated_key; } @@ -298,9 +296,15 @@ bool BaseDescriptor::Sorter::operator()(IndexPair i, IndexPair j, bool total_ord // identical, then the rows are ordered according to the second column, and so forth. For the // first column, all the payload of the View is cached in IndexPair::cached_value. for (size_t t = 0; t < m_columns.size(); t++) { + ObjKey key_i = i.key_for_object; + ObjKey key_j = j.key_for_object; + if (!m_columns[t].translated_keys.empty()) { - bool null_i = m_columns[t].is_null[i.index_in_view]; - bool null_j = m_columns[t].is_null[j.index_in_view]; + key_i = m_columns[t].translated_keys[i.index_in_view]; + key_j = m_columns[t].translated_keys[j.index_in_view]; + + bool null_i = !key_i; + bool null_j = !key_j; if (null_i && null_j) { continue; @@ -320,13 +324,6 @@ bool BaseDescriptor::Sorter::operator()(IndexPair i, IndexPair j, bool total_ord if (m_cache[t - 1].empty()) { m_cache[t - 1].resize(256); } - ObjKey key_i = i.key_for_object; - ObjKey key_j = j.key_for_object; - - if (!m_columns[t].translated_keys.empty()) { - key_i = m_columns[t].translated_keys[i.index_in_view]; - key_j = m_columns[t].translated_keys[j.index_in_view]; - } ObjCache& cache_i = m_cache[t - 1][key_i.value & 0xFF]; ObjCache& cache_j = m_cache[t - 1][key_j.value & 0xFF]; @@ -364,13 +361,11 @@ void BaseDescriptor::Sorter::cache_first_column(IndexPairs& v) ObjKey key = index.key_for_object; if (!col.translated_keys.empty()) { - if (col.is_null[i]) { + key = col.translated_keys[v[i].index_in_view]; + if (!key) { index.cached_value = Mixed(); continue; } - else { - key = col.translated_keys[v[i].index_in_view]; - } } index.cached_value = col.table->get_object(key).get_any(ck); diff --git a/src/realm/sort_descriptor.hpp b/src/realm/sort_descriptor.hpp index d2739d15baa..cc2e524b9ca 100644 --- a/src/realm/sort_descriptor.hpp +++ b/src/realm/sort_descriptor.hpp @@ -91,7 +91,7 @@ class BaseDescriptor { bool any_is_null(IndexPair i) const { return std::any_of(m_columns.begin(), m_columns.end(), [=](auto&& col) { - return col.is_null.empty() ? false : col.is_null[i.index_in_view]; + return !col.translated_keys.empty() && !col.translated_keys[i.index_in_view]; }); } void cache_first_column(IndexPairs& v); @@ -104,7 +104,6 @@ class BaseDescriptor { , ascending(a) { } - std::vector is_null; std::vector translated_keys; const Table* table; diff --git a/src/realm/sync/network/network.hpp b/src/realm/sync/network/network.hpp index aa5e116ef38..16b352d764b 100644 --- a/src/realm/sync/network/network.hpp +++ b/src/realm/sync/network/network.hpp @@ -24,11 +24,12 @@ #include #include #include +#include +#include #include #include #include -#include -#include +#include // Linux epoll #if defined(REALM_USE_EPOLL) && !REALM_ANDROID @@ -2747,28 +2748,31 @@ inline void Service::AsyncOper::do_recycle_and_execute(bool orphaned, H& handler // the memory is available for a new post operation that might be initiated // during the execution of the handler. bool was_recycled = false; - try { - // We need to copy or move all arguments to be passed to the handler, - // such that there is no risk of references to the recycled operation - // object being passed to the handler (the passed arguments may be - // references to members of the recycled operation object). The easiest - // way to achive this, is by forwarding the reference arguments (passed - // to this function) to a helper function whose arguments have - // nonreference type (`Args...` rather than `Args&&...`). - // - // Note that the copying and moving of arguments may throw, and it is - // important that the operation is still recycled even if that - // happens. For that reason, copying and moving of arguments must not - // happen until we are in a scope (this scope) that catches and deals - // correctly with such exceptions. - do_recycle_and_execute_helper(orphaned, was_recycled, std::move(handler), - std::forward(args)...); // Throws - } - catch (...) { - if (!was_recycled) + + // ScopeExit to ensure the AsyncOper object was reclaimed/deleted + auto at_exit = util::ScopeExit([this, &was_recycled, &orphaned]() noexcept { + if (!was_recycled) { do_recycle(orphaned); - throw; - } + } + }); + + // We need to copy or move all arguments to be passed to the handler, + // such that there is no risk of references to the recycled operation + // object being passed to the handler (the passed arguments may be + // references to members of the recycled operation object). The easiest + // way to achive this, is by forwarding the reference arguments (passed + // to this function) to a helper function whose arguments have + // nonreference type (`Args...` rather than `Args&&...`). + // + // Note that the copying and moving of arguments may throw, and it is + // important that the operation is still recycled even if that + // happens. For that reason, copying and moving of arguments must not + // happen until we are in a scope (this scope) that catches and deals + // correctly with such exceptions. + do_recycle_and_execute_helper(orphaned, was_recycled, std::move(handler), + std::forward(args)...); // Throws + + // Removed catch to prevent truncating the stack trace on exception } template diff --git a/src/realm/table.cpp b/src/realm/table.cpp index db4d3800b76..9c5eff2639b 100644 --- a/src/realm/table.cpp +++ b/src/realm/table.cpp @@ -31,7 +31,6 @@ #include #include #include -#include #include #include #include @@ -565,9 +564,11 @@ void Table::remove_recursive(CascadeState& cascade_state) cascade_state.send_notifications(); for (auto& l : cascade_state.m_to_be_nullified) { - group->get_table(l.origin_table) - ->get_object(l.origin_key) - .nullify_link(l.origin_col_key, l.old_target_link); + Obj obj = group->get_table(l.origin_table)->try_get_object(l.origin_key); + REALM_ASSERT_DEBUG(obj); + if (obj) { + std::move(obj).nullify_link(l.origin_col_key, l.old_target_link); + } } cascade_state.m_to_be_nullified.clear(); diff --git a/src/realm/table_view.cpp b/src/realm/table_view.cpp index e95db832cb7..0c5e4c7cff6 100644 --- a/src/realm/table_view.cpp +++ b/src/realm/table_view.cpp @@ -76,9 +76,7 @@ TableView::TableView(TableView& src, Transaction* tr, PayloadPolicy policy_mode) } if (was_in_sync) - m_last_seen_versions = get_dependency_versions(); - else - m_last_seen_versions.clear(); + get_dependencies(m_last_seen_versions); // don't use methods which throw after this point...or m_table_view_key_values will leak if (policy_mode == PayloadPolicy::Copy && src.m_key_values.is_attached()) { @@ -333,6 +331,7 @@ bool TableView::depends_on_deleted_object() const void TableView::get_dependencies(TableVersions& ret) const { + auto table = m_table ? m_table.unchecked_ptr() : nullptr; if (m_source_column_key && m_linked_obj) { // m_source_column_key is set when this TableView was created by Table::get_backlink_view(). if (auto linked_table = m_linked_obj.get_table()) { @@ -344,18 +343,18 @@ void TableView::get_dependencies(TableVersions& ret) const } else { // This TableView was created by Table::get_distinct_view() or get_sorted_view() on collections - ret.emplace_back(m_table->get_key(), m_table->get_content_version()); + ret.emplace_back(table->get_key(), table->get_content_version()); } // Finally add dependencies from sort/distinct - if (m_table) { - m_descriptor_ordering.get_versions(m_table->get_parent_group(), ret); + if (table) { + m_descriptor_ordering.get_versions(table->get_parent_group(), ret); } } bool TableView::is_in_sync() const { - return !m_table ? false : m_last_seen_versions == get_dependency_versions(); + return m_table && !has_changed(); } void TableView::sync_if_needed() const @@ -495,7 +494,7 @@ void TableView::do_sync() do_sort(m_descriptor_ordering); - m_last_seen_versions = get_dependency_versions(); + get_dependencies(m_last_seen_versions); } void TableView::do_sort(const DescriptorOrdering& ordering) diff --git a/src/realm/table_view.hpp b/src/realm/table_view.hpp index 02eb796c710..dc393f0a658 100644 --- a/src/realm/table_view.hpp +++ b/src/realm/table_view.hpp @@ -339,7 +339,7 @@ class TableView : public ObjList { // Remove rows that are duplicated with respect to the column set passed as argument. // distinct() will preserve the original order of the row pointers, also if the order is a result of sort() - // If two rows are indentical (for the given set of distinct-columns), then the last row is removed. + // If two rows are identical (for the given set of distinct-columns), then the last row is removed. // You can call sync_if_needed() to update the distinct view, just like you can for a sorted view. // Each time you call distinct() it will compound on the previous calls void distinct(ColKey column); diff --git a/src/realm/util/features.h b/src/realm/util/features.h index 7bb1bc4c8c6..831ae320aaf 100644 --- a/src/realm/util/features.h +++ b/src/realm/util/features.h @@ -211,8 +211,13 @@ #if defined ANDROID || defined __ANDROID_API__ #define REALM_ANDROID 1 +#define REALM_LINUX 0 +#elif defined(__linux__) +#define REALM_ANDROID 0 +#define REALM_LINUX 1 #else #define REALM_ANDROID 0 +#define REALM_LINUX 0 #endif #if defined _WIN32 diff --git a/src/realm/util/platform_info.hpp b/src/realm/util/platform_info.hpp index 58c7f529d5c..275cfb19af4 100644 --- a/src/realm/util/platform_info.hpp +++ b/src/realm/util/platform_info.hpp @@ -36,6 +36,45 @@ inline std::string get_platform_info() info.machine); // Throws } +inline std::string get_library_platform() +{ +#if REALM_ANDROID + return "Android"; +#elif REALM_WINDOWS + return "Windows"; +#elif REALM_UWP + return "UWP"; +#elif REALM_MACCATALYST // test Catalyst first because it's a subset of iOS + return "Mac Catalyst"; +#elif REALM_IOS + return "iOS"; +#elif REALM_TVOS + return "tvOS"; +#elif REALM_WATCHOS + return "watchOS"; +#elif REALM_PLATFORM_APPLE + return "macOS"; +#elif REALM_LINUX + return "Linux"; +#endif + + return "unknown"; +} + +inline std::string get_library_cpu_arch() +{ +#if REALM_ARCHITECTURE_ARM32 + return "arm"; +#elif REALM_ARCHITECTURE_ARM64 + return "arm64"; +#elif REALM_ARCHITECTURE_X86_32 + return "x86"; +#elif REALM_ARCHITECTURE_X86_64 + return "x86_64"; +#endif + + return "unknown"; +} } // namespace util } // namespace realm diff --git a/src/realm/utilities.hpp b/src/realm/utilities.hpp index 2358735fca1..5875bff7698 100644 --- a/src/realm/utilities.hpp +++ b/src/realm/utilities.hpp @@ -361,31 +361,6 @@ constexpr inline size_t round_down(size_t p, size_t align) } -template -struct Wrap { - Wrap(const T& v) - : m_value(v) - { - } - operator T() const - { - return m_value; - } - -private: - T m_value; -}; - -// PlacementDelete is intended for use with std::unique_ptr when it holds an object allocated with -// placement new. It simply calls the object's destructor without freeing the memory. -struct PlacementDelete { - template - void operator()(T* v) const - { - v->~T(); - } -}; - #ifdef _WIN32 typedef HANDLE FileDesc; #else diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8e8e0391103..e0429271a0c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -121,6 +121,12 @@ set(FUZZY_TEST_SOURCES fuzz_group.cpp) set(CORE_TESTS ${CORE_TEST_SOURCES} ${LARGE_TEST_SOURCES} ${FUZZY_TEST_SOURCES}) +set_source_files_properties(test_query_geo.cpp PROPERTIES + INCLUDE_DIRECTORIES "${RealmCore_SOURCE_DIR}/src/external" + # the only flag not supported with pragma diagnostic disable in src file by gcc until 13. + COMPILE_FLAGS "$<$: -Wno-unknown-pragmas>" +) + # FIXME: Benchmarks if (MSVC) diff --git a/test/benchmark-common-tasks/CMakeLists.txt b/test/benchmark-common-tasks/CMakeLists.txt index e39b2c876a1..9d7d9fa7c9c 100644 --- a/test/benchmark-common-tasks/CMakeLists.txt +++ b/test/benchmark-common-tasks/CMakeLists.txt @@ -1,4 +1,4 @@ add_executable(realm-benchmark-common-tasks main.cpp) -target_link_libraries(realm-benchmark-common-tasks TestUtil) +target_link_libraries(realm-benchmark-common-tasks TestUtil QueryParser) add_dependencies(benchmarks realm-benchmark-common-tasks) diff --git a/test/benchmark-common-tasks/main.cpp b/test/benchmark-common-tasks/main.cpp index 6bd355cd3e8..5fd45eec3ba 100644 --- a/test/benchmark-common-tasks/main.cpp +++ b/test/benchmark-common-tasks/main.cpp @@ -23,6 +23,9 @@ #include #include +#if REALM_ENABLE_GEOSPATIAL +#include +#endif #include #include @@ -40,6 +43,8 @@ using namespace realm; using namespace realm::util; using namespace realm::test_util; +static std::set g_bench_filter; + namespace { // not smaller than 100.000 or the UID based benchmarks has to be modified! #define BASE_SIZE 200000 @@ -246,6 +251,60 @@ struct BenchmarkWithStringsManyDup : BenchmarkWithStringsTable { } }; +struct BenchmarkLongStringsManyDup : BenchmarkWithStringsTable { + void before_all(DBRef group) + { + const char* strings[] = { + "An object database (also object-oriented database management system) is a database management system in " + "which information is represented in the form of objects as used in object-oriented programming. Object " + "databases are different from relational databases which are table-oriented. Object-relational databases " + "are a hybrid of both approaches.", + "Object database management systems grew out of research during the early to mid-1970s into having " + "intrinsic database management support for graph-structured objects. The term 'object-oriented database " + "system' first appeared around 1985.[4] Notable research projects included Encore-Ob/Server (Brown " + "University), EXODUS (University of Wisconsin–Madison), IRIS (Hewlett-Packard), ODE (Bell Labs), ORION " + "(Microelectronics and Computer Technology Corporation or MCC), Vodak (GMD-IPSI), and Zeitgeist (Texas " + "Instruments). The ORION project had more published papers than any of the other efforts. Won Kim of MCC " + "compiled the best of those papers in a book published by The MIT Press.", + "Early commercial products included Gemstone (Servio Logic, name changed to GemStone Systems), Gbase " + "(Graphael), and Vbase (Ontologic). The early to mid-1990s saw additional commercial products enter the " + "market. These included ITASCA (Itasca Systems), Jasmine (Fujitsu, marketed by Computer Associates), " + "Matisse (Matisse Software), Objectivity/DB (Objectivity, Inc.), ObjectStore (Progress Software, " + "acquired from eXcelon which was originally Object Design), ONTOS (Ontos, Inc., name changed from " + "Ontologic), O2[6] (O2 Technology, merged with several companies, acquired by Informix, which was in " + "turn acquired by IBM), POET (now FastObjects from Versant which acquired Poet Software), Versant Object " + "Database (Versant Corporation), VOSS (Logic Arts) and JADE (Jade Software Corporation). Some of these " + "products remain on the market and have been joined by new open source and commercial products such as " + "InterSystems Caché.", + "As the usage of web-based technology increases with the implementation of Intranets and extranets, " + "companies have a vested interest in OODBMSs to display their complex data. Using a DBMS that has been " + "specifically designed to store data as objects gives an advantage to those companies that are geared " + "towards multimedia presentation or organizations that utilize computer-aided design (CAD).[3]", + "Object database management systems added the concept of persistence to object programming languages. " + "The early commercial products were integrated with various languages: GemStone (Smalltalk), Gbase " + "(LISP), Vbase (COP) and VOSS (Virtual Object Storage System for Smalltalk). For much of the 1990s, C++ " + "dominated the commercial object database management market. Vendors added Java in the late 1990s and " + "more recently, C#.", + "L’archive ouverte pluridisciplinaire HAL, est destinée au dépôt et à la diffusion de documents " + "scientifiques de niveau recherche, publiés ou non, émanant des établissements d’enseignement et de " + "recherche français ou étrangers, des laboratoires publics ou privés.", + "object object object object object duplicates", + }; + + BenchmarkWithStringsTable::before_all(group); + WriteTransaction tr(group); + TableRef t = tr.get_table(name()); + Random r; + for (size_t i = 0; i < BASE_SIZE; ++i) { + Obj obj = t->create_object(); + obj.set(m_col, strings[i % 7]); + m_keys.push_back(obj.get_key()); + } + t->add_fulltext_index(m_col); + tr.commit(); + } +}; + struct BenchmarkFindAllStringFewDupes : BenchmarkWithStringsFewDup { const char* name() const { @@ -272,6 +331,19 @@ struct BenchmarkFindAllStringManyDupes : BenchmarkWithStringsManyDup { } }; +struct BenchmarkFindAllFulltextStringManyDupes : BenchmarkLongStringsManyDup { + const char* name() const + { + return "FindAllFulltextStringManyDupes"; + } + + void operator()(DBRef) + { + ConstTableRef table = m_table; + TableView view = table->where().fulltext(m_col, "object gemstone").find_all(); + } +}; + template struct BenchmarkCountStringManyDupes : BenchmarkWithStringsManyDup { const char* name() const @@ -1846,6 +1918,197 @@ struct TransactionDuplicate : Benchmark { void after_each(DBRef) {} }; +#if REALM_ENABLE_GEOSPATIAL + +struct BenchmarkWithGeospatial : Benchmark { + std::string loc_name() const + { + return std::string(name()) + "Location"; + } + + void before_all(DBRef group) override + { + WriteTransaction tr(group); + + bool was_added = false; + auto t = tr.get_or_add_table(name(), Table::Type::TopLevel, &was_added); + + if (was_added) { + auto loc = tr.add_table(loc_name(), Table::Type::Embedded); + loc->add_column(type_String, "type"); + loc->add_column_list(type_Double, "coordinates"); + m_col = t->add_column(*loc, "location"); + } + + tr.commit(); + } + + void after_all(DBRef group) override + { + (void)group; + WriteTransaction tr(group); + tr.get_group().remove_table(name()); + tr.get_group().remove_table(loc_name()); + tr.commit(); + } + + void assign_points(TableRef table) + { + for (size_t i = 0; i < BASE_SIZE; ++i) { + double lon = ((double)i / BASE_SIZE) * 360 - 180; + double lat = (i / 1000.0) / (BASE_SIZE / 1000.0) * 180 - 90; + table->get_object(i).set(m_col, Geospatial{GeoPoint{lon, lat, 42.24}}); + } + } + + void add_records(TableRef table, bool assign_value = true) + { + for (size_t i = 0; i < BASE_SIZE; ++i) + table->create_object(); + + if (assign_value) + assign_points(table); + } +}; + +struct BenchmarkWithGeoPoints : BenchmarkWithGeospatial { + void before_all(DBRef group) override + { + BenchmarkWithGeospatial::before_all(group); + WriteTransaction tr(group); + add_records(tr.get_table(name())); + tr.commit(); + } +}; + +struct BenchmarkAssignGeoPoints : BenchmarkWithGeoPoints { + const char* name() const override + { + return "AssignGeoPoints"; + } + + void operator()(DBRef) override + { + assign_points(m_table); + } +}; + +struct BenchmarkAssignGeoPointsFromNull : BenchmarkWithGeoPoints { + const char* name() const override + { + return "AssignGeoPointsFromNull"; + } + + void before_all(DBRef group) override + { + BenchmarkWithGeospatial::before_all(group); + WriteTransaction tr(group); + add_records(tr.get_table(name()), false); + tr.commit(); + } + + void operator()(DBRef) override + { + assign_points(m_table); + } +}; + +struct BenchmarkFetchGeoPoints : BenchmarkWithGeoPoints { + const char* name() const override + { + return "FetchGeoPoints"; + } + + void operator()(DBRef) override + { + for (size_t i = 0; i < BASE_SIZE; ++i) { + auto g = m_table->get_object(i).get(m_col); + if (!g.is_valid().is_ok() || g.get_type() != Geospatial::Type::Point) + throw std::logic_error("Invalid GeoPoint"); + } + } +}; + +struct BenchmarkGeoPointsWithinBox : BenchmarkWithGeoPoints { + const char* name() const override + { + return "GeoPointsWithinBox"; + } + + void operator()(DBRef) override + { + auto geometry = GeoBox{{-34.0, -34.0}, {42.0, 42.0}}; + m_table->column(m_col).geo_within(geometry).count(); + } +}; + +struct BenchmarkGeoPointsWithinBoxRQL : BenchmarkWithGeoPoints { + const char* name() const override + { + return "GeoPointsWithinBoxRQL"; + } + + void operator()(DBRef) override + { + m_table->query("location geoWithin geoBox([-34.0, -34.0], [42.0, 42.0])").count(); + } +}; + +struct BenchmarkGeoPointsWithinCircle : BenchmarkWithGeoPoints { + const char* name() const override + { + return "GeoPointsWithinCircle"; + } + + void operator()(DBRef) override + { + auto geometry = GeoCircle::from_kms(5000, {42.0, 42.0}); + m_table->column(m_col).geo_within(geometry).count(); + } +}; + +struct BenchmarkGeoPointsWithinCircleRQL : BenchmarkWithGeoPoints { + const char* name() const override + { + return "GeoPointsWithinCircleRQL"; + } + + void operator()(DBRef) override + { + m_table->query("location geoWithin geoCircle([42.0, 42.0], 0.78393252)").count(); + } +}; + +struct BenchmarkGeoPointsWithinPolygon : BenchmarkWithGeoPoints { + const char* name() const override + { + return "GeoPointsWithinPolygon"; + } + + void operator()(DBRef) override + { + GeoPolygon geometry{{{-24, -24}, {-34, 34}, {44, 44}, {-55, 55}, {-24, -24}}}; + m_table->column(m_col).geo_within(geometry).count(); + } +}; + +struct BenchmarkGeoPointsWithinPolygonRQL : BenchmarkWithGeoPoints { + const char* name() const override + { + return "GeoPointsWithinPolygonRQL"; + } + + void operator()(DBRef) override + { + m_table + ->query("location geoWithin geoPolygon({[-24.0, -24.0], [-34.0, 34.0], [44.0, 44.0], [-55.0, 55], " + "[-24.0, -24.0]})") + .count(); + } +}; + +#endif + const char* to_lead_cstr(DBOptions::Durability level) { switch (level) { @@ -1909,7 +2172,11 @@ void run_benchmark(BenchmarkResults& results, bool force_full = false) for (auto it = configs.begin(); it != configs.end(); ++it) { DBOptions::Durability level = it->first; const char* key = it->second; + B benchmark; + if (!g_bench_filter.empty() && g_bench_filter.find(benchmark.name()) == g_bench_filter.end()) + return; + benchmark.m_durability = level; benchmark.m_encryption_key = key; @@ -2017,6 +2284,7 @@ int benchmark_common_tasks_main() // queries / searching BENCH(BenchmarkFindAllStringFewDupes); BENCH(BenchmarkFindAllStringManyDupes); + BENCH(BenchmarkFindAllFulltextStringManyDupes); BENCH(BenchmarkFindFirstStringFewDupes); BENCH(BenchmarkFindFirstStringManyDupes); BENCH(BenchmarkCountStringManyDupes); @@ -2071,6 +2339,18 @@ int benchmark_common_tasks_main() BENCH(TransactionDuplicate); +#if REALM_ENABLE_GEOSPATIAL + BENCH(BenchmarkAssignGeoPoints); + BENCH(BenchmarkAssignGeoPointsFromNull); + BENCH(BenchmarkFetchGeoPoints); + BENCH(BenchmarkGeoPointsWithinBox); + BENCH(BenchmarkGeoPointsWithinBoxRQL); + BENCH(BenchmarkGeoPointsWithinCircle); + BENCH(BenchmarkGeoPointsWithinCircleRQL); + BENCH(BenchmarkGeoPointsWithinPolygon); + BENCH(BenchmarkGeoPointsWithinPolygonRQL); +#endif + #undef BENCH #undef BENCH2 return 0; @@ -2081,7 +2361,7 @@ int main(int argc, const char** argv) if (argc > 1) { std::string arg_path = argv[1]; if (arg_path == "-h" || arg_path == "--help") { - std::cout << "Usage: " << argv[0] << " [-h|--help] [PATH]" << std::endl + std::cout << "Usage: " << argv[0] << " [-h|--help] [PATH] [NAMES]" << std::endl << "Run the common tasks benchmark test application." << std::endl << "Results are placed in the executable directory by default." << std::endl << std::endl @@ -2089,6 +2369,7 @@ int main(int argc, const char** argv) << " -h, --help display this help" << std::endl << " PATH alternate path to store the results files;" << std::endl << " this path should end with a slash." << std::endl + << " NAMES benchmark names to run (',' separated)" << std::endl << std::endl; return 1; } @@ -2096,5 +2377,17 @@ int main(int argc, const char** argv) if (!initialize_test_path(argc, argv)) return 1; + + if (argc > 2) { + std::string filter = argv[2]; + for (size_t i = 0, j = 0, len = filter.size(); i <= len; ++i) { + if (i == len || filter[i] == ',') { + if (j < i) + g_bench_filter.insert(filter.substr(j, i - j)); + j = i + 1; + } + } + } + return benchmark_common_tasks_main(); } diff --git a/test/object-store/CMakeLists.txt b/test/object-store/CMakeLists.txt index 51725920af3..d4977596e49 100644 --- a/test/object-store/CMakeLists.txt +++ b/test/object-store/CMakeLists.txt @@ -119,9 +119,12 @@ if(REALM_TEST_LOGGING) ) if(REALM_TEST_LOGGING_LEVEL) + message(STATUS "Test logging level: ${REALM_TEST_LOGGING_LEVEL}") target_compile_definitions(ObjectStoreTests PRIVATE TEST_LOGGING_LEVEL=${REALM_TEST_LOGGING_LEVEL} ) + else() + message(STATUS "Test logging enabled") endif() endif() diff --git a/test/object-store/benchmarks/object.cpp b/test/object-store/benchmarks/object.cpp index 5c79bec4c65..834991ca98c 100644 --- a/test/object-store/benchmarks/object.cpp +++ b/test/object-store/benchmarks/object.cpp @@ -32,7 +32,6 @@ #include #include -#include #include #include diff --git a/test/object-store/benchmarks/results.cpp b/test/object-store/benchmarks/results.cpp index 99dde148869..d40398133e8 100644 --- a/test/object-store/benchmarks/results.cpp +++ b/test/object-store/benchmarks/results.cpp @@ -25,10 +25,10 @@ #include #include #include +#include #include #include -#include #include using namespace realm; @@ -419,3 +419,98 @@ TEST_CASE("aggregates") { return object_store::Dictionary(realm, int_dict).sum(); }; } + +TEST_CASE("Benchmark sectioned results", "[benchmark]") { + InMemoryTestFile config; + config.automatic_change_notifications = false; + config.schema = Schema{{"object", {{"value", PropertyType::Int}}}}; + + auto realm = Realm::get_shared_realm(config); + auto& coordinator = *_impl::RealmCoordinator::get_coordinator(config.path); + auto table = realm->read_group().get_table("class_object"); + auto col = table->get_column_key("value"); + + realm->begin_transaction(); + for (int64_t i = 0; i < 100'000; ++i) { + table->create_object().set_all(i); + } + realm->commit_transaction(); + + size_t section_count = GENERATE(1, 10, 1000, 10000); + auto key_fn = [&](Mixed value, const std::shared_ptr&) -> Mixed { + return table->get_object(value.get_link().get_obj_key()).get(col) % int64_t(section_count); + }; + + BENCHMARK("create and get section count") { + auto size = Results(realm, table).sectioned_results(key_fn).size(); + REQUIRE(size == section_count); + }; + + BENCHMARK_ADVANCED("iterate directly")(Catch::Benchmark::Chronometer meter) + { + auto results = Results(realm, table).sectioned_results(key_fn); + static_cast(results.size()); // evaluate sections + meter.measure([&] { + for (size_t i = 0, size = results.size(); i < size; ++i) { + auto section = results[i]; + for (size_t j = 0, size = section.size(); j < size; ++j) { + static_cast(section[j]); + } + } + }); + }; + + BENCHMARK_ADVANCED("iterate over a snapshot")(Catch::Benchmark::Chronometer meter) + { + auto results = Results(realm, table).sectioned_results(key_fn); + static_cast(results.size()); // evaluate sections + meter.measure([&] { + auto snapshot = results.snapshot(); + for (size_t i = 0, size = snapshot.size(); i < size; ++i) { + auto section = snapshot[i]; + for (size_t j = 0, size = section.size(); j < size; ++j) { + static_cast(section[j]); + } + } + }); + }; + + BENCHMARK_ADVANCED("change notification")(Catch::Benchmark::Chronometer meter) + { + auto results = Results(realm, table).sectioned_results(key_fn); + auto token = results.add_notification_callback([](auto&&) {}); + coordinator.on_change(); + realm->notify(); + + auto col = table->get_column_key("value"); + meter.measure([&] { + realm->begin_transaction(); + for (auto& obj : *table) { + obj.set(col, obj.get(col)); + } + realm->commit_transaction(); + coordinator.on_change(); + realm->notify(); + }); + }; + + BENCHMARK_ADVANCED("single section change notification")(Catch::Benchmark::Chronometer meter) + { + auto results = Results(realm, table).sectioned_results(key_fn); + auto section = results[section_count > 5 ? 5 : 0]; + auto token = section.add_notification_callback([](auto&&) {}); + coordinator.on_change(); + realm->notify(); + + auto col = table->get_column_key("value"); + meter.measure([&] { + realm->begin_transaction(); + for (auto& obj : *table) { + obj.set(col, obj.get(col)); + } + realm->commit_transaction(); + coordinator.on_change(); + realm->notify(); + }); + }; +} diff --git a/test/object-store/c_api/c_api.cpp b/test/object-store/c_api/c_api.cpp index 74032732b9d..22d16c51649 100644 --- a/test/object-store/c_api/c_api.cpp +++ b/test/object-store/c_api/c_api.cpp @@ -288,8 +288,9 @@ class CApiUnitTestTransport : public app::GenericNetworkTransport { std::string m_provider_type; public: - CApiUnitTestTransport(const std::string& provider_type = "anon-user") - : m_provider_type(provider_type) + CApiUnitTestTransport(const std::string& provider_type = {}, uint64_t request_timeout = 60000) + : m_provider_type(provider_type.empty() ? "anon-user" : provider_type) + , request_timeout(request_timeout) { profile_0 = nlohmann::json({{"name", "profile_0_name"}, {"first_name", "profile_0_first_name"}, @@ -302,6 +303,11 @@ class CApiUnitTestTransport : public app::GenericNetworkTransport { {"max_age", "profile_0_max_age"}}); } + explicit CApiUnitTestTransport(const uint64_t request_timeout) + : CApiUnitTestTransport({}, request_timeout) + { + } + void set_provider_type(const std::string& provider_type) { m_provider_type = provider_type; @@ -316,6 +322,7 @@ class CApiUnitTestTransport : public app::GenericNetworkTransport { const std::string identity_0_id = "eflkjf393flkj33fjf3"; const std::string identity_1_id = "aewfjklewfwoifejjef"; nlohmann::json profile_0; + uint64_t request_timeout; private: @@ -343,18 +350,19 @@ class CApiUnitTestTransport : public app::GenericNetworkTransport { nlohmann::json({{"device", {{"appId", "app_id_123"}, {"appVersion", "some_app_version"}, - {"platform", "some_platform_name"}, + {"platform", util::get_library_platform()}, {"platformVersion", "some_platform_version"}, {"sdk", "some_sdk_name"}, {"sdkVersion", "some_sdk_version"}, - {"cpuArch", "some_cpu_arch"}, + {"cpuArch", util::get_library_cpu_arch()}, {"deviceName", "some_device_name"}, {"deviceVersion", "some_device_version"}, {"frameworkName", "some_framework_name"}, {"frameworkVersion", "some_framework_version"}, - {"coreVersion", REALM_VERSION_STRING}}}})); + {"coreVersion", REALM_VERSION_STRING}, + {"bundleId", "some_bundle_id"}}}})); - CHECK(request.timeout_ms == 60000); + CHECK(request.timeout_ms == request_timeout); std::string response = nlohmann::json({{"access_token", access_token}, {"refresh_token", access_token}, @@ -613,7 +621,9 @@ TEST_CASE("C API (non-database)", "[c_api]") { #if REALM_ENABLE_AUTH_TESTS SECTION("realm_app_config_t") { - std::shared_ptr transport = std::make_shared(); + const uint64_t request_timeout = 2500; + std::shared_ptr transport = + std::make_shared(request_timeout); auto http_transport = realm_http_transport(transport); auto app_config = cptr(realm_app_config_new("app_id_123", &http_transport)); CHECK(app_config.get() != nullptr); @@ -629,11 +639,8 @@ TEST_CASE("C API (non-database)", "[c_api]") { realm_app_config_set_local_app_version(app_config.get(), "some_app_version"); CHECK(app_config->local_app_version == "some_app_version"); - realm_app_config_set_default_request_timeout(app_config.get(), 2500); - CHECK(app_config->default_request_timeout_ms == 2500); - - realm_app_config_set_platform(app_config.get(), "some_platform_name"); - CHECK(app_config->device_info.platform == "some_platform_name"); + realm_app_config_set_default_request_timeout(app_config.get(), request_timeout); + CHECK(app_config->default_request_timeout_ms == request_timeout); realm_app_config_set_platform_version(app_config.get(), "some_platform_version"); CHECK(app_config->device_info.platform_version == "some_platform_version"); @@ -644,9 +651,6 @@ TEST_CASE("C API (non-database)", "[c_api]") { realm_app_config_set_sdk(app_config.get(), "some_sdk_name"); CHECK(app_config->device_info.sdk == "some_sdk_name"); - realm_app_config_set_cpu_arch(app_config.get(), "some_cpu_arch"); - CHECK(app_config->device_info.cpu_arch == "some_cpu_arch"); - realm_app_config_set_device_name(app_config.get(), "some_device_name"); CHECK(app_config->device_info.device_name == "some_device_name"); @@ -659,6 +663,9 @@ TEST_CASE("C API (non-database)", "[c_api]") { realm_app_config_set_framework_version(app_config.get(), "some_framework_version"); CHECK(app_config->device_info.framework_version == "some_framework_version"); + realm_app_config_set_bundle_id(app_config.get(), "some_bundle_id"); + CHECK(app_config->device_info.bundle_id == "some_bundle_id"); + auto test_app = std::make_shared(*app_config); auto credentials = app::AppCredentials::anonymous(); // Verify the values above are included in the login request @@ -5618,6 +5625,7 @@ TEST_CASE("app: flx-sync compensating writes C API support", "[c_api][flx][sync] FLXSyncTestHarness harness("c_api_comp_writes"); create_user_and_log_in(harness.app()); SyncTestFile test_config(harness.app()->current_user(), harness.schema(), realm::SyncConfig::FLXSyncEnabled{}); + test_config.sync_config = std::make_shared(*test_config.sync_config); realm_sync_config_t* sync_config = static_cast(test_config.sync_config.get()); struct TestState { diff --git a/test/object-store/dictionary.cpp b/test/object-store/dictionary.cpp index 3cfe681b180..42a38982292 100644 --- a/test/object-store/dictionary.cpp +++ b/test/object-store/dictionary.cpp @@ -33,9 +33,6 @@ #include #include -#include -#include - using namespace realm; using namespace realm::util; diff --git a/test/object-store/frozen_objects.cpp b/test/object-store/frozen_objects.cpp index 1902cc10eaf..d984c7fb092 100644 --- a/test/object-store/frozen_objects.cpp +++ b/test/object-store/frozen_objects.cpp @@ -345,32 +345,48 @@ TEST_CASE("Freeze Results", "[freeze_results]") { SECTION("Results after source remove") { Results results; - std::function do_remove; SECTION("Results on collection") { Obj obj; write([&]() { obj = create_object(42, true); }); + auto key = obj.get_key(); SECTION("Dictionary") { results = Results(realm, obj.get_dictionary_ptr(int_dict_col)); + write([&]() { + table->remove_object(key); + }); + // If Results is based on collection of primitives, the removal of + // the collection should invalidate Results. + VERIFY_STALE_RESULTS(results, realm); } SECTION("Links") { results = Results(realm, obj.get_linklist_ptr(object_link_col)); + auto snapshot = results.snapshot(); + // If Results is based on collection of objects, the removal of + // the collection should not invalidate Results as the table still exists. + write([&]() { + table->remove_object(key); + // Snapshot should not be affected by the removed collection + REQUIRE(snapshot.size() == 5); + }); + REQUIRE(results.is_valid()); + REQUIRE(results.size() == 0); + auto frozen = results.freeze(realm); + REQUIRE(frozen.is_valid()); + REQUIRE(frozen.size() == 0); } - - do_remove = [&, key = obj.get_key()] { - table->remove_object(key); - }; } SECTION("Results on table") { results = Results(realm, table); - do_remove = [&] { + write([&]() { realm->read_group().remove_table(table->get_key()); - }; + }); + VERIFY_STALE_RESULTS(results, realm); } // FIXME? the test itself passes but crashes on teardown in notifier thread @@ -381,10 +397,6 @@ TEST_CASE("Freeze Results", "[freeze_results]") { realm->read_group().remove_table(table->get_key()); }; } */ - - VERIFY_VALID_RESULTS(results, realm); - write(do_remove); - VERIFY_STALE_RESULTS(results, realm); } } diff --git a/test/object-store/realm.cpp b/test/object-store/realm.cpp index 9c945808c71..aa064738403 100644 --- a/test/object-store/realm.cpp +++ b/test/object-store/realm.cpp @@ -1895,6 +1895,7 @@ TEST_CASE("SharedRealm: async writes") { "an error"); REQUIRE(realm->is_closed()); } +#endif SECTION("exception thrown from async commit completion callback with error handler") { Realm::AsyncHandle h; realm->set_async_error_handler([&](Realm::AsyncHandle handle, std::exception_ptr error) { @@ -1912,6 +1913,7 @@ TEST_CASE("SharedRealm: async writes") { wait_for_done(); verify_persisted_count(1); } +#ifndef _WIN32 SECTION("exception thrown from async commit completion callback without error handler") { realm->begin_transaction(); table->create_object(); @@ -1924,6 +1926,7 @@ TEST_CASE("SharedRealm: async writes") { "an error"); REQUIRE(table->size() == 1); } +#endif if (_impl::SimulatedFailure::is_enabled()) { SECTION("error in the synchronous part of async commit") { @@ -2340,15 +2343,38 @@ TEST_CASE("SharedRealm: async writes") { REQUIRE(table->size() == 6); } + SECTION("async writes which would run inside sync writes are deferred") { + realm->async_begin_transaction([&] { + done = true; + }); + + // Wait for the background thread to hold the write lock (without letting + // the event loop run so that the scheduled task isn't run) + DBOptions options; + options.encryption_key = config.encryption_key.data(); + auto db = DB::create(make_in_realm_history(), config.path, options); + while (db->start_write(true)) + millisleep(1); + + realm->begin_transaction(); + + // Invoke the pending callback + util::EventLoop::main().run_pending(); + // Should not have run the async write block + REQUIRE(done == false); + + // Should run the async write block once the synchronous transaction is done + realm->cancel_transaction(); + REQUIRE(done == false); + util::EventLoop::main().run_pending(); + REQUIRE(done == true); + } + util::EventLoop::main().run_until([&] { return !realm || !realm->has_pending_async_work(); }); -#endif - -#ifdef _WIN32 _impl::RealmCoordinator::clear_all_caches(); -#endif } // Our libuv scheduler currently does not support background threads, so we can // only run this on apple platforms diff --git a/test/object-store/results.cpp b/test/object-store/results.cpp index f52dc725bd1..cfe6b8ef532 100644 --- a/test/object-store/results.cpp +++ b/test/object-store/results.cpp @@ -36,7 +36,6 @@ #include #include -#include #include #if REALM_ENABLE_SYNC diff --git a/test/object-store/sectioned_results.cpp b/test/object-store/sectioned_results.cpp index 4eeea866f82..d625a43babc 100644 --- a/test/object-store/sectioned_results.cpp +++ b/test/object-store/sectioned_results.cpp @@ -482,31 +482,20 @@ TEST_CASE("sectioned results", "[sectioned_results]") { auto coordinator = _impl::RealmCoordinator::get_coordinator(config.path); auto table = r->read_group().get_table("class_object"); auto name_col = table->get_column_key("name_col"); - auto int_col = table->get_column_key("int_col"); auto array_string_col = table->get_column_key("array_string_col"); r->begin_transaction(); - auto o1 = table->create_object(); - o1.set(name_col, "banana"); - o1.set(int_col, 3); - auto o2 = table->create_object(); - o2.set(name_col, "apricot"); - o2.set(int_col, 2); - auto o3 = table->create_object(); - o3.set(name_col, "apple"); - o3.set(int_col, 1); - auto o4 = table->create_object(); - o4.set(name_col, "orange"); - o4.set(int_col, 2); - auto o5 = table->create_object(); - o5.set(name_col, "apples"); - o5.set(int_col, 3); + table->create_object().set_all("banana", 3); + table->create_object().set_all("apricot", 2); + table->create_object().set_all("apple", 1); + table->create_object().set_all("orange", 2); + auto o5 = table->create_object().set_all("apples", 3); r->commit_transaction(); Results results(r, table); auto sorted = results.sort({{"name_col", true}}); int algo_run_count = 0; - auto sectioned_results = sorted.sectioned_results([&algo_run_count](Mixed value, SharedRealm realm) { + auto sectioned_results = sorted.sectioned_results([&algo_run_count](Mixed value, const SharedRealm& realm) { algo_run_count++; auto obj = Object(realm, value.get_link()); auto v = obj.get_column_value("name_col"); @@ -783,22 +772,19 @@ TEST_CASE("sectioned results", "[sectioned_results]") { advance_and_notify(*r); REQUIRE(algo_run_count == 11); - REQUIRE(changes.sections_to_insert.count() == 3); - REQUIRE(changes.sections_to_delete.count() == 0); + REQUIRE(changes.sections_to_delete.empty()); REQUIRE_INDICES(changes.sections_to_insert, 2, 3, 5); - REQUIRE(changes.insertions.size() == 4); + REQUIRE(changes.insertions.size() == 6); // Section 0 is 'A' REQUIRE_INDICES(changes.insertions[0], 0); - REQUIRE(changes.insertions[0].count() == 1); + REQUIRE(changes.insertions[1].empty()); // Section 2 is 'C' - REQUIRE(changes.insertions[2].count() == 2); REQUIRE_INDICES(changes.insertions[2], 0, 1); // Section 3 is 'M' - REQUIRE(changes.insertions[3].count() == 1); REQUIRE_INDICES(changes.insertions[3], 0); + REQUIRE(changes.insertions[4].empty()); // Section 5 is 'S' - REQUIRE(changes.insertions[5].count() == 2); REQUIRE_INDICES(changes.insertions[5], 0, 1); REQUIRE(changes.modifications.empty()); REQUIRE(changes.deletions.empty()); @@ -809,10 +795,15 @@ TEST_CASE("sectioned results", "[sectioned_results]") { o4.set(name_col, "stocksss"); r->commit_transaction(); advance_and_notify(*r); - REQUIRE(changes.sections_to_insert.count() == 0); - REQUIRE(changes.sections_to_delete.count() == 0); + REQUIRE(changes.sections_to_insert.empty()); + REQUIRE(changes.sections_to_delete.empty()); - REQUIRE(changes.modifications.size() == 1); + REQUIRE(changes.modifications.size() == 6); + REQUIRE(changes.modifications[0].empty()); + REQUIRE(changes.modifications[1].empty()); + REQUIRE(changes.modifications[2].empty()); + REQUIRE(changes.modifications[3].empty()); + REQUIRE(changes.modifications[4].empty()); REQUIRE_INDICES(changes.modifications[5], 1); REQUIRE(changes.insertions.empty()); REQUIRE(changes.deletions.empty()); @@ -825,10 +816,12 @@ TEST_CASE("sectioned results", "[sectioned_results]") { table->remove_object(o3.get_key()); r->commit_transaction(); advance_and_notify(*r); - REQUIRE(changes.sections_to_insert.count() == 0); + REQUIRE(changes.sections_to_insert.empty()); REQUIRE(changes.sections_to_delete.count() == 1); - REQUIRE(changes.deletions.size() == 1); + REQUIRE(changes.deletions.size() == 3); + REQUIRE(changes.deletions[0].empty()); + REQUIRE(changes.deletions[1].empty()); REQUIRE_INDICES(changes.deletions[2], 1); REQUIRE(changes.insertions.empty()); REQUIRE(changes.modifications.empty()); @@ -842,15 +835,19 @@ TEST_CASE("sectioned results", "[sectioned_results]") { o4.set(name_col, "erie"); r->commit_transaction(); advance_and_notify(*r); - REQUIRE(changes.sections_to_insert.count() == 1); - REQUIRE(changes.sections_to_delete.count() == 1); REQUIRE_INDICES(changes.sections_to_delete, 4); REQUIRE_INDICES(changes.sections_to_insert, 3); - REQUIRE(changes.deletions.size() == 1); - REQUIRE(changes.insertions.size() == 2); + REQUIRE(changes.deletions.size() == 4); + REQUIRE(changes.insertions.size() == 5); REQUIRE(changes.modifications.empty()); + REQUIRE(changes.deletions[0].empty()); + REQUIRE(changes.deletions[1].empty()); + REQUIRE(changes.deletions[2].empty()); REQUIRE_INDICES(changes.deletions[3], 0); + REQUIRE(changes.insertions[0].empty()); + REQUIRE(changes.insertions[1].empty()); + REQUIRE(changes.insertions[2].empty()); REQUIRE_INDICES(changes.insertions[3], 0, 1); REQUIRE_INDICES(changes.insertions[4], 0); REQUIRE(algo_run_count == 9); @@ -864,7 +861,6 @@ TEST_CASE("sectioned results", "[sectioned_results]") { r->commit_transaction(); advance_and_notify(*r); REQUIRE(changes.sections_to_insert.empty()); - REQUIRE(changes.sections_to_delete.count() == 1); REQUIRE_INDICES(changes.sections_to_delete, 3); REQUIRE(changes.deletions.empty()); @@ -881,7 +877,6 @@ TEST_CASE("sectioned results", "[sectioned_results]") { advance_and_notify(*r); REQUIRE(algo_run_count == 0); REQUIRE(changes.sections_to_insert.empty()); - REQUIRE(changes.sections_to_delete.count() == 4); REQUIRE_INDICES(changes.sections_to_delete, 0, 1, 2, 3); REQUIRE(changes.deletions.empty()); @@ -900,7 +895,6 @@ TEST_CASE("sectioned results", "[sectioned_results]") { r->commit_transaction(); advance_and_notify(*r); REQUIRE(algo_run_count == 7); - REQUIRE(changes.sections_to_insert.count() == 5); REQUIRE(changes.sections_to_delete.empty()); REQUIRE_INDICES(changes.sections_to_insert, 0, 1, 2, 3, 4); @@ -917,7 +911,7 @@ TEST_CASE("sectioned results", "[sectioned_results]") { r->begin_transaction(); o1.set(name_col, "banana"); o2.set(name_col, "melon"); - o3.set(name_col, "calender"); + o3.set(name_col, "calendar"); o4.set(name_col, "apricot"); o5.set(name_col, "duck"); // stays the same o6.set(name_col, "duck"); @@ -925,14 +919,12 @@ TEST_CASE("sectioned results", "[sectioned_results]") { r->commit_transaction(); advance_and_notify(*r); REQUIRE(algo_run_count == 7); - REQUIRE(changes.sections_to_insert.count() == 2); - REQUIRE(changes.sections_to_delete.count() == 2); REQUIRE_INDICES(changes.sections_to_insert, 2, 4); REQUIRE_INDICES(changes.sections_to_delete, 3, 4); REQUIRE(changes.deletions.size() == 2); REQUIRE(changes.insertions.size() == 5); - REQUIRE(changes.modifications.size() == 1); + REQUIRE(changes.modifications.size() == 3); REQUIRE_INDICES(changes.insertions[0], 0, 1); REQUIRE_INDICES(changes.insertions[1], 0); REQUIRE_INDICES(changes.insertions[2], 0); @@ -942,6 +934,8 @@ TEST_CASE("sectioned results", "[sectioned_results]") { REQUIRE_INDICES(changes.deletions[0], 0, 1, 2); REQUIRE_INDICES(changes.deletions[1], 0); + REQUIRE(changes.modifications[0].empty()); + REQUIRE(changes.modifications[1].empty()); REQUIRE_INDICES(changes.modifications[2], 0); algo_run_count = 0; @@ -957,21 +951,26 @@ TEST_CASE("sectioned results", "[sectioned_results]") { advance_and_notify(*r); REQUIRE(algo_run_count == 7); REQUIRE(changes.sections_to_insert.empty()); - REQUIRE(changes.sections_to_delete.count() == 1); REQUIRE_INDICES(changes.sections_to_delete, 1); - REQUIRE(changes.deletions.size() == 3); - REQUIRE(changes.insertions.size() == 3); - REQUIRE(changes.modifications.size() == 1); + REQUIRE(changes.deletions.size() == 5); + REQUIRE(changes.insertions.size() == 4); + REQUIRE(changes.modifications.size() == 4); REQUIRE_INDICES(changes.insertions[0], 0, 1, 2); REQUIRE_INDICES(changes.insertions[1], 0); + REQUIRE(changes.insertions[2].empty()); REQUIRE_INDICES(changes.insertions[3], 0); REQUIRE_INDICES(changes.deletions[0], 0, 1); + REQUIRE(changes.deletions[1].empty()); REQUIRE_INDICES(changes.deletions[2], 0); + REQUIRE(changes.deletions[3].empty()); REQUIRE_INDICES(changes.deletions[4], 0); + REQUIRE(changes.modifications[0].empty()); + REQUIRE(changes.modifications[1].empty()); + REQUIRE(changes.modifications[2].empty()); REQUIRE_INDICES(changes.modifications[3], 0, 1); algo_run_count = 0; @@ -986,14 +985,12 @@ TEST_CASE("sectioned results", "[sectioned_results]") { r->commit_transaction(); advance_and_notify(*r); REQUIRE(algo_run_count == 7); - REQUIRE(changes.sections_to_insert.count() == 3); - REQUIRE(changes.sections_to_delete.count() == 2); REQUIRE_INDICES(changes.sections_to_insert, 2, 3, 4); REQUIRE_INDICES(changes.sections_to_delete, 2, 3); REQUIRE(changes.deletions.size() == 1); REQUIRE(changes.insertions.size() == 5); - REQUIRE(changes.modifications.size() == 1); + REQUIRE(changes.modifications.size() == 2); REQUIRE_INDICES(changes.insertions[0], 0); REQUIRE_INDICES(changes.insertions[1], 1); @@ -1001,12 +998,13 @@ TEST_CASE("sectioned results", "[sectioned_results]") { REQUIRE_INDICES(changes.insertions[3], 0, 1); REQUIRE_INDICES(changes.insertions[4], 0); + REQUIRE(changes.modifications[0].empty()); REQUIRE_INDICES(changes.modifications[1], 0); REQUIRE_INDICES(changes.deletions[0], 0, 1, 2); } - SECTION("notifications ascending / decsending") { + SECTION("notifications ascending / descending") { // Ascending SectionedResultsChangeSet changes; auto token = sectioned_results.add_notification_callback([&](SectionedResultsChangeSet c) { @@ -1041,17 +1039,16 @@ TEST_CASE("sectioned results", "[sectioned_results]") { advance_and_notify(*r); REQUIRE(algo_run_count == 4); - REQUIRE(changes.sections_to_insert.count() == 1); - REQUIRE(changes.sections_to_delete.count() == 1); REQUIRE_INDICES(changes.sections_to_insert, 1); REQUIRE_INDICES(changes.sections_to_delete, 0); - REQUIRE(changes.deletions.size() == 1); + REQUIRE(changes.deletions.size() == 2); REQUIRE(changes.insertions.size() == 2); REQUIRE(changes.modifications.empty()); REQUIRE_INDICES(changes.insertions[0], 0, 1); REQUIRE_INDICES(changes.insertions[1], 0, 1); + REQUIRE(changes.deletions[0].empty()); REQUIRE_INDICES(changes.deletions[1], 0, 1); // Descending @@ -1091,8 +1088,6 @@ TEST_CASE("sectioned results", "[sectioned_results]") { advance_and_notify(*r); REQUIRE(algo_run_count == 4); - REQUIRE(changes.sections_to_insert.count() == 1); - REQUIRE(changes.sections_to_delete.count() == 1); REQUIRE_INDICES(changes.sections_to_insert, 0); REQUIRE_INDICES(changes.sections_to_delete, 1); @@ -1106,7 +1101,7 @@ TEST_CASE("sectioned results", "[sectioned_results]") { REQUIRE_INDICES(changes.modifications[0], 1); } - SECTION("notifications ascending / decsending primitive") { + SECTION("notifications ascending / descending primitive") { // Ascending r->begin_transaction(); auto o1 = table->create_object(); @@ -1146,17 +1141,16 @@ TEST_CASE("sectioned results", "[sectioned_results]") { advance_and_notify(*r); REQUIRE(algo_run_count == 4); - REQUIRE(changes.sections_to_insert.count() == 1); - REQUIRE(changes.sections_to_delete.count() == 1); REQUIRE_INDICES(changes.sections_to_insert, 1); REQUIRE_INDICES(changes.sections_to_delete, 0); - REQUIRE(changes.deletions.size() == 1); + REQUIRE(changes.deletions.size() == 2); REQUIRE(changes.insertions.size() == 2); REQUIRE(changes.modifications.empty()); REQUIRE_INDICES(changes.insertions[0], 0, 1); REQUIRE_INDICES(changes.insertions[1], 0, 1); + REQUIRE(changes.deletions[0].empty()); REQUIRE_INDICES(changes.deletions[1], 0, 1); // Descending @@ -1196,8 +1190,6 @@ TEST_CASE("sectioned results", "[sectioned_results]") { advance_and_notify(*r); REQUIRE(algo_run_count == 4); - REQUIRE(changes.sections_to_insert.count() == 1); - REQUIRE(changes.sections_to_delete.count() == 1); REQUIRE_INDICES(changes.sections_to_insert, 0); REQUIRE_INDICES(changes.sections_to_delete, 1); @@ -1245,7 +1237,6 @@ TEST_CASE("sectioned results", "[sectioned_results]") { REQUIRE(section1_notification_calls == 1); REQUIRE(section2_notification_calls == 0); REQUIRE(section1_changes.insertions.size() == 1); - REQUIRE(section1_changes.insertions[0].count() == 1); REQUIRE_INDICES(section1_changes.insertions[0], 0); REQUIRE(section1_changes.modifications.empty()); REQUIRE(section1_changes.deletions.empty()); @@ -1257,8 +1248,8 @@ TEST_CASE("sectioned results", "[sectioned_results]") { advance_and_notify(*r); REQUIRE(section1_notification_calls == 1); REQUIRE(section2_notification_calls == 1); - REQUIRE(section2_changes.insertions.size() == 1); - REQUIRE(section2_changes.insertions[1].count() == 1); + REQUIRE(section2_changes.insertions.size() == 2); + REQUIRE(section2_changes.insertions[0].empty()); REQUIRE_INDICES(section2_changes.insertions[1], 1); REQUIRE(section2_changes.modifications.empty()); REQUIRE(section2_changes.deletions.empty()); @@ -1298,7 +1289,8 @@ TEST_CASE("sectioned results", "[sectioned_results]") { advance_and_notify(*r); REQUIRE(section1_notification_calls == 3); REQUIRE(section2_notification_calls == 2); - REQUIRE(section2_changes.deletions.size() == 1); + REQUIRE(section2_changes.deletions.size() == 2); + REQUIRE(section2_changes.deletions[0].empty()); REQUIRE_INDICES(section2_changes.deletions[1], 1); REQUIRE(section2_changes.insertions.empty()); REQUIRE(section2_changes.modifications.empty()); @@ -1758,7 +1750,8 @@ TEMPLATE_TEST_CASE("sectioned results primitive types", "[sectioned_results]", c REQUIRE_INDICES(changes1.sections_to_insert, 0); REQUIRE(changes1.sections_to_delete.empty()); - REQUIRE(changes2.insertions.size() == 1); + REQUIRE(changes2.insertions.size() == 2); + REQUIRE(changes2.insertions[0].empty()); REQUIRE(!changes2.insertions[1].empty()); REQUIRE(changes2.deletions.empty()); REQUIRE(changes2.modifications.empty()); diff --git a/test/object-store/set.cpp b/test/object-store/set.cpp index a16a902efde..63d9fb3c3dc 100644 --- a/test/object-store/set.cpp +++ b/test/object-store/set.cpp @@ -705,8 +705,6 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) CHECK(set().insert(-1).second); }); - auto x = set().min(); - REQUIRE(set().is_valid()); CHECK(set().sum(col_int_set) == 578); CHECK(set().min(col_int_set) == -1); diff --git a/test/object-store/sync/app.cpp b/test/object-store/sync/app.cpp index d23e9a23367..c7223609e99 100644 --- a/test/object-store/sync/app.cpp +++ b/test/object-store/sync/app.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #include #include @@ -2212,7 +2213,7 @@ TEST_CASE("app: sync integration", "[sync][app]") { logger->trace("Received request[%1]: %2", request_count, request.url); if (request_count == 0) { // First request should be to location - REQUIRE(request.url.find_first_of("/location") != std::string::npos); + REQUIRE(request.url.find("/location") != std::string::npos); if (request.url.find("https://") != std::string::npos) { redirect_scheme = "https://"; } @@ -2360,10 +2361,10 @@ TEST_CASE("app: sync integration", "[sync][app]") { std::string websocket_url = "ws://some-websocket:9090"; std::string original_url; redir_transport->request_hook = [&](const Request& request) { - logger->trace("Received request[%1]: %2", request_count, request.url); + logger->trace("request.url (%1): %2", request_count, request.url); if (request_count == 0) { // First request should be to location - REQUIRE(request.url.find_first_of("/location") != std::string::npos); + REQUIRE(request.url.find("/location") != std::string::npos); if (request.url.find("https://") != std::string::npos) { original_scheme = "https://"; } @@ -2379,7 +2380,6 @@ TEST_CASE("app: sync integration", "[sync][app]") { logger->trace("original_url (%1): %2", request_count, original_url); } else if (request_count == 1) { - logger->trace("request.url (%1): %2", request_count, request.url); REQUIRE(!request.redirect_count); redir_transport->simulated_response = { 308, @@ -2388,7 +2388,6 @@ TEST_CASE("app: sync integration", "[sync][app]") { "Some body data"}; } else if (request_count == 2) { - logger->trace("request.url (%1): %2", request_count, request.url); REQUIRE(request.url.find("http://somehost:9090") != std::string::npos); REQUIRE(request.url.find("location") != std::string::npos); // app hostname will be updated via the metadata info @@ -2401,7 +2400,6 @@ TEST_CASE("app: sync integration", "[sync][app]") { original_url, websocket_url)}; } else { - logger->trace("request.url (%1): %2", request_count, request.url); REQUIRE(request.url.find(original_url) != std::string::npos); redir_transport->simulated_response.reset(); } @@ -2506,10 +2504,11 @@ TEST_CASE("app: sync integration", "[sync][app]") { }; int request_count = 0; redir_transport->request_hook = [&](const Request& request) { + logger->trace("request.url (%1): %2", request_count, request.url); if (request_count++ == 0) { - logger->trace("request.url (%1): %2", request_count, request.url); - // First request should be to location - REQUIRE(request.url.find_first_of("/location") != std::string::npos); + // First request should be a location request against the original URL + REQUIRE(request.url.find(original_host) != std::string::npos); + REQUIRE(request.url.find("/location") != std::string::npos); REQUIRE(request.redirect_count == 0); redir_transport->simulated_response = { static_cast(sync::HTTPStatus::PermanentRedirect), @@ -2517,8 +2516,7 @@ TEST_CASE("app: sync integration", "[sync][app]") { {{"Location", redirect_url}, {"Content-Type", "application/json"}}, "Some body data"}; } - else if (request.url.find("location") != std::string::npos) { - logger->trace("request.url (%1): %2", request_count, request.url); + else if (request.url.find("/location") != std::string::npos) { redir_transport->simulated_response = { static_cast(sync::HTTPStatus::Ok), 0, @@ -2529,7 +2527,6 @@ TEST_CASE("app: sync integration", "[sync][app]") { redirect_host, redirect_scheme, websocket_scheme)}; } else { - logger->trace("request.url (%1): %2", request_count, request.url); redir_transport->simulated_response.reset(); } }; @@ -2560,10 +2557,11 @@ TEST_CASE("app: sync integration", "[sync][app]") { }; int request_count = 0; redir_transport->request_hook = [&](const Request& request) { + logger->trace("request.url (%1): %2", request_count, request.url); if (request_count++ == 0) { - logger->trace("request.url (%1): %2", request_count, request.url); - // First request should be to location - REQUIRE(request.url.find_first_of("/location") != std::string::npos); + // First request should be a location request against the original URL + REQUIRE(request.url.find(original_host) != std::string::npos); + REQUIRE(request.url.find("/location") != std::string::npos); REQUIRE(request.redirect_count == 0); redir_transport->simulated_response = { static_cast(sync::HTTPStatus::MovedPermanently), @@ -2571,8 +2569,7 @@ TEST_CASE("app: sync integration", "[sync][app]") { {{"Location", redirect_url}, {"Content-Type", "application/json"}}, "Some body data"}; } - else if (request.url.find("location") != std::string::npos) { - logger->trace("request.url (%1): %2", request_count, request.url); + else if (request.url.find("/location") != std::string::npos) { redir_transport->simulated_response = { static_cast(sync::HTTPStatus::Ok), 0, @@ -2583,14 +2580,12 @@ TEST_CASE("app: sync integration", "[sync][app]") { redirect_host, redirect_scheme, websocket_scheme)}; } else if (request.url.find("auth/session") != std::string::npos) { - logger->trace("request.url (%1): %2", request_count, request.url); redir_transport->simulated_response = {static_cast(sync::HTTPStatus::Unauthorized), 0, {{"Content-Type", "application/json"}}, ""}; } else { - logger->trace("request.url (%1): %2", request_count, request.url); redir_transport->simulated_response.reset(); } }; @@ -2599,9 +2594,59 @@ TEST_CASE("app: sync integration", "[sync][app]") { sync_session->resume(); REQUIRE(wait_for_download(*r)); std::unique_lock lk(logout_mutex); - REQUIRE(logout_cv.wait_for(lk, std::chrono::seconds(15), [&]() { + auto result = logout_cv.wait_for(lk, std::chrono::seconds(15), [&]() { return logged_out; - })); + }); + REQUIRE(result); + REQUIRE(!user1->is_logged_in()); + } + SECTION("Too many websocket redirects logs out user") { + auto sync_manager = test_session.app()->sync_manager(); + auto sync_session = sync_manager->get_existing_session(r->config().path); + sync_session->pause(); + + int connect_count = 0; + redir_provider->websocket_connect_func = [&connect_count](int& status_code, std::string& body) { + if (connect_count++ > 0) + return false; + + status_code = static_cast(sync::HTTPStatus::MovedPermanently); + body = ""; + return true; + }; + int request_count = 0; + const int max_http_redirects = 20; // from app.cpp in object-store + redir_transport->request_hook = [&](const Request& request) { + logger->trace("request.url (%1): %2", request_count, request.url); + if (request_count++ == 0) { + // First request should be a location request against the original URL + REQUIRE(request.url.find(original_host) != std::string::npos); + REQUIRE(request.url.find("/location") != std::string::npos); + REQUIRE(request.redirect_count == 0); + } + if (request.url.find("/location") != std::string::npos) { + // Keep returning the redirected response + REQUIRE(request.redirect_count < max_http_redirects); + redir_transport->simulated_response = { + static_cast(sync::HTTPStatus::MovedPermanently), + 0, + {{"Location", redirect_url}, {"Content-Type", "application/json"}}, + "Some body data"}; + } + else { + // should not get any other types of requests during the test - the log out is local + REQUIRE(false); + } + }; + + SyncManager::OnlyForTesting::voluntary_disconnect_all_connections(*sync_manager); + sync_session->resume(); + REQUIRE(wait_for_download(*r)); + std::unique_lock lk(logout_mutex); + auto result = logout_cv.wait_for(lk, std::chrono::seconds(15), [&]() { + return logged_out; + }); + REQUIRE(result); REQUIRE(!user1->is_logged_in()); } } @@ -3598,16 +3643,17 @@ class UnitTestTransport : public GenericNetworkTransport { nlohmann::json({{"device", {{"appId", "app_id"}, {"appVersion", "A Local App Version"}, - {"platform", "Object Store Test Platform"}, + {"platform", util::get_library_platform()}, {"platformVersion", "Object Store Test Platform Version"}, {"sdk", "SDK Name"}, {"sdkVersion", "SDK Version"}, - {"cpuArch", "CPU Arch"}, + {"cpuArch", util::get_library_cpu_arch()}, {"deviceName", "Device Name"}, {"deviceVersion", "Device Version"}, {"frameworkName", "Framework Name"}, {"frameworkVersion", "Framework Version"}, - {"coreVersion", REALM_VERSION_STRING}}}})); + {"coreVersion", REALM_VERSION_STRING}, + {"bundleId", "Bundle Id"}}}})); CHECK(request.timeout_ms == 60000); diff --git a/test/object-store/sync/flx_sync.cpp b/test/object-store/sync/flx_sync.cpp index 6dd7b2152a4..bd6afe7b0ed 100644 --- a/test/object-store/sync/flx_sync.cpp +++ b/test/object-store/sync/flx_sync.cpp @@ -30,6 +30,10 @@ #include "realm/object-store/impl/realm_coordinator.hpp" #include "realm/object-store/schema.hpp" #include "realm/object-store/sync/generic_network_transport.hpp" +#include +#include +#include +#include #include "realm/object-store/sync/sync_session.hpp" #include "realm/object_id.hpp" #include "realm/query_expression.hpp" @@ -1281,35 +1285,124 @@ TEST_CASE("flx: geospatial", "[sync][flx][app]") { auto subs = create_subscription(realm, "class_restaurant", "queryable_str_field", [](Query q, ColKey c) { return q.equal(c, "synced"); }); + auto make_polygon_filter = [&](const GeoPolygon& polygon) -> bson::BsonDocument { + bson::BsonArray inner{}; + REALM_ASSERT_3(polygon.points.size(), ==, 1); + for (auto& point : polygon.points[0]) { + inner.push_back(bson::BsonArray{point.longitude, point.latitude}); + } + bson::BsonArray coords; + coords.push_back(inner); + bson::BsonDocument geo_bson{{{"type", "Polygon"}, {"coordinates", coords}}}; + bson::BsonDocument filter{ + {"location", bson::BsonDocument{{"$geoWithin", bson::BsonDocument{{"$geometry", geo_bson}}}}}}; + return filter; + }; + auto make_circle_filter = [&](const GeoCircle& circle) -> bson::BsonDocument { + bson::BsonArray coords{circle.center.longitude, circle.center.latitude}; + bson::BsonArray inner; + inner.push_back(coords); + inner.push_back(circle.radius_radians); + bson::BsonDocument filter{ + {"location", bson::BsonDocument{{"$geoWithin", bson::BsonDocument{{"$centerSphere", inner}}}}}}; + return filter; + }; + auto run_query_on_server = [&](const bson::BsonDocument& filter, + std::optional expected_error = {}) -> size_t { + auto remote_client = harness->app()->current_user()->mongo_client("BackingDB"); + auto db = remote_client.db(harness->session().app_session().config.mongo_dbname); + auto restaurant_collection = db["restaurant"]; + bool processed = false; + constexpr int64_t limit = 1000; + size_t matches = 0; + restaurant_collection.count(filter, limit, [&](uint64_t count, util::Optional error) { + processed = true; + if (error) { + if (!expected_error) { + util::format(std::cout, "query error: %1\n", error->reason()); + FAIL(error); + } + else { + std::string_view reason = error->reason(); + auto pos = reason.find(*expected_error); + if (pos == std::string::npos) { + util::format(std::cout, "mismatch error: '%1' and '%2'\n", reason, *expected_error); + FAIL(reason); + } + } + } + matches = size_t(count); + }); + REQUIRE(processed); + return matches; + }; auto sub_res = subs.get_state_change_notification(sync::SubscriptionSet::State::Complete).get_no_throw(); CHECK(sub_res.is_ok()); CHECK(realm->get_active_subscription_set().version() == 1); CHECK(realm->get_latest_subscription_set().version() == 1); realm->begin_transaction(); + CppContext c(realm); - Object::create( - c, realm, "restaurant", - std::any(AnyDict{{"_id", INT64_C(1)}, - {"queryable_str_field", "synced"s}, - {"location", AnyDict{{"type", "Point"s}, - {"coordinates", std::vector{1.1, 2.2, 3.3}}}}})); + int64_t pk = 0; + auto add_point = [&](GeoPoint p) { + Object::create( + c, realm, "restaurant", + std::any(AnyDict{ + {"_id", ++pk}, + {"queryable_str_field", "synced"s}, + {"location", AnyDict{{"type", "Point"s}, + {"coordinates", std::vector{p.longitude, p.latitude}}}}})); + }; + std::vector points = { + GeoPoint{-74.006, 40.712800000000001}, GeoPoint{12.568300000000001, 55.676099999999998}, + GeoPoint{12.082599999999999, 55.628}, GeoPoint{-180.1, -90.1}, // invalid + }; + for (auto& point : points) { + add_point(point); + } realm->commit_transaction(); wait_for_upload(*realm); { auto table = realm->read_group().get_table("class_restaurant"); - CHECK(table->size() == 1); + CHECK(table->size() == points.size()); Obj obj = table->get_object_with_primary_key(Mixed{1}); REQUIRE(obj); Geospatial geo = obj.get("location"); REQUIRE(geo.get_type_string() == "Point"); REQUIRE(geo.get_type() == Geospatial::Type::Point); GeoPoint point = geo.get(); - REQUIRE(point.longitude == 1.1); - REQUIRE(point.latitude == 2.2); - REQUIRE(point.get_altitude()); - REQUIRE(*point.get_altitude() == 3.3); + REQUIRE(point.longitude == points[0].longitude); + REQUIRE(point.latitude == points[0].latitude); + REQUIRE(!point.get_altitude()); + ColKey location_col = table->get_column_key("location"); + GeoPolygon bounds{ + {GeoPoint{-80, 40.7128}, GeoPoint{20, 60}, GeoPoint{20, 20}, GeoPoint{-80, 40.7128}}}; + Query query = table->column(location_col).geo_within(Geospatial(bounds)); + size_t local_matches = query.find_all().size(); + REQUIRE(local_matches == 2); + + reset_utils::wait_for_num_objects_in_atlas( + harness->app()->current_user(), harness->session().app_session(), "restaurant", points.size()); + + bson::BsonDocument filter = make_polygon_filter(bounds); + size_t server_results = run_query_on_server(filter); + CHECK(server_results == local_matches); + + GeoCircle circle = GeoCircle::from_kms(10, GeoPoint{-180.1, -90.1}); + CHECK_THROWS_WITH(table->column(location_col).geo_within(circle).count(), + "Invalid region in GEOWITHIN query for parameter 'GeoCircle([-180.1, -90.1], " + "0.00156787)': 'Longitude/latitude is out of bounds, lng: -180.1 lat: -90.1'"); + filter = make_circle_filter(circle); + run_query_on_server(filter, "(BadValue) longitude/latitude is out of bounds"); + + circle = GeoCircle::from_kms(-1, GeoPoint{0, 0}); + CHECK_THROWS_WITH(table->column(location_col).geo_within(circle).count(), + "Invalid region in GEOWITHIN query for parameter 'GeoCircle([0, 0], " + "-0.000156787)': 'The radius of a circle must be a non-negative number'"); + filter = make_circle_filter(circle); + run_query_on_server(filter, "(BadValue) radius must be a non-negative number"); } }); } diff --git a/test/object-store/sync/sync_test_utils.cpp b/test/object-store/sync/sync_test_utils.cpp index dece34092f4..391957bbce7 100644 --- a/test/object-store/sync/sync_test_utils.cpp +++ b/test/object-store/sync/sync_test_utils.cpp @@ -409,6 +409,32 @@ void wait_for_object_to_persist_to_atlas(std::shared_ptr user, const A std::chrono::minutes(15), std::chrono::milliseconds(500)); } +void wait_for_num_objects_in_atlas(std::shared_ptr user, const AppSession& app_session, + const std::string& schema_name, size_t expected_size) +{ + app::MongoClient remote_client = user->mongo_client("BackingDB"); + app::MongoDatabase db = remote_client.db(app_session.config.mongo_dbname); + app::MongoCollection object_coll = db[schema_name]; + + const bson::BsonDocument& filter_bson{}; + timed_sleeping_wait_for( + [&]() -> bool { + auto pf = util::make_promise_future(); + object_coll.count(filter_bson, [promise = std::move(pf.promise)]( + uint64_t count, util::Optional error) mutable { + REQUIRE(!error); + if (error) { + promise.set_error({ErrorCodes::RuntimeError, error->reason()}); + } + else { + promise.emplace_value(count); + } + }); + return pf.future.get() >= expected_size; + }, + std::chrono::minutes(15), std::chrono::milliseconds(500)); +} + void trigger_client_reset(const AppSession& app_session) { // cause a client reset by restarting the sync service diff --git a/test/object-store/sync/sync_test_utils.hpp b/test/object-store/sync/sync_test_utils.hpp index 52805dfa1d8..7686f6cea17 100644 --- a/test/object-store/sync/sync_test_utils.hpp +++ b/test/object-store/sync/sync_test_utils.hpp @@ -217,6 +217,9 @@ std::unique_ptr make_baas_flx_client_reset(const Realm::Config& void wait_for_object_to_persist_to_atlas(std::shared_ptr user, const AppSession& app_session, const std::string& schema_name, const bson::BsonDocument& filter_bson); +void wait_for_num_objects_in_atlas(std::shared_ptr user, const AppSession& app_session, + const std::string& schema_name, size_t expected_size); + void trigger_client_reset(const AppSession& app_session); void trigger_client_reset(const AppSession& app_session, const SharedRealm& realm); #endif // REALM_ENABLE_AUTH_TESTS diff --git a/test/object-store/util/baas_admin_api.hpp b/test/object-store/util/baas_admin_api.hpp index 0fd0b5f1672..03edeb26a88 100644 --- a/test/object-store/util/baas_admin_api.hpp +++ b/test/object-store/util/baas_admin_api.hpp @@ -281,8 +281,8 @@ inline app::App::Config get_config(Factory factory, const AppSession& app_sessio util::none, util::Optional("A Local App Version"), util::none, - {"Object Store Platform Tests", "Object Store Platform Version Blah", "An sdk version", "An sdk name", - "A cpu arch", "A device name", "A device version", "A framework name", "A framework version"}}; + {"Object Store Platform Version Blah", "An sdk version", "An sdk name", "A device name", + "A device version", "A framework name", "A framework version", "A bundle id"}}; } } // namespace realm diff --git a/test/object-store/util/event_loop.cpp b/test/object-store/util/event_loop.cpp index 05d45ff2148..6463179e12e 100644 --- a/test/object-store/util/event_loop.cpp +++ b/test/object-store/util/event_loop.cpp @@ -81,6 +81,9 @@ struct EventLoop::Impl { // Schedule execution of the given function on the event loop. void perform(util::UniqueFunction); + // Run the event loop until all currently pending work has been run. + void run_pending(); + ~Impl(); private: @@ -124,6 +127,11 @@ void EventLoop::perform(util::UniqueFunction function) return m_impl->perform(std::move(function)); } +void EventLoop::run_pending() +{ + return m_impl->run_pending(); +} + #if REALM_USE_UV bool EventLoop::has_implementation() @@ -204,6 +212,11 @@ void EventLoop::Impl::perform(util::UniqueFunction f) uv_async_send(&m_perform_work); } +void EventLoop::Impl::run_pending() +{ + uv_run(m_loop, UV_RUN_NOWAIT); +} + #elif REALM_PLATFORM_APPLE bool EventLoop::has_implementation() @@ -254,6 +267,12 @@ void EventLoop::Impl::perform(util::UniqueFunction func) CFRunLoopWakeUp(m_loop.get()); } +void EventLoop::Impl::run_pending() +{ + while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) == kCFRunLoopRunHandledSource) + ; +} + #else bool EventLoop::has_implementation() @@ -273,5 +292,9 @@ void EventLoop::Impl::perform(util::UniqueFunction) { printf("WARNING: there is no event loop implementation and nothing is happening.\n"); } +void EventLoop::Impl::run_pending() +{ + printf("WARNING: there is no event loop implementation and nothing is happening.\n"); +} #endif diff --git a/test/object-store/util/event_loop.hpp b/test/object-store/util/event_loop.hpp index 8c191d698ea..a4a2ee31944 100644 --- a/test/object-store/util/event_loop.hpp +++ b/test/object-store/util/event_loop.hpp @@ -39,6 +39,9 @@ struct EventLoop { // Schedule execution of the given function on the event loop. void perform(util::UniqueFunction); + // Run the event loop until all currently pending work has been run. + void run_pending(); + EventLoop(EventLoop&&) = default; EventLoop& operator=(EventLoop&&) = default; ~EventLoop(); diff --git a/test/object-store/util/test_file.cpp b/test/object-store/util/test_file.cpp index 33a9ea78772..bdd47811a97 100644 --- a/test/object-store/util/test_file.cpp +++ b/test/object-store/util/test_file.cpp @@ -287,16 +287,12 @@ void set_app_config_defaults(app::App::Config& app_config, { if (!app_config.transport) app_config.transport = transport; - if (app_config.device_info.platform.empty()) - app_config.device_info.platform = "Object Store Test Platform"; if (app_config.device_info.platform_version.empty()) app_config.device_info.platform_version = "Object Store Test Platform Version"; if (app_config.device_info.sdk_version.empty()) app_config.device_info.sdk_version = "SDK Version"; if (app_config.device_info.sdk.empty()) app_config.device_info.sdk = "SDK Name"; - if (app_config.device_info.cpu_arch.empty()) - app_config.device_info.cpu_arch = "CPU Arch"; if (app_config.device_info.device_name.empty()) app_config.device_info.device_name = "Device Name"; if (app_config.device_info.device_version.empty()) @@ -305,6 +301,8 @@ void set_app_config_defaults(app::App::Config& app_config, app_config.device_info.framework_name = "Framework Name"; if (app_config.device_info.framework_version.empty()) app_config.device_info.framework_version = "Framework Version"; + if (app_config.device_info.bundle_id.empty()) + app_config.device_info.bundle_id = "Bundle Id"; if (app_config.app_id.empty()) app_config.app_id = "app_id"; if (!app_config.local_app_version) diff --git a/test/test_parser.cpp b/test/test_parser.cpp index 874a5ef24e5..4ea504e87c8 100644 --- a/test/test_parser.cpp +++ b/test/test_parser.cpp @@ -5657,101 +5657,164 @@ TEST(Parser_Geospatial) auto geo_table = g.add_table("Position", Table::Type::Embedded); geo_table->add_column(type_String, "type"); geo_table->add_column_list(type_Double, "coordinates"); - table->add_column_list(*geo_table, "links"); - table->add_column(*table, "self_link"); + ColKey self_col = table->add_column(*table, "self_link"); + ColKey list_col = table->add_column_list(*table, "partners"); + ColKey name_col = table->add_column(type_String, "name"); + ColKey col_link = table->add_column(*geo_table, "location"); #if !REALM_ENABLE_GEOSPATIAL auto error = "Support for Geospatial queries is not enabled"; + static_cast(self_col); + static_cast(list_col); + static_cast(name_col); + static_cast(col_link); #define CHECK_QUERY(query) \ do { \ CHECK_THROW_EX(verify_query(test_context, table, query, 1), realm::LogicError, \ CHECK(std::string(e.what()).find(error) != std::string::npos)); \ } while (false) - CHECK_QUERY("link geoWithin geoBox([0.2, 0.2], [0.7, 0.7])"); - CHECK_QUERY("link geoWithin geoBox([0.2, 0.2, 0.2], [0.7, 0.7, 0.7])"); - CHECK_QUERY("link geoWithin geoSphere([0.3, 0.3], 1000.0)"); - CHECK_QUERY("link geoWithin geoSphere([0.3, 0.3, 0.3], 1000.0)"); - CHECK_QUERY("link geoWithin geoPolygon({[0.0, 0.0], [1.0, 0.0], [1, 1], [0, 1]})"); + CHECK_QUERY("location geoWithin geoBox([0.2, 0.2], [0.7, 0.7])"); + CHECK_QUERY("location geoWithin geoBox([0.2, 0.2, 0.2], [0.7, 0.7, 0.7])"); + CHECK_QUERY("location geoWithin geoCircle([0.3, 0.3], 1000.0)"); + CHECK_QUERY("location geoWithin geoCircle([0.3, 0.3, 0.3], 1000.0)"); + CHECK_QUERY("location geoWithin geoPolygon({[0.0, 0.0], [1.0, 0.0], [1, 1], [0, 1], [0.0, 0.0]})"); - CHECK_THROW_EX(verify_query_sub(test_context, table, "link GEOWITHIN $0", {}, 1), realm::LogicError, + CHECK_THROW_EX(verify_query_sub(test_context, table, "location GEOWITHIN $0", {}, 1), realm::LogicError, CHECK(std::string(e.what()).find(error) != std::string::npos)); #else - auto col_link = table->add_column(*geo_table, "link"); - std::vector point_data = {GeoPoint{0, 0}, GeoPoint{0.5, 0.5}, GeoPoint{1, 1}, GeoPoint{2, 2}}; - for (auto& geo : point_data) { - table->create_object_with_primary_key(ObjectId::gen()).set(col_link, geo); + struct Restaurant { + std::string name; + GeoPoint location; + ObjectId pk; + }; + std::vector data = {{"one", GeoPoint{0, 0}}, + {"two", GeoPoint{0.5, 0.5}}, + {"three", GeoPoint{1, 1}}, + {"four", GeoPoint{2, 2}}, + {"Red Fish Blue Fish", GeoPoint{-123.37039, 48.42437}}, + {"Foo", GeoPoint{-123.36253, 48.42566}}, + {"Superbaba", GeoPoint{-123.3615, 48.4267}}, + {"Sen Zushi", GeoPoint{-123.3579, 48.42398}}}; + for (size_t i = 0; i < data.size(); ++i) { + Restaurant& r = data[i]; + r.pk = ObjectId::gen(); + Obj obj = + table->create_object_with_primary_key(r.pk).set(col_link, Geospatial(r.location)).set(name_col, r.name); + obj.set(self_col, obj.get_key()); + LnkLst lst = obj.get_linklist(list_col); + for (auto it = table->begin(); it != table->end(); ++it) { + if (it->get_key() != obj.get_key()) { + lst.add(it->get_key()); + } + } } // add one object with a null link - table->create_object_with_primary_key(ObjectId::gen()); + table->create_object_with_primary_key(ObjectId::gen()).set(name_col, "empty"); - verify_query(test_context, table, "link geoWithin geoBox([0.2, 0.2], [0.7, 0.7])", 1); - verify_query(test_context, table, "link geoWithin geoBox([0.2, 0.2, 0.2], [0.7, 0.7, 0.7])", 1); - verify_query(test_context, table, "link geoWithin geoSphere([0.3, 0.3], 1000.0)", 4); - verify_query(test_context, table, "link geoWithin geoSphere([0.3, 0.3, 0.3], 1000.0)", 4); - verify_query(test_context, table, "link geoWithin geoPolygon({[0.0, 0.0], [1.0, 0.0], [1, 1], [0, 1]})", 1); + verify_query(test_context, table, "location geoWithin geoBox([0.2, 0.2], [0.7, 0.7])", 1); + verify_query(test_context, table, "location geoWithin geoBox([0.2, 0.2, 0.2], [0.7, 0.7, 0.7])", 1); + verify_query(test_context, table, "location geoWithin geoCircle([0.3, 0.3], 1)", 4); + verify_query(test_context, table, "location geoWithin geoCircle([0.3, 0.3, 0.3], 1)", 4); verify_query(test_context, table, - "link geoWithin geoPolygon({[0.0, 0.0], [1.0, 0.0], [1, 1], [0, 1]}, " - "{[0.25, 0.25], [0.75, 0.25], [0.75, 0.75], [0.25, 0.75]})", + "location geoWithin geoPolygon({[0.0, 0.0], [1.0, 0.0], [1, 1], [0, 1], [0.0, 0.0]})", 1); + verify_query(test_context, table, + "location geoWithin geoPolygon({[0.0, 0.0], [1.0, 0.0], [1, 1], [0, 1], [0.0, 0.0]}, " + "{[0.25, 0.25], [0.75, 0.25], [0.75, 0.75], [0.25, 0.75], [0.25, 0.25]})", 0); // polygon with hole - verify_query(test_context, table, "link == NULL", 1); + verify_query(test_context, table, "location == NULL", 1); + // this circle contains "superbaba" and "foo" + Geospatial area = GeoCircle{0.000021950110534, GeoPoint{-123.36197, 48.42626}}; + verify_query(test_context, table, "ANY partners.location GEOWITHIN " + area.to_string(), 2); + verify_query(test_context, table, "ALL partners.location GEOWITHIN " + area.to_string(), 0); + verify_query(test_context, table, "NONE partners.location GEOWITHIN " + area.to_string(), 7); Geospatial box{GeoBox{GeoPoint{0.2, 0.2}, GeoPoint{0.7, 0.7}}}; - Geospatial sphere{GeoCenterSphere{1000, GeoPoint{0.3, 0.3}}}; - Geospatial polygon{GeoPolygon{{GeoPoint{0, 0}, GeoPoint{1, 0}, GeoPoint{1, 1}, GeoPoint{0, 1}}}}; + Geospatial circle{GeoCircle{1, GeoPoint{0.3, 0.3}}}; + Geospatial polygon{GeoPolygon{{GeoPoint{0, 0}, GeoPoint{1, 0}, GeoPoint{1, 1}, GeoPoint{0, 1}, GeoPoint{0, 0}}}}; Geospatial invalid; Geospatial point{GeoPoint{0, 0}}; - std::vector args = {Mixed{&box}, Mixed{&sphere}, Mixed{&polygon}, Mixed{&invalid}, + std::vector args = {Mixed{&box}, Mixed{&circle}, Mixed{&polygon}, Mixed{&invalid}, Mixed{realm::null()}, Mixed{1.2}, Mixed{1000}, Mixed{"string value"}}; - verify_query_sub(test_context, table, "link GEOWITHIN $0", args, 1); - verify_query_sub(test_context, table, "link GEOWITHIN $1", args, 4); - verify_query_sub(test_context, table, "link GEOWITHIN $2", args, 1); + verify_query_sub(test_context, table, "location GEOWITHIN $0", args, 1); + verify_query_sub(test_context, table, "location GEOWITHIN $1", args, 4); + verify_query_sub(test_context, table, "location GEOWITHIN $2", args, 1); + + GeoCircle c = circle.get(); + std::vector coord_args = {Mixed{c.center.longitude}, Mixed{c.center.latitude}, Mixed{c.radius_radians}}; + verify_query_sub(test_context, table, "location GEOWITHIN geoCircle([$0, $1], $2)", coord_args, 4); + GeoPolygon p = polygon.get(); + coord_args = {Mixed{p.points[0][0].longitude}, Mixed{p.points[0][0].latitude}, Mixed{p.points[0][1].longitude}, + Mixed{p.points[0][1].latitude}, Mixed{p.points[0][2].longitude}, Mixed{p.points[0][2].latitude}, + Mixed{p.points[0][3].longitude}, Mixed{p.points[0][3].latitude}, Mixed{p.points[0][4].longitude}, + Mixed{p.points[0][4].latitude}}; + verify_query_sub(test_context, table, + "location GEOWITHIN geoPolygon({[$0, $1], [$2, $3], [$4, $5], [$6, $7], [$8, $9]})", coord_args, + 1); + GeoBox b = box.get(); + coord_args = {b.lo.longitude, b.lo.latitude, b.hi.longitude, b.hi.latitude}; + verify_query_sub(test_context, table, "location GEOWITHIN geoBox([$0, $1], [$2, $3])", coord_args, 1); + CHECK_THROW_EX( verify_query(test_context, table, "_id geoWithin geoBox([0.2, 0.2], [0.7, 0.7])", 1), query_parser::InvalidQueryError, CHECK(std::string(e.what()).find("The left hand side of 'geoWithin' must be a link to geoJSON formatted " "data. But the provided type is 'objectId'") != std::string::npos)); - CHECK_THROW_ANY(verify_query(test_context, table, "link geoWithin _id", 0)); - CHECK_THROW_ANY(verify_query(test_context, table, "link geoWithin link", 0)); + CHECK_THROW_ANY(verify_query(test_context, table, "location geoWithin _id", 0)); + CHECK_THROW_ANY(verify_query(test_context, table, "location geoWithin location", 0)); CHECK_THROW_EX( verify_query(test_context, table, "self_link geoWithin geoBox([0.2, 0.2], [0.7, 0.7])", 0), std::runtime_error, CHECK(std::string(e.what()).find("Query 'self_link GEOWITHIN GeoBox([0.2, 0.2], [0.7, 0.7])' links to data " "in the wrong format for a geoWithin query") != std::string::npos)); - CHECK_THROW_EX( - verify_query(test_context, table, "link geoWithin NULL", 1), query_parser::SyntaxError, - CHECK(std::string(e.what()).find("Invalid predicate: 'link geoWithin NULL': syntax error, unexpected null") != - std::string::npos)); - CHECK_THROW_EX( - verify_query(test_context, table, "link geoWithin 1.2", 1), query_parser::SyntaxError, - CHECK(std::string(e.what()).find("Invalid predicate: 'link geoWithin 1.2': syntax error, unexpected float") != - std::string::npos)); - CHECK_THROW_EX(verify_query(test_context, table, "link geoWithin 'test string'", 1), query_parser::SyntaxError, + CHECK_THROW_EX(verify_query(test_context, table, "location geoWithin NULL", 1), query_parser::SyntaxError, + CHECK(std::string(e.what()).find( + "Invalid predicate: 'location geoWithin NULL': syntax error, unexpected null") != + std::string::npos)); + CHECK_THROW_EX(verify_query(test_context, table, "location geoWithin 1.2", 1), query_parser::SyntaxError, CHECK(std::string(e.what()).find( - "Invalid predicate: 'link geoWithin 'test string'': syntax error, unexpected string") != + "Invalid predicate: 'location geoWithin 1.2': syntax error, unexpected float") != std::string::npos)); CHECK_THROW_EX( - verify_query_sub(test_context, table, "link GEOWITHIN $4", args, 1), query_parser::InvalidQueryError, + verify_query(test_context, table, "location geoWithin 'test string'", 1), query_parser::SyntaxError, + CHECK(std::string(e.what()).find( + "Invalid predicate: 'location geoWithin 'test string'': syntax error, unexpected string") != + std::string::npos)); + CHECK_THROW_EX( + verify_query_sub(test_context, table, "location GEOWITHIN $4", args, 1), query_parser::InvalidQueryError, CHECK(std::string(e.what()).find("The right hand side of 'geoWithin' must be a geospatial constant value. " "But the provided type is 'null'") != std::string::npos)); CHECK_THROW_EX( - verify_query_sub(test_context, table, "link GEOWITHIN $5", args, 1), query_parser::InvalidQueryError, + verify_query_sub(test_context, table, "location GEOWITHIN $5", args, 1), query_parser::InvalidQueryError, CHECK(std::string(e.what()).find("The right hand side of 'geoWithin' must be a geospatial constant value. " "But the provided type is 'double'") != std::string::npos)); CHECK_THROW_EX( - verify_query_sub(test_context, table, "link GEOWITHIN $6", args, 1), query_parser::InvalidQueryError, + verify_query_sub(test_context, table, "location GEOWITHIN $6", args, 1), query_parser::InvalidQueryError, CHECK(std::string(e.what()).find("The right hand side of 'geoWithin' must be a geospatial constant value. " "But the provided type is 'int'") != std::string::npos)); CHECK_THROW_EX( - verify_query_sub(test_context, table, "link GEOWITHIN $7", args, 1), query_parser::InvalidQueryError, + verify_query_sub(test_context, table, "location GEOWITHIN $7", args, 1), query_parser::InvalidQueryError, CHECK(std::string(e.what()).find("The right hand side of 'geoWithin' must be a geospatial constant value. " "But the provided type is 'string'") != std::string::npos)); - CHECK_THROW_EX(verify_query_sub(test_context, table, "link GEOWITHIN $3", args, 0), + CHECK_THROW_EX(verify_query_sub(test_context, table, "location GEOWITHIN $3", args, 0), query_parser::InvalidQueryError, CHECK(std::string(e.what()).find("The right hand side of 'geoWithin' must be a valid " "Geospatial value, got 'NULL'") != std::string::npos)); + CHECK_THROW_EX( + verify_query_sub(test_context, table, "location GEOWITHIN geoCircle([$1, $2], $3)", args, 0), + query_parser::InvalidQueryError, + CHECK(std::string(e.what()).find("Invalid parameter 'geospatial' used in coordinate at argument '$1'") != + std::string::npos)); + CHECK_THROW_EX( + verify_query_sub(test_context, table, "location GEOWITHIN geoCircle([$4, $4], $4)", args, 0), + query_parser::InvalidQueryError, + CHECK(std::string(e.what()).find("NULL cannot be used in coordinate at argument '$4'") != std::string::npos)); + CHECK_THROW_EX(verify_query_sub(test_context, table, "location GEOWITHIN geoCircle([$7, $7], $7)", args, 0), + query_parser::InvalidQueryError, + CHECK(std::string(e.what()).find( + "Invalid parameter 'string' used in coordinate at argument '$7'") != std::string::npos)); #endif } diff --git a/test/test_query_geo.cpp b/test/test_query_geo.cpp index eaf24b34ae2..6cb1cfa402f 100644 --- a/test/test_query_geo.cpp +++ b/test/test_query_geo.cpp @@ -19,6 +19,10 @@ #include "testsettings.hpp" #ifdef TEST_GEO +#include "s2/util/math/mathutil.h" +// conflicting defines of CHECK from s2 +#undef CHECK + #include "test.hpp" #include @@ -33,6 +37,33 @@ using namespace realm; using namespace realm::util; using namespace realm::test_util; +// From https://github.com/10gen/mongo/pull/11605 +// Test which verifies that the rounding functions used by s2 follow 'round to even' rounding +// behavior. +TEST(S2VerifyS2RoundingBehavior) +{ + const double roundDownToEven = 2.5; + CHECK_EQUAL(2, MathUtil::FastIntRound(roundDownToEven)); + CHECK_EQUAL(2LL, MathUtil::FastInt64Round(roundDownToEven)); + + const double roundUpToEven = 3.5; + CHECK_EQUAL(4, MathUtil::FastIntRound(roundUpToEven)); + CHECK_EQUAL(4LL, MathUtil::FastInt64Round(roundUpToEven)); + + const double roundDownToEvenNegative = -3.5; + CHECK_EQUAL(-4, MathUtil::FastIntRound(roundDownToEvenNegative)); + CHECK_EQUAL(-4LL, MathUtil::FastInt64Round(roundDownToEvenNegative)); + + const double roundUpToEvenNegative = -2.5; + CHECK_EQUAL(-2, MathUtil::FastIntRound(roundUpToEvenNegative)); + CHECK_EQUAL(-2LL, MathUtil::FastInt64Round(roundUpToEvenNegative)); + + const double point = 944920918.5; + CHECK_EQUAL(944920918, MathUtil::FastIntRound(point)); + CHECK_EQUAL(944920918LL, MathUtil::FastInt64Round(point)); +} + + static TableRef setup_with_points(Group& g, const std::vector& points) { auto location_table = g.add_table("Location", Table::Type::Embedded); @@ -64,13 +95,13 @@ TEST(Geospatial_Assignment) obj.set(location_column_key, std::optional{}); CHECK(obj.is_null(location_column_key)); - CHECK(!obj.get(location_column_key).is_valid()); + CHECK(obj.get(location_column_key).get_type() == Geospatial::Type::Invalid); CHECK(!obj.get>(location_column_key)); obj.set(location_column_key, geo); obj.set(location_column_key, Geospatial{}); CHECK(obj.is_null(location_column_key)); - CHECK(!obj.get(location_column_key).is_valid()); + CHECK(obj.get(location_column_key).get_type() == Geospatial::Type::Invalid); CHECK(!obj.get>(location_column_key)); Geospatial geo_without_altitude{GeoPoint{5.5, 6.6}}; @@ -81,8 +112,24 @@ TEST(Geospatial_Assignment) Geospatial geo_box(GeoBox{GeoPoint{1.1, 2.2}, GeoPoint{3.3, 4.4}}); std::string_view err_msg = "The only Geospatial type currently supported for storage is 'point'"; CHECK_THROW_CONTAINING_MESSAGE(obj.set(location_column_key, geo_box), err_msg); - Geospatial geo_sphere(GeoCenterSphere{10, GeoPoint{1.1, 2.2}}); - CHECK_THROW_CONTAINING_MESSAGE(obj.set(location_column_key, geo_sphere), err_msg); + Geospatial geo_circle(GeoCircle{10, GeoPoint{1.1, 2.2}}); + CHECK_THROW_CONTAINING_MESSAGE(obj.set(location_column_key, geo_circle), err_msg); +} + +TEST(Geospatial_invalid_format) +{ + Group g; + TableRef table = setup_with_points(g, {}); + ColKey location_column_key = table->get_column_key("location"); + TableRef location_table = g.get_table("Location"); + CHECK(location_table); + location_table->set_table_type(Table::Type::TopLevel); + auto&& location = table->column(location_column_key); + + Geospatial bounds{GeoBox{GeoPoint{0.2, 0.2}, GeoPoint{0.7, 0.7}}}; + CHECK_THROW_CONTAINING_MESSAGE( + location.geo_within(bounds), + "A GEOWITHIN query can only operate on a link to an embedded class but 'Location' is at the top level"); } TEST(Query_GeoWithinBasics) @@ -104,13 +151,69 @@ TEST(Query_GeoWithinBasics) CHECK_EQUAL(location.geo_within(GeoBox{GeoPoint{0.2, 0.2}, GeoPoint{0.7, 0.7}}).count(), 1); CHECK_EQUAL(location.geo_within(GeoBox{GeoPoint{-2, -1.5}, GeoPoint{0.7, 0.5}}).count(), 3); - GeoPolygon p{{GeoPoint{-0.5, -0.5}, GeoPoint{1.0, 2.5}, GeoPoint{2.5, -0.5}}}; + GeoPolygon p{{GeoPoint{-0.5, -0.5}, GeoPoint{1.0, 2.5}, GeoPoint{2.5, -0.5}, GeoPoint{-0.5, -0.5}}}; CHECK_EQUAL(location.geo_within(p).count(), 3); - p = {{{-3.0, -1.0}, {-2.0, -2.0}, {-1.0, -1.0}, {1.5, -1.0}, {-1.0, 1.5}}}; + p = {{{-3.0, -1.0}, {-2.0, -2.0}, {-1.0, -1.0}, {1.5, -1.0}, {-1.0, 1.5}, {-3.0, -1.0}}}; CHECK_EQUAL(location.geo_within(p).count(), 2); - CHECK_EQUAL(location.geo_within(GeoCenterSphere::from_kms(150.0, GeoPoint{1.0, 0.5})).count(), 3); - CHECK_EQUAL(location.geo_within(GeoCenterSphere::from_kms(90.0, GeoPoint{-1.5, -1.5})).count(), 2); + CHECK_EQUAL(location.geo_within(GeoCircle::from_kms(150.0, GeoPoint{1.0, 0.5})).count(), 3); + CHECK_EQUAL(location.geo_within(GeoCircle::from_kms(90.0, GeoPoint{-1.5, -1.5})).count(), 2); + + CHECK_THROW_CONTAINING_MESSAGE(location.geo_within(Geospatial{GeoPoint{0.0, 0.0}}), + "Invalid region in GEOWITHIN query for parameter 'GeoPoint([0, 0])': 'A point " + "cannot be used on the right hand side of GEOWITHIN query"); + CHECK_THROW_CONTAINING_MESSAGE(location.geo_within(Geospatial{}), + "Invalid region in GEOWITHIN query for parameter 'NULL': 'NULL cannot be used on " + "the right hand side of a GEOWITHIN query"); +} + +TEST(Geospatial_ListOfPrimitives) +{ + auto make_list_with_points = [](Obj obj, const std::vector& points) { + ColKey list_col = obj.get_table()->get_column_key("locations"); + LnkLst list = obj.get_linklist(list_col); + for (const GeoPoint& point : points) { + Obj location = list.create_and_insert_linked_object(0); + Geospatial{point}.assign_to(location); + } + }; + Group g; + std::vector data = {GeoPoint{0, 0}, GeoPoint{0, 0}, GeoPoint{0, 0}, GeoPoint{0, 0}}; + TableRef table = setup_with_points(g, data); + TableRef location_table = g.get_table("Location"); + ColKey list_col = table->add_column_list(*location_table, "locations"); + CHECK(table->size() == 4); + auto obj_it = table->begin(); + make_list_with_points(*obj_it, {GeoPoint{1, 1}, GeoPoint{2, 2}}); + make_list_with_points(*++obj_it, {GeoPoint{2, 2}, GeoPoint{3, 3}}); + make_list_with_points(*++obj_it, {GeoPoint{1, 1}, GeoPoint{1, 1}, GeoPoint{1, 1}}); + // the fourth object has no elements in the list + + using GC = GeoCircle; + const double r = 0.00872665; + util::Optional ect; + + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {1, 1}}).count(), 2); + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {2, 2}}).count(), 2); + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {3, 3}}).count(), 1); + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {4, 4}}).count(), 0); + ect = ExpressionComparisonType::Any; + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {1, 1}}).count(), 2); + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {2, 2}}).count(), 2); + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {3, 3}}).count(), 1); + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {4, 4}}).count(), 0); + + ect = ExpressionComparisonType::All; + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {1, 1}}).count(), 1); + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {2, 2}}).count(), 0); + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {3, 3}}).count(), 0); + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {4, 4}}).count(), 0); + + ect = ExpressionComparisonType::None; + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {1, 1}}).count(), 2); // 1, 3 + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {2, 2}}).count(), 2); // 2, 3 + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {3, 3}}).count(), 3); // 0, 2, 3 + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {4, 4}}).count(), 4); // 0, 1, 2, 3 } TEST(Geospatial_MeridianQuery) @@ -141,7 +244,7 @@ TEST(Geospatial_EquatorQuery) CHECK_EQUAL(num_results, 1); } -TEST(Geospatial_CenterSphere) +TEST(Geospatial_Circle) { Group g; std::vector points = {GeoPoint{-118.2400013, 34.073893}, GeoPoint{-118.2400012, 34.073894}, @@ -149,9 +252,9 @@ TEST(Geospatial_CenterSphere) TableRef table = setup_with_points(g, points); ColKey location_column_key = table->get_column_key("location"); ColKey id_col = table->get_primary_key_column(); - Geospatial geo_sphere{GeoCenterSphere{0.44915760491198753, GeoPoint{-118.240013, 34.073893}}}; + Geospatial geo_circle{GeoCircle{0.44915760491198753, GeoPoint{-118.240013, 34.073893}}}; - Query query = table->column(location_column_key).geo_within(geo_sphere); + Query query = table->column(location_column_key).geo_within(geo_circle); CHECK_EQUAL(query.count(), 2); CHECK_EQUAL((query && table->column(id_col) == 0).count(), 1); CHECK_EQUAL((query && table->column(id_col) == 1).count(), 1); @@ -166,7 +269,7 @@ TEST(Geospatial_GeoWithinShapes) ColKey location_column_key = table->get_column_key("location"); std::vector shapes = { - Geospatial{GeoCenterSphere{1, GeoPoint{0, 0}}}, + Geospatial{GeoCircle{1, GeoPoint{0, 0}}}, Geospatial{GeoBox{GeoPoint{-5, -5}, GeoPoint{5, 5}}}, Geospatial{GeoPolygon{{{-5, -5}, {5, -5}, {5, 5}, {-5, 5}, {-5, -5}}}}, }; @@ -180,4 +283,91 @@ TEST(Geospatial_GeoWithinShapes) } } +TEST(Geospatial_PolygonValidation) +{ + Group g; + std::vector points = {GeoPoint{40.7128, -74.0060}, GeoPoint{55.6761, 12.5683}, + GeoPoint{55.6280, 12.0826}}; + TableRef table = setup_with_points(g, points); + ColKey location_column_key = table->get_column_key("location"); + Geospatial geo_poly{GeoPolygon{{GeoPoint{40.7128, -74.006}, GeoPoint{55.6761, 12.5683}, GeoPoint{55.628, 12.0826}, + GeoPoint{40.7128, -74.006}}}}; + CHECK(geo_poly.is_valid().is_ok()); + Query query = table->column(location_column_key).geo_within(geo_poly); + CHECK_EQUAL(query.count(), 1); + + // same as above because the normalized polygon inverts when covering more than a hemisphere + Geospatial geo_poly_reversed{GeoPolygon{{GeoPoint{40.7128, -74.006}, GeoPoint{55.628, 12.0826}, + GeoPoint{55.6761, 12.5683}, GeoPoint{40.7128, -74.006}}}}; + CHECK(geo_poly_reversed.is_valid().is_ok()); + query = table->column(location_column_key).geo_within(geo_poly_reversed); + CHECK_EQUAL(query.count(), 1); + + Geospatial poly_mismatch_loop{GeoPolygon{{GeoPoint{40.7128, -74.006}, GeoPoint{55.6761, 12.5683}, + GeoPoint{55.628, 12.0826}, GeoPoint{40.7128, -74.000}}}}; + Status status = poly_mismatch_loop.is_valid(); + CHECK(!status.is_ok()); + CHECK_EQUAL(status.reason(), "Ring is not closed, first vertex 'GeoPoint([40.7128, -74.006])' does not equal " + "last vertex 'GeoPoint([40.7128, -74])'"); + + Geospatial poly_three_point{ + GeoPolygon{{GeoPoint{40.7128, -74.006}, GeoPoint{55.6761, 12.5683}, GeoPoint{40.7128, -74.006}}}}; + status = poly_three_point.is_valid(); + CHECK(!status.is_ok()); + CHECK_EQUAL(status.reason(), "Ring 0 must have at least 3 different vertices, 2 unique vertices were provided"); + + Geospatial loop_outside{ + GeoPolygon{{{GeoPoint{40.7128, -74.006}, GeoPoint{55.6761, 12.5683}, GeoPoint{55.628, 12.0826}, + GeoPoint{40.7128, -74.006}}, + {GeoPoint{39, -74.006}, GeoPoint{56, 12.5683}, GeoPoint{56, 12.0826}, GeoPoint{39, -74.006}}}}}; + status = loop_outside.is_valid(); + CHECK(!status.is_ok()); + CHECK_EQUAL( + status.reason(), + "Secondary ring 1 not contained by first exterior ring - secondary rings must be holes in the first ring"); + + Geospatial touching_vertices{GeoPolygon{{{GeoPoint{40.7128, -74.006}, GeoPoint{55.6761, 12.5683}, + GeoPoint{55.628, 12.0826}, GeoPoint{40.7128, -74.006}}, + {GeoPoint{40.7128, -74.006}, GeoPoint{55.6761, 12.5683}, + GeoPoint{55.628, 12.0826}, GeoPoint{40.7128, -74.006}}}}}; + status = touching_vertices.is_valid(); + CHECK(!status.is_ok()); + CHECK_EQUAL(status.reason(), "Polygon isn't valid: 'Duplicate edge: ring 1, edge 0 and ring 0, edge 0'"); + + Geospatial touching_interior_holes{ + GeoPolygon{{{GeoPoint{55.652263, 12.046461}, GeoPoint{55.621198, 12.051422}, GeoPoint{55.615860, 12.132292}, + GeoPoint{55.658441, 12.115444}, GeoPoint{55.652263, 12.046461}}, + {GeoPoint{55.629568, 12.098421}, GeoPoint{55.628449, 12.098661}, GeoPoint{55.628670, 12.100613}, + GeoPoint{55.629670, 12.100283}, GeoPoint{55.629568, 12.098421}}, + {// shares the same north edge as the previous hole + GeoPoint{55.629568, 12.098421}, GeoPoint{55.626245, 12.099442}, GeoPoint{55.626432, 12.100973}, + GeoPoint{55.629670, 12.100283}, GeoPoint{55.629568, 12.098421}}}}}; + status = touching_interior_holes.is_valid(); + CHECK(!status.is_ok()); + CHECK_EQUAL(status.reason(), "Polygon isn't valid: 'Duplicate edge: ring 2, edge 3 and ring 1, edge 3'"); + + Geospatial empty_poly{GeoPolygon{std::vector>{}}}; + status = empty_poly.is_valid(); + CHECK(!status.is_ok()); + CHECK_EQUAL(status.reason(), "Polygon has no rings."); + + Geospatial poly_duplicates{ + GeoPolygon{{GeoPoint{0, 0}, GeoPoint{0, 1}, GeoPoint{0, 1}, GeoPoint{0, 1}, GeoPoint{1, 1}, GeoPoint{0, 0}}}}; + status = poly_duplicates.is_valid(); + CHECK(status.is_ok()); // adjacent duplicates are removed + + Geospatial poly_intersect{ + GeoPolygon{{GeoPoint{0, 0}, GeoPoint{0, 1}, GeoPoint{2, 1}, GeoPoint{2, 2}, GeoPoint{0, 0}}}}; + status = poly_intersect.is_valid(); + CHECK(!status.is_ok()); + CHECK_EQUAL(status.reason(), + "Ring 0 is not valid: 'Edges 1 and 3 cross. Edge locations in degrees: [1.0000000, " + "0.0000000]-[1.0000000, 2.0000000] and [2.0000000, 2.0000000]-[0.0000000, 0.0000000]'"); + + // this appears to be a line, but because the points are mapped to a sphere, it is not + Geospatial poly_line{GeoPolygon{{GeoPoint{0, 0}, GeoPoint{1, 1}, GeoPoint{2, 2}, GeoPoint{0, 0}}}}; + status = poly_line.is_valid(); + CHECK(status.is_ok()); +} + #endif diff --git a/test/test_table.cpp b/test/test_table.cpp index 61c3ee2f160..6a536e70702 100644 --- a/test/test_table.cpp +++ b/test/test_table.cpp @@ -5534,7 +5534,7 @@ TEST(Table_EmbeddedObjectCreateAndDestroyDictionary) CHECK(table->size() == 6); parent_dict.create_and_insert_linked_object("one"); // implicitly remove entry for 02 CHECK(!o2.is_valid()); - CHECK(table->size() == 4); + CHECK_EQUAL(table->size(), 4); parent_dict.clear(); CHECK(table->size() == 0); parent_dict.create_and_insert_linked_object("four"); diff --git a/test/test_unresolved_links.cpp b/test/test_unresolved_links.cpp index 16f530b9bf8..db675150829 100644 --- a/test/test_unresolved_links.cpp +++ b/test/test_unresolved_links.cpp @@ -260,6 +260,86 @@ TEST(Unresolved_LinkList) CHECK_EQUAL(stock_copy.get(3), mercedes.get_key()); } +TEST(Unresolved_LinkSet) +{ + Group g; + + auto cars = g.add_table_with_primary_key("Car", type_String, "model"); + auto dealers = g.add_table_with_primary_key("Dealer", type_Int, "cvr"); + auto col_has = dealers->add_column_set(*cars, "stock"); + + auto dealer = dealers->create_object_with_primary_key(18454033); + auto stock1 = dealer.get_linkset(col_has); + auto stock2 = dealer.get_linkset(col_has); + + auto skoda = cars->create_object_with_primary_key("Skoda Fabia"); + auto tesla = cars->create_object_with_primary_key("Tesla 10"); + auto volvo = cars->create_object_with_primary_key("Volvo XC90"); + auto bmw = cars->create_object_with_primary_key("BMW 750"); + auto mercedes = cars->create_object_with_primary_key("Mercedes SLC500"); + + stock1.insert(skoda.get_key()); + stock1.insert(tesla.get_key()); + stock1.insert(volvo.get_key()); + stock1.insert(bmw.get_key()); + + CHECK_EQUAL(stock1.size(), 4); + CHECK_EQUAL(stock2.size(), 4); + tesla.invalidate(); + CHECK_EQUAL(stock1.size(), 3); + CHECK_EQUAL(stock2.size(), 3); + + stock1.insert(mercedes.get_key()); + // If REALM_MAX_BPNODE_SIZE is 4, we test that context flag is copied over when replacing root + CHECK_EQUAL(stock1.size(), 4); + CHECK_EQUAL(stock2.size(), 4); + + LnkSet stock_copy{stock1}; + CHECK_EQUAL(stock_copy.get(3), mercedes.get_key()); +} + +TEST(Unresolved_Dictionary) +{ + Group g; + + auto cars = g.add_table_with_primary_key("Car", type_String, "model"); + auto dealers = g.add_table_with_primary_key("Dealer", type_Int, "cvr"); + auto col_has = dealers->add_column_dictionary(*cars, "stock"); + + auto dealer = dealers->create_object_with_primary_key(18454033); + auto stock1 = dealer.get_dictionary(col_has); + auto stock2 = dealer.get_dictionary(col_has); + + auto skoda = cars->create_object_with_primary_key("Skoda Fabia"); + auto tesla = cars->create_object_with_primary_key("Tesla 10"); + auto volvo = cars->create_object_with_primary_key("Volvo XC90"); + auto bmw = cars->create_object_with_primary_key("BMW 750"); + auto mercedes = cars->create_object_with_primary_key("Mercedes SLC500"); + + stock1.insert("1", skoda); + stock1.insert("2", tesla); + stock1.insert("3", volvo); + stock1.insert("4", bmw); + + CHECK_EQUAL(stock1.size(), 4); + CHECK_EQUAL(stock2.size(), 4); + tesla.invalidate(); + + // Dictionary changes to null on removal rather than removing the entry + CHECK_EQUAL(stock1.size(), 4); + CHECK_EQUAL(stock2.size(), 4); + CHECK_EQUAL(stock1.get_any(1), Mixed()); + CHECK_EQUAL(stock2.get_any(1), Mixed()); + + stock1.insert("5", mercedes); + // If REALM_MAX_BPNODE_SIZE is 4, we test that context flag is copied over when replacing root + CHECK_EQUAL(stock1.size(), 5); + CHECK_EQUAL(stock2.size(), 5); + + Dictionary stock_copy{stock1}; + CHECK_EQUAL(stock_copy.get("5").get(), mercedes.get_key()); +} + TEST(Unresolved_NullKey) { Group group; @@ -326,59 +406,117 @@ TEST(Unresolved_MixedIndexed) } } - -TEST(Unresolved_QueryOverLinks) +TEST(Unresolved_SortList) { Group g; + auto origin = g.add_table("origin"); + auto target = g.add_table_with_primary_key("target", type_Int, "_id"); + origin->add_column_list(*target, "list"); + + auto obj1 = target->create_object_with_primary_key(1); + auto obj2 = target->create_object_with_primary_key(2); + auto obj3 = target->create_object_with_primary_key(3); + + auto list = origin->create_object().get_linklist("list"); + list.add(obj1.get_key()); + list.add(obj2.get_key()); + list.add(obj3.get_key()); + + obj2.invalidate(); + CHECK_EQUAL(list.size(), 2); + + auto sorted = list.get_sorted_view(target->get_column_key("_id"), false); + CHECK_EQUAL(sorted.size(), 2); + CHECK_EQUAL(sorted.get_key(0), obj3.get_key()); + CHECK_EQUAL(sorted.get_key(1), obj1.get_key()); +} - auto cars = g.add_table_with_primary_key("Car", type_String, "model"); - auto col_price = cars->add_column(type_Decimal, "price"); - auto persons = g.add_table_with_primary_key("Person", type_String, "e-mail"); - auto col_owns = persons->add_column(*cars, "car"); - auto dealers = g.add_table_with_primary_key("Dealer", type_Int, "cvr"); - auto col_has = dealers->add_column_list(*cars, "stock"); - - auto finn = persons->create_object_with_primary_key("finn.schiermer-andersen@mongodb.com"); - auto mathias = persons->create_object_with_primary_key("mathias@10gen.com"); - auto bilcentrum = dealers->create_object_with_primary_key(18454033); - auto bilmekka = dealers->create_object_with_primary_key(26293995); - auto skoda = cars->create_object_with_primary_key("Skoda Fabia").set(col_price, Decimal128("149999.5")); - auto tesla = cars->create_object_with_primary_key("Tesla 3").set(col_price, Decimal128("449999.5")); - auto volvo = cars->create_object_with_primary_key("Volvo XC90").set(col_price, Decimal128("1056000")); - auto bmw = cars->create_object_with_primary_key("BMW 750").set(col_price, Decimal128("2088188")); - auto mercedes = cars->create_object_with_primary_key("Mercedes SLC500").set(col_price, Decimal128("2355103")); - - finn.set(col_owns, skoda.get_key()); - mathias.set(col_owns, bmw.get_key()); - - { - auto stock = bilcentrum.get_linklist(col_has); - stock.add(skoda.get_key()); - stock.add(tesla.get_key()); - stock.add(volvo.get_key()); - } - { - auto stock = bilmekka.get_linklist(col_has); - stock.add(volvo.get_key()); - stock.add(bmw.get_key()); - stock.add(mercedes.get_key()); - } - - auto q = dealers->link(col_has).column(col_price) < Decimal128("1000000"); - CHECK_EQUAL(q.count(), 1); - - auto new_tesla = cars->get_objkey_from_primary_key("Tesla 10"); - bilmekka.get_list(col_has).insert(0, new_tesla); - CHECK_EQUAL(q.count(), 1); - - q = persons->link(col_owns).column(col_price) < Decimal128("1000000"); - CHECK_EQUAL(q.count(), 1); - mathias.set(col_owns, new_tesla); - CHECK_EQUAL(q.count(), 1); +TEST(Unresolved_SortOverLink) +{ + Group g; + auto origin = g.add_table("origin"); + auto target = g.add_table_with_primary_key("target", type_Int, "_id"); + auto link_col = origin->add_column(*target, "link"); + auto pk_col = target->get_column_key("_id"); + + auto t1 = target->create_object_with_primary_key(1); + auto t2 = target->create_object_with_primary_key(2); + auto t3 = target->create_object_with_primary_key(3); + + auto o1 = origin->create_object().set_all(t1.get_key()); + auto o2 = origin->create_object().set_all(t2.get_key()); + auto o3 = origin->create_object().set_all(t3.get_key()); + + t2.invalidate(); + + auto sorted = origin->get_sorted_view(SortDescriptor({{link_col, pk_col}}, {false})); + CHECK_EQUAL(sorted.size(), 3); + // Descending order means link to invalidated comes first (as the value is nil) + CHECK_EQUAL(sorted.get_key(0), o2.get_key()); + CHECK_EQUAL(sorted.get_key(1), o3.get_key()); + CHECK_EQUAL(sorted.get_key(2), o1.get_key()); +} - auto stock = bilmekka.get_linklist(col_has); - q = cars->where(stock).and_query(cars->column(col_price) < Decimal128("2000000")); - CHECK_EQUAL(q.count(), 1); +TEST(Unresolved_QueryOverLinks) +{ + Group g; + auto origin = g.add_table("origin"); + auto target = g.add_table_with_primary_key("target", type_Int, "_id"); + auto link_col = origin->add_column(*target, "link"); + auto list_col = origin->add_column_list(*target, "list"); + auto set_col = origin->add_column_set(*target, "set"); + auto dict_col = origin->add_column_dictionary(*target, "dict"); + auto pk_col = target->get_column_key("_id"); + + auto t1 = target->create_object_with_primary_key(1); + auto t2 = target->create_object_with_primary_key(2); + auto t3 = target->create_object_with_primary_key(3); + + auto o1 = origin->create_object().set_all(t1.get_key()); + auto o2 = origin->create_object().set_all(t2.get_key()); + auto o3 = origin->create_object().set_all(t3.get_key()); + + auto list = o1.get_linklist(list_col); + list.add(t1.get_key()); + list.add(t2.get_key()); + list.add(t3.get_key()); + + auto set = o1.get_linkset(set_col); + set.insert(t1.get_key()); + set.insert(t2.get_key()); + set.insert(t3.get_key()); + + auto dict = o1.get_dictionary(dict_col); + dict.insert("1", t1); + dict.insert("2", t2); + dict.insert("3", t3); + + t2.invalidate(); + + // Query over a single link of each type + CHECK_EQUAL((origin->column(link_col) == t3).count(), 1); + CHECK_EQUAL((origin->link(link_col).column(pk_col) > 1).count(), 1); + CHECK_EQUAL((origin->link(list_col).column(pk_col) > 1).count(), 1); + CHECK_EQUAL((origin->link(set_col).column(pk_col) > 1).count(), 1); + CHECK_EQUAL((origin->link(dict_col).column(pk_col) > 1).count(), 1); + + // Query the collections themselves + CHECK_EQUAL(target->where(list).and_query(target->column(pk_col) > 1).count(), 1); + CHECK_EQUAL(target->where(set).and_query(target->column(pk_col) > 1).count(), 1); + CHECK_EQUAL(target->where(dict).and_query(target->column(pk_col) > 1).count(), 1); + + // Add a second level of links as that hits a slightly different code path + auto origin2 = g.add_table("origin2"); + auto link2_col = origin2->add_column(*origin, "link"); + origin2->create_object().set_all(o1.get_key()); + origin2->create_object().set_all(o2.get_key()); + origin2->create_object().set_all(o3.get_key()); + + CHECK_EQUAL((origin2->link(link2_col).column(link_col) == t3).count(), 1); + CHECK_EQUAL((origin2->link(link2_col).link(link_col).column(pk_col) > 1).count(), 1); + CHECK_EQUAL((origin2->link(link2_col).link(list_col).column(pk_col) > 1).count(), 1); + CHECK_EQUAL((origin2->link(link2_col).link(set_col).column(pk_col) > 1).count(), 1); + CHECK_EQUAL((origin2->link(link2_col).link(dict_col).column(pk_col) > 1).count(), 1); } TEST(Unresolved_PrimaryKeyInt) diff --git a/tools/cmake/SpecialtyBuilds.cmake b/tools/cmake/SpecialtyBuilds.cmake index d69034a9359..938bf0cdc32 100644 --- a/tools/cmake/SpecialtyBuilds.cmake +++ b/tools/cmake/SpecialtyBuilds.cmake @@ -83,7 +83,7 @@ if(REALM_ASAN) message(FATAL_ERROR "The Address Sanitizer is not yet supported on Visual Studio builds") else() - list(APPEND REALM_SANITIZER_FLAGS -fsanitize=address) + list(APPEND REALM_SANITIZER_FLAGS -fsanitize=address -fno-sanitize-recover=all -fsanitize-address-use-after-scope) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O1 -g") endif() endif() @@ -97,7 +97,7 @@ if(REALM_TSAN) message(FATAL_ERROR "The Thread Sanitizer is not yet supported on Visual Studio builds") else() - list(APPEND REALM_SANITIZER_FLAGS -fsanitize=thread) + list(APPEND REALM_SANITIZER_FLAGS -fsanitize=thread -fno-sanitize-recover=all) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -g -fPIE") # According to the clang docs, if -fsanitize=thread is specified then compiling # and linking with PIE is turned on automatically. @@ -130,7 +130,7 @@ if(REALM_USAN) message(FATAL_ERROR "The Undefined Sanitizer is not yet supported on Visual Studio builds") else() - list(APPEND REALM_SANITIZER_FLAGS -fsanitize=undefined) + list(APPEND REALM_SANITIZER_FLAGS -fsanitize=undefined -fno-sanitize-recover=all) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -O2 -g -fPIE -pie") endif() endif() From a7f2242344918f19e0c96290728a3c0aa0ff27cf Mon Sep 17 00:00:00 2001 From: Yavor Georgiev Date: Wed, 7 Jun 2023 12:22:16 +0200 Subject: [PATCH 2/3] Properly register libuv scheduler in ObjectStore tests --- test/object-store/CMakeLists.txt | 5 +---- test/object-store/main.cpp | 10 ++++++++++ test/object-store/util/event_loop.cpp | 6 +++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/test/object-store/CMakeLists.txt b/test/object-store/CMakeLists.txt index d4977596e49..9e3204c9c32 100644 --- a/test/object-store/CMakeLists.txt +++ b/test/object-store/CMakeLists.txt @@ -164,10 +164,7 @@ if(NOT APPLE AND NOT EMSCRIPTEN AND NOT WINDOWS_STORE) set(libuv_target uv_a) endif() target_link_libraries(ObjectStoreTests ${libuv_target}) - # FIXME: ObjectStore itself shouldn't care about this, but we need to refactor scheduler.cpp to make it happen - target_compile_definitions(ObjectStore PUBLIC REALM_HAVE_UV=1) - get_property(libuv_include_dir TARGET ${libuv_target} PROPERTY INCLUDE_DIRECTORIES) - target_include_directories(ObjectStore PRIVATE ${libuv_include_dir}) + target_compile_definitions(ObjectStoreTests PRIVATE TEST_SCHEDULER_UV=1) endif() add_subdirectory(notifications-fuzzer) diff --git a/test/object-store/main.cpp b/test/object-store/main.cpp index f5519cbc2f3..18954217633 100644 --- a/test/object-store/main.cpp +++ b/test/object-store/main.cpp @@ -22,6 +22,10 @@ #include #include +#if TEST_SCHEDULER_UV +#include +#endif + #include #include #include @@ -60,6 +64,12 @@ int main(int argc, const char** argv) } } +#if TEST_SCHEDULER_UV + realm::util::Scheduler::set_default_factory([]() { + return std::make_shared(); + }); +#endif + Catch::Session session; session.useConfigData(config); int result = session.run(argc, argv); diff --git a/test/object-store/util/event_loop.cpp b/test/object-store/util/event_loop.cpp index 6463179e12e..98bc72e2858 100644 --- a/test/object-store/util/event_loop.cpp +++ b/test/object-store/util/event_loop.cpp @@ -26,7 +26,7 @@ #include #include -#if REALM_USE_UV +#if TEST_SCHEDULER_UV #include #elif REALM_PLATFORM_APPLE #include @@ -87,7 +87,7 @@ struct EventLoop::Impl { ~Impl(); private: -#if REALM_USE_UV +#if TEST_SCHEDULER_UV Impl(uv_loop_t* loop); std::vector> m_pending_work; @@ -132,7 +132,7 @@ void EventLoop::run_pending() return m_impl->run_pending(); } -#if REALM_USE_UV +#if TEST_SCHEDULER_UV bool EventLoop::has_implementation() { From fcc39bd436992a1b34a80ef9c7ab59907af8dffc Mon Sep 17 00:00:00 2001 From: Mathias Stearn Date: Wed, 7 Jun 2023 23:43:12 +0200 Subject: [PATCH 3/3] Add spec validator and fix discovered issues with spec --- bindgen/CMakeLists.txt | 15 ++ bindgen/spec.yml | 55 ++----- bindgen/src/realm_helpers.h | 27 ++-- bindgen/src/templates/validate_spec.ts | 155 +++++++++++++++++++ src/realm/object-store/schema.cpp | 18 +-- src/realm/object-store/schema.hpp | 42 ++++- src/realm/object-store/shared_realm.cpp | 4 +- src/realm/object-store/shared_realm.hpp | 4 +- src/realm/object-store/sync/sync_manager.cpp | 4 +- src/realm/object-store/sync/sync_manager.hpp | 4 +- src/realm/object-store/sync/sync_user.hpp | 1 + 11 files changed, 253 insertions(+), 76 deletions(-) create mode 100644 bindgen/src/templates/validate_spec.ts diff --git a/bindgen/CMakeLists.txt b/bindgen/CMakeLists.txt index 895db35bc81..43707147aa8 100644 --- a/bindgen/CMakeLists.txt +++ b/bindgen/CMakeLists.txt @@ -3,6 +3,7 @@ file(GLOB_RECURSE BINDGEN_LIB_TS_FILES CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/*.ts ) +list(FILTER BINDGEN_LIB_TS_FILES EXCLUDE REGEX "templates/[^/]*\.ts$") list(FILTER BINDGEN_LIB_TS_FILES EXCLUDE REGEX "tests/[^/]*\.ts$") # Ninja buffers output so we need to tell tools to use colors even though stdout isn't a tty. @@ -84,3 +85,17 @@ function(bindgen) set_property(SOURCE ${FILE} PROPERTY INCLUDE_DIRECTORIES "$<$:${CMAKE_CURRENT_FUNCTION_LIST_DIR}/src>") endforeach() endfunction() + +bindgen( + TEMPLATE ${CMAKE_CURRENT_SOURCE_DIR}/src/templates/validate_spec.ts + OUTPUTS ${CMAKE_CURRENT_BINARY_DIR}/validate_spec.cpp + OUTDIR ${CMAKE_CURRENT_BINARY_DIR} +) + +add_library(ValidateBindgenSpec MODULE ${CMAKE_CURRENT_BINARY_DIR}/validate_spec.cpp) +set_property(TARGET ValidateBindgenSpec PROPERTY CXX_STANDARD 20) +target_link_libraries(ValidateBindgenSpec Realm::ObjectStore) +if (NOT MSVC) + # Ensure that linking to Realm::ObjectStore doesn't leave undefined symbols. + target_link_options(ValidateBindgenSpec PRIVATE LINKER:-z,defs) +endif() diff --git a/bindgen/spec.yml b/bindgen/spec.yml index e470877981e..51441b7665b 100644 --- a/bindgen/spec.yml +++ b/bindgen/spec.yml @@ -1,6 +1,7 @@ # yaml-language-server: $schema=generated/spec.schema.json headers: + - "realm_helpers.h" - "realm/sync/config.hpp" - "realm/sync/subscriptions.hpp" - "realm/object-store/shared_realm.hpp" @@ -455,6 +456,7 @@ records: query_string: std::string ObjectChangeSet: + cppName: Helpers::ObjectChangeSet fields: is_deleted: bool changed_columns: std::vector @@ -539,9 +541,9 @@ records: type: std::error_code cppName: get_system_error() is_fatal: bool - simple_message: StringData - logURL: StringData - user_info: std::unordered_map + simple_message: std::string_view + logURL: std::string_view + user_info: std::unordered_map is_unrecognized_by_client: type: bool default: false @@ -605,7 +607,6 @@ records: opaqueTypes: - Schema - - Group - AuditInterface classes: @@ -629,15 +630,15 @@ classes: make_network_transport: '(runRequest: (request: const Request&, callback: util::UniqueFunction<(response: Response&&)>&&) off_thread) -> SharedGenericNetworkTransport' delete_data_for_object: '(realm: SharedRealm, object_type: StringData)' is_empty_realm: '(realm: SharedRealm) -> bool' - base64_decode: '(input: StringData) -> BinaryData' + base64_decode: '(input: StringData) -> OwnedBinaryData' make_logger_factory: '(log: (level: LoggerLevel, message: const std::string&) off_thread) -> LoggerFactory' make_logger: '(log: (level: LoggerLevel, message: const std::string&) off_thread) -> SharedLogger' simulate_sync_error: '(session: SyncSession&, code: const int&, message: const std::string&, type: const std::string&, is_fatal: bool)' - consume_thread_safe_reference_to_shared_realm: '(tsr: ThreadSafeReference) -> SharedRealm' + consume_thread_safe_reference_to_shared_realm: '(tsr: ThreadSafeReference&) -> SharedRealm' file_exists: '(path: StringData) -> bool' erase_subscription: '(subs: MutableSyncSubscriptionSet&, sub_to_remove: const SyncSubscription&) -> bool' # This is added due to DescriptorOrdering not being exposed - get_results_description: '(results: const Results&) -> StringData' + get_results_description: '(results: const Results&) -> std::string' feed_buffer: '(ws: WatchStream&, buffer: BinaryData)' # Converts char* and length to a combined string_view argument to callback. # TODO: Consider making preverify_ok a bool. @@ -708,29 +709,6 @@ classes: Transaction: sharedPtrWrapped: TransactionRef - ObjectStore: - staticMethods: - get_schema_version: '(group: Group) -> SchemaVersion' - set_schema_version: '(group: Group, version: SchemaVersion)' - # verify_no_migration_required: '(changes: std::vector)' - #needs_migration: '(changes: std::vector) -> bool' - #verify_valid_additive_changes: - # - '(changes: std::vector) -> bool' - # - suffix: and_update_indexes - # sig: '(changes: std::vector, update_indexes: bool) -> bool' - #verify_valid_external_changes: '(changes: std::vector)' - #verify_compatible_for_immutable_and_readonly: '(changes: std::vector)' - #verify_no_changes_required: '(changes: std::vector)' - - # apply_schema_changes: '(group: Transaction&, schema_version: SchemaVersion, target_schema: Schema&, target_schema_version: SchemaVersion, mode: SchemaMode, changes: std::vector, migration_function: () -> void)' - # apply_additive_changes: '(group: Group&, changes: std::vector, update_indexes: bool)' - # table_for_object_type - # table_for_Schema_type - - schema_from_group: '(group: Group) -> std::vector' - - # some more... - # TODO should this be nullable or always non-null? Timestamp: constructors: @@ -752,7 +730,7 @@ classes: Query: properties: get_table: ConstTableRef - get_description: StringData + get_description: std::string methods: count: () const -> count_t @@ -782,9 +760,9 @@ classes: get_type: PropertyType methods: size: () -> count_t - index_of: - - sig: '(value: Mixed) const -> count_t' - - sig: '(obj: Obj) const -> count_t' + index_of: # TODO should these be const? + - sig: '(value: Mixed) -> count_t' + - sig: '(obj: Obj) -> count_t' suffix: 'obj' get: # NOTE: this is actually a template with a defaulted template param. - sig: '(index: count_t) -> Obj' @@ -798,7 +776,7 @@ classes: suffix: by_names limit: '(max_count: count_t) const -> Results' snapshot: '() const -> Results' - freeze: '(frozen_realm: SharedRealm) const -> Results' + freeze: '(frozen_realm: SharedRealm) -> Results' # TODO const max: '(column: ColKey) -> util::Optional' min: '(column: ColKey) -> util::Optional' average: '(column: ColKey) -> util::Optional' @@ -853,7 +831,6 @@ classes: cancel_transaction: () freeze: () -> SharedRealm last_seen_transaction_version: () -> uint64_t - read_group: () -> Group& duplicate: () -> TransactionRef update_schema: '(schema: std::vector, version: SchemaVersion, migration_function: Nullable) -> void>>, initialization_function: Nullable void>>, in_transaction: bool) -> void' @@ -1078,7 +1055,6 @@ classes: SyncUser: sharedPtrWrapped: SharedSyncUser properties: - all_sessions: std::vector is_logged_in: bool identity: const std::string& provider_type: const std::string& @@ -1095,6 +1071,7 @@ classes: subscribers_count: count_t methods: log_out: () + all_sessions: () -> std::vector session_for_on_disk_path: '(path: StringData) -> Nullable' subscribe: '(observer: (user: IgnoreArgument)) -> SyncUserSubscriptionToken' unsubscribe: '(token: SyncUserSubscriptionToken)' @@ -1217,7 +1194,7 @@ classes: set_timeouts: '(timeouts: SyncClientTimeouts)' reconnect: () wait_for_sessions_to_terminate: () - path_for_realm: '(config: SyncConfig, custom_file_name: util::Optional) -> StringData' + path_for_realm: '(config: SyncConfig, custom_file_name: util::Optional) -> std::string' get_existing_active_session: '(path: const std::string&) -> SharedSyncSession' ThreadSafeReference: {} @@ -1234,7 +1211,7 @@ classes: properties: state: SyncSessionState connection_state: SyncSessionConnectionState - path: std::string + path: const std::string& user: SharedSyncUser config: SyncConfig full_realm_url: util::Optional diff --git a/bindgen/src/realm_helpers.h b/bindgen/src/realm_helpers.h index 8699e33b4ba..bbe487bcc80 100644 --- a/bindgen/src/realm_helpers.h +++ b/bindgen/src/realm_helpers.h @@ -42,7 +42,7 @@ // Equivalent to std::forward(x), but faster to compile and less impact on debug builds #define FWD(x) ((decltype(x)&&)(x)) -namespace realm::js { +namespace realm { namespace { // These types are exposed to JS in the spec. @@ -281,20 +281,21 @@ struct Helpers { return callback(server_address, server_port, std::string_view(pem_data, pem_size), preverify_ok, depth); }; } -}; -struct ObjectChangeSet { - ObjectChangeSet() = default; - /*implicit*/ ObjectChangeSet(const CollectionChangeSet& changes) - { - is_deleted = !changes.deletions.empty(); - for (const auto& [col_key_val, index_set] : changes.columns) { - changed_columns.push_back(ColKey(col_key_val)); + // TODO There is already a realm::ObjectChangeSet. Find a better name for this that doesn't collide. + struct ObjectChangeSet { + ObjectChangeSet() = default; + /*implicit*/ ObjectChangeSet(const CollectionChangeSet& changes) + { + is_deleted = !changes.deletions.empty(); + for (const auto& [col_key_val, index_set] : changes.columns) { + changed_columns.push_back(ColKey(col_key_val)); + } } - } - bool is_deleted; - std::vector changed_columns; + bool is_deleted; + std::vector changed_columns; + }; }; //////////////////////////////////////////////////////////// @@ -383,4 +384,4 @@ auto asSigned(T num) } } // namespace -} // namespace realm::js +} // namespace realm diff --git a/bindgen/src/templates/validate_spec.ts b/bindgen/src/templates/validate_spec.ts new file mode 100644 index 00000000000..48e63c027c8 --- /dev/null +++ b/bindgen/src/templates/validate_spec.ts @@ -0,0 +1,155 @@ +import { CppVar, CppFunc, CppDecls } from "@realm/bindgen/cpp"; +import { bindModel, Type, Template, Const, Ref, Func, Arg } from "@realm/bindgen/bound-model"; +import { TemplateContext } from "@realm/bindgen/context"; +import { clangFormat } from "@realm/bindgen/formatter"; + +// This is a good starting place but it is incomplete. It only checks convertibility, not for correct types. +// In some cases this is OK. Especially SDK -> C++. However, for C++ -> SDK, we often need more precise types, +// eg because we call methods on them. This is mostly an issue for primitives and templates. It is _especially_ +// a problem for types like Nullable<> which aren't reflected directly in the C++ types, but require that types +// used as arguments support a specific API. We should also ensure that numeric types have the right sign and width. + +function funcToStdFunction(sig: Type): Type { + if (sig.kind != "Func") return sig; + return new Template("std::function", [ + new Func( + funcToStdFunction(sig.ret), + sig.args.map((a) => new Arg(a.name, funcToStdFunction(a.type))), + sig.isConst, + sig.noexcept, + sig.isOffThread, + ), + ]); +} + +function requiresExactReturn(ret: Type) { + ret = ret.removeConstRef(); + + // We require that anything returning a view (including references) must return that exact type. + // This is to ensure that the lifetime of buffers are long enough to survive returning to the SDK, + // without copying to a separate buffer. std::string is checked to ensure that we declare things + // as views when we should, and because `const string&` is a kind of view. + if (ret.kind == "Primitive") { + return ["StringData", "std::string_view", "std::string", "BinaryData", "Mixed"].includes(ret.name); + } + + if (ret.kind == "Template") { + // Optional is convertible from T, so make sure we aren't marking things optional incorrectly. + // TODO remove this once we check API rather than convertibility. + if (ret.name == "util::Optional") return true; + return ret.args.some(requiresExactReturn); + } + return false; +} + +export function generate({ spec, file: makeFile }: TemplateContext): void { + const out = makeFile("validate_spec.cpp", clangFormat); + + // HEADER + out(`// This file is generated: Update the spec instead of editing this file directly`); + + for (const header of spec.headers) { + out(`#include <${header}>`); + } + + out(`namespace realm::validate_spec {`); + + const model = bindModel(spec); + const decls = new CppDecls(); + + for (const enm of model.enums) { + decls.static_asserts.push(`std::is_enum_v<${enm.cppName}>`); + decls.static_asserts.push(`sizeof(${enm.cppName}) <= sizeof(int32_t), "we only support enums up to 32 bits"`); + for (const { name, value } of enm.enumerators) { + decls.static_asserts.push(`${enm.cppName}(int(${value})) == ${enm.cppName}::${name}`); + } + } + + for (const rec of model.records) { + decls.free_funcs.push( + new CppFunc( + `ensure_can_consume_${rec.name}`, + "void", + [new CppVar(`[[maybe_unused]] const ${rec.cppName}&`, "rec")], + { + body: `${rec.fields + .filter((f) => !f.type.isFunction()) // Functions on structs only go _from_ the sdk to core. + .map((f) => `[[maybe_unused]] ${f.type.toCpp()} ${f.name} = rec.${f.cppName};`) + .join("\n")}`, + }, + ), + ); + + // SyncError only gets converted to the SDK, so it doesn't need to be default constructable. + // TODO stop hard coding here. Make this specified in spec or detect automatically like a binding does. + if (rec.cppName == "SyncError") { + continue; + } + + decls.free_funcs.push( + new CppFunc( + `ensure_can_produce_${rec.name}`, + rec.cppName, + rec.fields.map((f) => new CppVar(f.type.toCpp(), f.name)), + { + body: ` + auto out = ${rec.cppName}(); + ${rec.fields + .filter((f) => !f.cppName.endsWith("()")) // These are function calls to send data _to_ the sdk. + .map((f) => `out.${f.cppName} = FWD(${f.name});`) + .join("\n")} + return out; + `, + }, + ), + ); + } + + for (const cls of model.classes) { + const self = cls.needsDeref ? "(*_this)" : "_this"; + for (const method of cls.methods) { + const ret = method.sig.ret; + const ignoreReturn = + // We allow declaring void returns for functions that return values we don't need. + ret.isVoid() || + // TODO these return pairs with iterators that we model as pointers. That doesn't work with the lazy validation + // we are doing, will need better validation. + (cls.name == "MutableSyncSubscriptionSet" && method.name == "insert_or_assign"); + + const args = method.sig.args // + .map((a) => new CppVar(funcToStdFunction(a.type).toCpp(), a.name)); + if (!method.isStatic) { + let thisType: Type = method.sig.isConst ? new Const(cls) : cls; + if (cls.sharedPtrWrapped) { + thisType = new Template("std::shared_ptr", [thisType]); + } + thisType = new Ref(thisType); + args.unshift(new CppVar(thisType.toCpp(), "_this")); + } + const callFunction = method.call({ self }, ...method.sig.args.map((a) => `FWD(${a.name})`)); + + let staticAsserts = ""; + if (requiresExactReturn(ret)) { + staticAsserts += `static_assert(std::is_same_v);`; + } + + decls.free_funcs.push( + new CppFunc(`ensure_can_call_${method.id}`, ignoreReturn ? "void" : ret.toCpp(), args, { + body: `${staticAsserts} ${ignoreReturn ? "" : "return "} ${callFunction}; `, + }), + ); + } + } + + decls.outputDefsTo(out); + + out(` + // Make it seem like all functions will be used by escaping pointers to them. + void* volatile escape; + void use_all_methods() { + ${decls.free_funcs.map((f) => `escape = reinterpret_cast(&${f.name});`).join("\n")} + } + + } // namespace realm::js::node + `); +} diff --git a/src/realm/object-store/schema.cpp b/src/realm/object-store/schema.cpp index 00e54d9f163..6f515552776 100644 --- a/src/realm/object-store/schema.cpp +++ b/src/realm/object-store/schema.cpp @@ -49,7 +49,7 @@ Schema::Schema(std::initializer_list types) } Schema::Schema(base types) noexcept - : base(std::move(types)) + : m_classes(std::move(types)) { sort_schema(); } @@ -201,12 +201,12 @@ void Schema::validate(SchemaValidationMode validation_mode) const // As the types are added sorted by name, we can detect duplicates by just looking at the following element. auto find_next_duplicate = [&](const_iterator start) { - return std::adjacent_find(start, cend(), [](ObjectSchema const& lft, ObjectSchema const& rgt) { + return std::adjacent_find(start, m_classes.end(), [](ObjectSchema const& lft, ObjectSchema const& rgt) { return lft.name == rgt.name; }); }; - for (auto it = find_next_duplicate(cbegin()); it != cend(); it = find_next_duplicate(++it)) { + for (auto it = find_next_duplicate(m_classes.begin()); it != m_classes.end(); it = find_next_duplicate(++it)) { exceptions.push_back( ObjectSchemaValidationException("Type '%1' appears more than once in the schema.", it->name)); } @@ -296,8 +296,8 @@ void Schema::zip_matching(T&& a, U&& b, Func&& func) { size_t i = 0, j = 0; while (i < a.size() && j < b.size()) { - auto& object_schema = a[i]; - auto& matching_schema = b[j]; + auto& object_schema = a.m_classes[i]; + auto& matching_schema = b.m_classes[j]; int cmp = object_schema.name.compare(matching_schema.name); if (cmp == 0) { func(&object_schema, &matching_schema); @@ -314,9 +314,9 @@ void Schema::zip_matching(T&& a, U&& b, Func&& func) } } for (; i < a.size(); ++i) - func(&a[i], nullptr); + func(&a.m_classes[i], nullptr); for (; j < b.size(); ++j) - func(nullptr, &b[j]); + func(nullptr, &b.m_classes[j]); } std::vector Schema::compare(Schema const& target_schema, SchemaMode mode, @@ -381,9 +381,9 @@ void Schema::copy_keys_from(realm::Schema const& other, SchemaSubsetMode subset_ }); if (!other_classes.empty()) { - reserve(size() + other_classes.size()); + m_classes.reserve(size() + other_classes.size()); for (auto other : other_classes) - push_back(*other); + m_classes.push_back(*other); sort_schema(); } } diff --git a/src/realm/object-store/schema.hpp b/src/realm/object-store/schema.hpp index db28751474a..fae5a268fc0 100644 --- a/src/realm/object-store/schema.hpp +++ b/src/realm/object-store/schema.hpp @@ -157,11 +157,14 @@ inline constexpr SchemaSubsetMode SchemaSubsetMode::AllProperties = {false, true inline constexpr SchemaSubsetMode SchemaSubsetMode::Complete = {true, true}; -class Schema : private std::vector { +class Schema { private: using base = std::vector; public: + using iterator = base::iterator; + using const_iterator = base::const_iterator; + Schema() noexcept; ~Schema(); // Create a schema from a vector of ObjectSchema @@ -201,18 +204,43 @@ class Schema : private std::vector { return !(a == b); } - using base::begin; - using base::const_iterator; - using base::empty; - using base::end; - using base::iterator; - using base::size; + operator const base&() const + { + return m_classes; + } + + size_t size() const + { + return m_classes.size(); + } + bool empty() const + { + return m_classes.empty(); + } + iterator begin() + { + return m_classes.begin(); + } + const_iterator begin() const + { + return m_classes.begin(); + } + iterator end() + { + return m_classes.begin(); + } + const_iterator end() const + { + return m_classes.begin(); + } private: template static void zip_matching(T&& a, U&& b, Func&& func); // sort all the classes by name in order to speed up find(StringData name) void sort_schema(); + + base m_classes; }; namespace schema_change { diff --git a/src/realm/object-store/shared_realm.cpp b/src/realm/object-store/shared_realm.cpp index 1d6974cbb6c..ac025ad32f7 100644 --- a/src/realm/object-store/shared_realm.cpp +++ b/src/realm/object-store/shared_realm.cpp @@ -194,7 +194,7 @@ std::shared_ptr Realm::sync_session() const return m_coordinator ? m_coordinator->sync_session() : nullptr; } -sync::SubscriptionSet Realm::get_latest_subscription_set() +sync::SubscriptionSet Realm::get_latest_subscription_set() const { if (!m_config.sync_config || !m_config.sync_config->flx_sync_requested) { throw IllegalOperation("Flexible sync is not enabled"); @@ -205,7 +205,7 @@ sync::SubscriptionSet Realm::get_latest_subscription_set() return flx_sub_store->get_latest(); } -sync::SubscriptionSet Realm::get_active_subscription_set() +sync::SubscriptionSet Realm::get_active_subscription_set() const { if (!m_config.sync_config || !m_config.sync_config->flx_sync_requested) { throw IllegalOperation("Flexible sync is not enabled"); diff --git a/src/realm/object-store/shared_realm.hpp b/src/realm/object-store/shared_realm.hpp index f75bfa147ba..0e61b830edf 100644 --- a/src/realm/object-store/shared_realm.hpp +++ b/src/realm/object-store/shared_realm.hpp @@ -207,8 +207,8 @@ class Realm : public std::enable_shared_from_this { // Returns the latest/active subscription set for a FLX-sync enabled realm. // Throws an exception for a non-FLX realm - sync::SubscriptionSet get_latest_subscription_set(); - sync::SubscriptionSet get_active_subscription_set(); + sync::SubscriptionSet get_latest_subscription_set() const; + sync::SubscriptionSet get_active_subscription_set() const; #endif // Returns a frozen Realm for the given Realm. This Realm can be accessed from any thread. diff --git a/src/realm/object-store/sync/sync_manager.cpp b/src/realm/object-store/sync/sync_manager.cpp index fef83d5d945..9e36b974ab4 100644 --- a/src/realm/object-store/sync/sync_manager.cpp +++ b/src/realm/object-store/sync/sync_manager.cpp @@ -692,13 +692,13 @@ std::shared_ptr SyncManager::get_session(std::shared_ptr db, co return external_reference; } -bool SyncManager::has_existing_sessions() +bool SyncManager::has_existing_sessions() const { util::CheckedLockGuard lock(m_session_mutex); return do_has_existing_sessions(); } -bool SyncManager::do_has_existing_sessions() +bool SyncManager::do_has_existing_sessions() const { return std::any_of(m_sessions.begin(), m_sessions.end(), [](auto& element) { return element.second->existing_external_reference(); diff --git a/src/realm/object-store/sync/sync_manager.hpp b/src/realm/object-store/sync/sync_manager.hpp index c00a6fb6e58..9aca051d2c8 100644 --- a/src/realm/object-store/sync/sync_manager.hpp +++ b/src/realm/object-store/sync/sync_manager.hpp @@ -166,7 +166,7 @@ class SyncManager : public std::enable_shared_from_this { // Returns `true` if the SyncManager still contains any existing sessions not yet fully cleaned up. // This will return true as long as there is an external reference to a session object, no matter // the state of that session. - bool has_existing_sessions() REQUIRES(!m_session_mutex); + bool has_existing_sessions() const REQUIRES(!m_session_mutex); // Blocking call that only return once all sessions have been terminated. // Due to the async nature of the SyncClient, even with `SyncSessionStopPolicy::Immediate`, a @@ -325,7 +325,7 @@ class SyncManager : public std::enable_shared_from_this { // Internal method returning `true` if the SyncManager still contains sessions not yet fully closed. // Callers of this method should hold the `m_session_mutex` themselves. - bool do_has_existing_sessions() REQUIRES(m_session_mutex); + bool do_has_existing_sessions() const REQUIRES(m_session_mutex); std::string m_sync_route GUARDED_BY(m_mutex); diff --git a/src/realm/object-store/sync/sync_user.hpp b/src/realm/object-store/sync/sync_user.hpp index 1bd5f9e8fca..b68b1a44ca1 100644 --- a/src/realm/object-store/sync/sync_user.hpp +++ b/src/realm/object-store/sync/sync_user.hpp @@ -172,6 +172,7 @@ struct SyncUserIdentity { // the associated provider type of the identity std::string provider_type; + SyncUserIdentity() = default; SyncUserIdentity(const std::string& id, const std::string& provider_type); bool operator==(const SyncUserIdentity& other) const