diff --git a/scripts/download-core.sh b/scripts/download-core.sh index 018b253c..2864449a 100755 --- a/scripts/download-core.sh +++ b/scripts/download-core.sh @@ -4,7 +4,7 @@ set -e set -o pipefail # Set to "latest" for the latest build. -: ${REALM_CORE_VERSION:=1.1.2} +: ${REALM_CORE_VERSION:=1.4.0} if [ "$1" = '--version' ]; then echo "$REALM_CORE_VERSION" @@ -22,7 +22,7 @@ if [ "$1" = 'node' ]; then fi else CORE_DIR='core' - CORE_DOWNLOAD_FILE="realm-core-$REALM_CORE_VERSION.tar.bz2" + CORE_DOWNLOAD_FILE="realm-core-$REALM_CORE_VERSION.tar.xz" fi # Start current working directory at the root of the project. diff --git a/src/js_list.hpp b/src/js_list.hpp index ad119f5b..35c632e1 100644 --- a/src/js_list.hpp +++ b/src/js_list.hpp @@ -207,7 +207,7 @@ void ListClass::snapshot(ContextType ctx, ObjectType this_object, size_t argc validate_argument_count(argc, 0); auto list = get_internal>(this_object); - return_value.set(ResultsClass::create_instance(ctx, *list, false)); + return_value.set(ResultsClass::create_instance(ctx, list->snapshot())); } template diff --git a/src/js_object_accessor.hpp b/src/js_object_accessor.hpp index 512de6ce..bce1089d 100644 --- a/src/js_object_accessor.hpp +++ b/src/js_object_accessor.hpp @@ -147,7 +147,7 @@ struct NativeAccessor { return ListClass::create_instance(ctx, std::move(list)); } static ValueType from_results(ContextType ctx, realm::Results results) { - return ResultsClass::create_instance(ctx, results); + return ResultsClass::create_instance(ctx, std::move(results)); } static Mixed to_mixed(ContextType ctx, ValueType &val) { throw std::runtime_error("'Any' type is unsupported"); diff --git a/src/js_results.hpp b/src/js_results.hpp index 08991f46..3913bb83 100644 --- a/src/js_results.hpp +++ b/src/js_results.hpp @@ -38,10 +38,8 @@ struct ResultsClass : ClassDefinition> { using Value = js::Value; using ReturnValue = js::ReturnValue; - static ObjectType create_instance(ContextType, const realm::Results &, bool live = true); - static ObjectType create_instance(ContextType, const realm::List &, bool live = true); - static ObjectType create_instance(ContextType, SharedRealm, const ObjectSchema &, bool live = true); - static ObjectType create_instance(ContextType, SharedRealm, const ObjectSchema &, Query, bool live = true); + static ObjectType create_instance(ContextType, realm::Results); + static ObjectType create_instance(ContextType, SharedRealm, const ObjectSchema &); template static ObjectType create_filtered(ContextType, const U &, size_t, const ValueType[]); @@ -74,33 +72,14 @@ struct ResultsClass : ClassDefinition> { }; template -typename T::Object ResultsClass::create_instance(ContextType ctx, const realm::Results &results, bool live) { - auto new_results = new realm::Results(results); - new_results->set_live(live); - - return create_object>(ctx, new_results); +typename T::Object ResultsClass::create_instance(ContextType ctx, realm::Results results) { + return create_object>(ctx, new realm::Results(std::move(results))); } template -typename T::Object ResultsClass::create_instance(ContextType ctx, const realm::List &list, bool live) { - return create_instance(ctx, list.get_realm(), list.get_object_schema(), list.get_query(), live); -} - -template -typename T::Object ResultsClass::create_instance(ContextType ctx, SharedRealm realm, const ObjectSchema &object_schema, bool live) { +typename T::Object ResultsClass::create_instance(ContextType ctx, SharedRealm realm, const ObjectSchema &object_schema) { auto table = ObjectStore::table_for_object_type(realm->read_group(), object_schema.name); - auto results = new realm::Results(realm, object_schema, *table); - results->set_live(live); - - return create_object>(ctx, results); -} - -template -typename T::Object ResultsClass::create_instance(ContextType ctx, SharedRealm realm, const ObjectSchema &object_schema, Query query, bool live) { - auto results = new realm::Results(realm, object_schema, std::move(query)); - results->set_live(live); - - return create_object>(ctx, results); + return create_object>(ctx, new realm::Results(realm, *table)); } template @@ -111,18 +90,13 @@ typename T::Object ResultsClass::create_filtered(ContextType ctx, const U &co auto const &realm = collection.get_realm(); auto const &object_schema = collection.get_object_schema(); - std::vector args; - args.reserve(argc - 1); - - for (size_t i = 1; i < argc; i++) { - args.push_back(arguments[i]); - } + std::vector args(&arguments[1], &arguments[argc]); parser::Predicate predicate = parser::parse(query_string); query_builder::ArgumentConverter converter(ctx, realm, args); query_builder::apply_predicate(query, predicate, converter, *realm->config().schema, object_schema.name); - return create_instance(ctx, realm, object_schema, std::move(query)); + return create_instance(ctx, realm::Results(realm, std::move(query))); } template @@ -179,7 +153,7 @@ typename T::Object ResultsClass::create_sorted(ContextType ctx, const U &coll columns.push_back(prop->table_column); } - auto results = new realm::Results(realm, object_schema, collection.get_query(), {std::move(columns), std::move(ascending)}); + auto results = new realm::Results(realm, collection.get_query(), {std::move(columns), std::move(ascending)}); return create_object>(ctx, results); } @@ -209,7 +183,7 @@ void ResultsClass::snapshot(ContextType ctx, ObjectType this_object, size_t a validate_argument_count(argc, 0); auto results = get_internal>(this_object); - return_value.set(ResultsClass::create_instance(ctx, *results, false)); + return_value.set(ResultsClass::create_instance(ctx, results->snapshot())); } template diff --git a/src/object-store/CMake/CompilerFlags.cmake b/src/object-store/CMake/CompilerFlags.cmake index c214b625..be24ea56 100644 --- a/src/object-store/CMake/CompilerFlags.cmake +++ b/src/object-store/CMake/CompilerFlags.cmake @@ -1,12 +1,36 @@ set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED on) set(CMAKE_CXX_EXTENSIONS off) -add_compile_options(-Wall -DREALM_HAVE_CONFIG) add_compile_options("$<$:-DREALM_DEBUG>") add_compile_options("$<$:-DREALM_DEBUG>") +add_compile_options( + -DREALM_HAVE_CONFIG + -Wall + -Wextra + -Wno-missing-field-initializers + -Wempty-body + -Wparentheses + -Wunknown-pragmas + -Wunreachable-code +) + +if(${CMAKE_CXX_COMPILER_ID} MATCHES "Clang") + add_compile_options( + -Wassign-enum + -Wbool-conversion + -Wconditional-uninitialized + -Wconstant-conversion + -Wenum-conversion + -Wint-conversion + -Wmissing-prototypes + -Wnewline-eof + -Wshorten-64-to-32 + -Wimplicit-fallthrough + ) +endif() if(${CMAKE_GENERATOR} STREQUAL "Ninja") - if(${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") + if(${CMAKE_CXX_COMPILER_ID} MATCHES "Clang") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fcolor-diagnostics") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcolor-diagnostics") elseif(${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU") diff --git a/src/object-store/CMake/RealmCore.cmake b/src/object-store/CMake/RealmCore.cmake index 87b7e566..42e2a7e4 100644 --- a/src/object-store/CMake/RealmCore.cmake +++ b/src/object-store/CMake/RealmCore.cmake @@ -28,8 +28,8 @@ function(use_realm_core version_or_path_to_source) endfunction() function(download_realm_core core_version) - set(core_url "https://static.realm.io/downloads/core/realm-core-${core_version}.tar.bz2") - set(core_tarball_name "realm-core-${core_version}.tar.bz2") + set(core_url "https://static.realm.io/downloads/core/realm-core-${core_version}.tar.xz") + set(core_tarball_name "realm-core-${core_version}.tar.xz") set(core_temp_tarball "/tmp/${core_tarball_name}") set(core_directory_parent "${CMAKE_CURRENT_SOURCE_DIR}${CMAKE_FILES_DIRECTORY}") set(core_directory "${core_directory_parent}/realm-core-${core_version}") diff --git a/src/object-store/CMakeLists.txt b/src/object-store/CMakeLists.txt index 90b17789..91fa56db 100644 --- a/src/object-store/CMakeLists.txt +++ b/src/object-store/CMakeLists.txt @@ -10,7 +10,7 @@ include(CompilerFlags) include(Sanitizers) include(RealmCore) -set(REALM_CORE_VERSION "1.1.0" CACHE STRING "") +set(REALM_CORE_VERSION "1.2.0" CACHE STRING "") use_realm_core(${REALM_CORE_VERSION}) set(PEGTL_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/pegtl) diff --git a/src/object-store/src/CMakeLists.txt b/src/object-store/src/CMakeLists.txt index 13cfc856..19c5f766 100644 --- a/src/object-store/src/CMakeLists.txt +++ b/src/object-store/src/CMakeLists.txt @@ -40,6 +40,7 @@ set(HEADERS parser/parser.hpp parser/query_builder.hpp util/atomic_shared_ptr.hpp + util/compiler.hpp util/format.hpp util/thread_id.hpp util/thread_local.hpp) diff --git a/src/object-store/src/impl/realm_coordinator.cpp b/src/object-store/src/impl/realm_coordinator.cpp index f2aab475..f9e2ae68 100644 --- a/src/object-store/src/impl/realm_coordinator.cpp +++ b/src/object-store/src/impl/realm_coordinator.cpp @@ -22,6 +22,7 @@ #include "impl/external_commit_helper.hpp" #include "impl/transact_log_handler.hpp" #include "impl/weak_realm_notifier.hpp" +#include "object_schema.hpp" #include "object_store.hpp" #include "schema.hpp" diff --git a/src/object-store/src/impl/transact_log_handler.cpp b/src/object-store/src/impl/transact_log_handler.cpp index 0a130ea1..c163fbb9 100644 --- a/src/object-store/src/impl/transact_log_handler.cpp +++ b/src/object-store/src/impl/transact_log_handler.cpp @@ -64,7 +64,7 @@ class TransactLogValidationMixin { REALM_NOINLINE void schema_error() { - throw std::runtime_error("Schema mismatch detected: another process has modified the Realm file's schema in an incompatible way"); + throw std::logic_error("Schema mismatch detected: another process has modified the Realm file's schema in an incompatible way"); } // Throw an exception if the currently modified table already existed before diff --git a/src/object-store/src/list.cpp b/src/object-store/src/list.cpp index 8276eb50..8ed098cc 100644 --- a/src/object-store/src/list.cpp +++ b/src/object-store/src/list.cpp @@ -20,7 +20,9 @@ #include "impl/list_notifier.hpp" #include "impl/realm_coordinator.hpp" +#include "object_store.hpp" #include "results.hpp" +#include "schema.hpp" #include "shared_realm.hpp" #include "util/format.hpp" @@ -37,13 +39,25 @@ List& List::operator=(const List&) = default; List::List(List&&) = default; List& List::operator=(List&&) = default; -List::List(std::shared_ptr r, const ObjectSchema& s, LinkViewRef l) noexcept +List::List(std::shared_ptr r, LinkViewRef l) noexcept : m_realm(std::move(r)) -, m_object_schema(&s) , m_link_view(std::move(l)) { } +const ObjectSchema& List::get_object_schema() const +{ + verify_attached(); + + if (!m_object_schema) { + auto object_type = ObjectStore::object_type_for_table_name(m_link_view->get_target_table().get_name()); + auto it = m_realm->config().schema->find(object_type); + REALM_ASSERT(it != m_realm->config().schema->end()); + m_object_schema = &*it; + } + return *m_object_schema; +} + Query List::get_query() const { verify_attached(); @@ -73,7 +87,7 @@ bool List::is_valid() const void List::verify_attached() const { if (!is_valid()) { - throw InvalidatedException{}; + throw InvalidatedException(); } } @@ -172,13 +186,19 @@ void List::delete_all() Results List::sort(SortOrder order) { verify_attached(); - return Results(m_realm, *m_object_schema, m_link_view, util::none, std::move(order)); + return Results(m_realm, m_link_view, util::none, std::move(order)); } Results List::filter(Query q) { verify_attached(); - return Results(m_realm, *m_object_schema, m_link_view, get_query().and_query(std::move(q))); + return Results(m_realm, m_link_view, get_query().and_query(std::move(q))); +} + +Results List::snapshot() const +{ + verify_attached(); + return Results(m_realm, m_link_view).snapshot(); } // These definitions rely on that LinkViews are interned by core diff --git a/src/object-store/src/list.hpp b/src/object-store/src/list.hpp index 7c412e67..ca75fd54 100644 --- a/src/object-store/src/list.hpp +++ b/src/object-store/src/list.hpp @@ -40,7 +40,7 @@ struct SortOrder; class List { public: List() noexcept; - List(std::shared_ptr r, const ObjectSchema& s, LinkViewRef l) noexcept; + List(std::shared_ptr r, LinkViewRef l) noexcept; ~List(); List(const List&); @@ -50,7 +50,7 @@ public: const std::shared_ptr& get_realm() const { return m_realm; } Query get_query() const; - const ObjectSchema& get_object_schema() const { return *m_object_schema; } + const ObjectSchema& get_object_schema() const; size_t get_origin_row_index() const; bool is_valid() const; @@ -75,6 +75,9 @@ public: Results sort(SortOrder order); Results filter(Query q); + // Return a Results representing a snapshot of this List. + Results snapshot() const; + bool operator==(List const& rgt) const noexcept; NotificationToken add_notification_callback(CollectionChangeCallback cb); @@ -92,8 +95,8 @@ public: // The List object has been invalidated (due to the Realm being invalidated, // or the containing object being deleted) // All non-noexcept functions can throw this - struct InvalidatedException : public std::runtime_error { - InvalidatedException() : std::runtime_error("Access to invalidated List object") {} + struct InvalidatedException : public std::logic_error { + InvalidatedException() : std::logic_error("Access to invalidated List object") {} }; // The input index parameter was out of bounds @@ -105,7 +108,7 @@ public: private: std::shared_ptr m_realm; - const ObjectSchema* m_object_schema; + mutable const ObjectSchema* m_object_schema = nullptr; LinkViewRef m_link_view; _impl::CollectionNotifier::Handle<_impl::CollectionNotifier> m_notifier; diff --git a/src/object-store/src/object_accessor.hpp b/src/object-store/src/object_accessor.hpp index 4df51508..a19608a6 100644 --- a/src/object-store/src/object_accessor.hpp +++ b/src/object-store/src/object_accessor.hpp @@ -123,50 +123,43 @@ namespace realm { // // Deprecated // - static Mixed to_mixed(ContextType ctx, ValueType &val) { throw std::runtime_error("'Any' type is unsupported"); } + static Mixed to_mixed(ContextType, ValueType&) { throw std::logic_error("'Any' type is unsupported"); } }; - class InvalidatedObjectException : public std::runtime_error - { - public: - InvalidatedObjectException(const std::string object_type, const std::string message) : std::runtime_error(message), object_type(object_type) {} + struct InvalidatedObjectException : public std::logic_error { + InvalidatedObjectException(const std::string& object_type, const std::string& message) : + std::logic_error(message), object_type(object_type) {} const std::string object_type; }; - class InvalidPropertyException : public std::runtime_error - { - public: - InvalidPropertyException(const std::string object_type, const std::string property_name, const std::string message) : std::runtime_error(message), object_type(object_type), property_name(property_name) {} + struct InvalidPropertyException : public std::logic_error { + InvalidPropertyException(const std::string& object_type, const std::string& property_name, const std::string& message) : + std::logic_error(message), object_type(object_type), property_name(property_name) {} const std::string object_type; const std::string property_name; }; - class MissingPropertyValueException : public std::runtime_error - { - public: - MissingPropertyValueException(const std::string object_type, const std::string property_name, const std::string message) : std::runtime_error(message), object_type(object_type), property_name(property_name) {} + struct MissingPropertyValueException : public std::logic_error { + MissingPropertyValueException(const std::string& object_type, const std::string& property_name, const std::string& message) : + std::logic_error(message), object_type(object_type), property_name(property_name) {} const std::string object_type; const std::string property_name; }; - class MissingPrimaryKeyException : public std::runtime_error - { - public: - MissingPrimaryKeyException(const std::string object_type, const std::string message) : std::runtime_error(message), object_type(object_type) {} + struct MissingPrimaryKeyException : public std::logic_error { + MissingPrimaryKeyException(const std::string& object_type, const std::string& message) : std::logic_error(message), object_type(object_type) {} const std::string object_type; }; - class ReadOnlyPropertyValueException : public std::runtime_error { - public: - ReadOnlyPropertyValueException(const std::string& object_type, const std::string& property_name, const std::string& message) - : std::runtime_error(message), object_type(object_type), property_name(property_name) {} + struct ReadOnlyPropertyException : public std::logic_error { + ReadOnlyPropertyException(const std::string& object_type, const std::string& property_name, const std::string& message) : + std::logic_error(message), object_type(object_type), property_name(property_name) {} const std::string object_type; const std::string property_name; }; - class MutationOutsideTransactionException : public std::runtime_error { - public: - MutationOutsideTransactionException(std::string message) : std::runtime_error(message) {} + struct MutationOutsideTransactionException : public std::logic_error { + MutationOutsideTransactionException(const std::string& message) : std::logic_error(message) {} }; // @@ -265,9 +258,9 @@ namespace realm { break; } case PropertyType::LinkingObjects: - throw ReadOnlyPropertyValueException(m_object_schema->name, property.name, - util::format("Cannot modify read-only property '%1.%2'", - m_object_schema->name, property.name)); + throw ReadOnlyPropertyException(m_object_schema->name, property.name, + util::format("Cannot modify read-only property '%1.%2'", + m_object_schema->name, property.name)); } } @@ -309,15 +302,14 @@ namespace realm { return Accessor::from_object(ctx, std::move(Object(m_realm, *linkObjectSchema, table->get(m_row.get_link(column))))); } case PropertyType::Array: { - auto arrayObjectSchema = m_realm->config().schema->find(property.object_type); - return Accessor::from_list(ctx, std::move(List(m_realm, *arrayObjectSchema, static_cast(m_row.get_linklist(column))))); + return Accessor::from_list(ctx, std::move(List(m_realm, static_cast(m_row.get_linklist(column))))); } case PropertyType::LinkingObjects: { auto target_object_schema = m_realm->config().schema->find(property.object_type); auto link_property = target_object_schema->property_for_name(property.link_origin_property_name); TableRef table = ObjectStore::table_for_object_type(m_realm->read_group(), target_object_schema->name); auto tv = m_row.get_table()->get_backlink_view(m_row.get_index(), table.get(), link_property->table_column); - Results results(m_realm, *m_object_schema, std::move(tv), {}); + Results results(m_realm, std::move(tv), {}); return Accessor::from_results(ctx, std::move(results)); } } diff --git a/src/object-store/src/object_store.cpp b/src/object-store/src/object_store.cpp index 141b6970..3d80e765 100644 --- a/src/object-store/src/object_store.cpp +++ b/src/object-store/src/object_store.cpp @@ -18,6 +18,7 @@ #include "object_store.hpp" +#include "object_schema.hpp" #include "schema.hpp" #include "util/format.hpp" @@ -123,7 +124,7 @@ StringData ObjectStore::object_type_for_table_name(StringData table_name) { } std::string ObjectStore::table_name_for_object_type(StringData object_type) { - return std::string(c_object_table_prefix) + object_type.data(); + return std::string(c_object_table_prefix) + static_cast(object_type); } TableRef ObjectStore::table_for_object_type(Group *group, StringData object_type) { @@ -507,7 +508,7 @@ bool ObjectStore::is_empty(const Group *group) { InvalidSchemaVersionException::InvalidSchemaVersionException(uint64_t old_version, uint64_t new_version) : m_old_version(old_version), m_new_version(new_version) { - m_what = util::format("Provided schema version %1 is less than last set version %2.", old_version, new_version); + m_what = util::format("Provided schema version %1 is less than last set version %2.", new_version, old_version); } DuplicatePrimaryKeyValueException::DuplicatePrimaryKeyValueException(std::string const& object_type, Property const& property) : @@ -527,16 +528,16 @@ DuplicatePrimaryKeyValueException::DuplicatePrimaryKeyValueException(std::string SchemaValidationException::SchemaValidationException(std::vector const& errors) : m_validation_errors(errors) { - m_what = "Schema validation failed due to the following errors: "; + m_what = "Schema validation failed due to the following errors:"; for (auto const& error : errors) { m_what += std::string("\n- ") + error.what(); } } SchemaMismatchException::SchemaMismatchException(std::vector const& errors) : -m_validation_errors(errors) + m_validation_errors(errors) { - m_what = "Migration is required due to the following errors: "; + m_what = "Migration is required due to the following errors:"; for (auto const& error : errors) { m_what += std::string("\n- ") + error.what(); } @@ -546,7 +547,7 @@ PropertyTypeNotIndexableException::PropertyTypeNotIndexableException(std::string Property const& property) : ObjectSchemaPropertyException(object_type, property) { - m_what = util::format("Can't index property %1.%2: indexing a property of type '%3' is currently not supported", + m_what = util::format("Can't index property %1.%2: indexing a property of type '%3' is currently not supported.", object_type, property.name, string_for_property_type(property.type)); } @@ -572,7 +573,7 @@ InvalidNullabilityException::InvalidNullabilityException(std::string const& obje case PropertyType::Any: case PropertyType::Array: case PropertyType::LinkingObjects: - m_what = util::format("Property '%1' of type '%2' cannoy be nullable", + m_what = util::format("Property '%1' of type '%2' cannot be nullable.", property.name, string_for_property_type(property.type)); break; case PropertyType::Int: @@ -599,17 +600,17 @@ MismatchedPropertiesException::MismatchedPropertiesException(std::string const& ObjectSchemaValidationException(object_type), m_old_property(old_property), m_new_property(new_property) { if (new_property.type != old_property.type) { - m_what = util::format("Property types for '%1' property doe not match. Old type '%2', new type '%3'", + m_what = util::format("Property types for '%1' property do not match. Old type '%2', new type '%3'.", old_property.name, string_for_property_type(old_property.type), string_for_property_type(new_property.type)); } else if (new_property.object_type != old_property.object_type) { - m_what = util::format("Target object type for property '%1' do not match. Old type '%2', new type '%3'", + m_what = util::format("Target object type for property '%1' do not match. Old type '%2', new type '%3'.", old_property.name, old_property.object_type, new_property.object_type); } else if (new_property.is_nullable != old_property.is_nullable) { - m_what = util::format("Nullability for property '%1' has been changed from %2 to %3", + m_what = util::format("Nullability for property '%1' has been changed from %2 to %3.", old_property.name, old_property.is_nullable, new_property.is_nullable); } @@ -645,17 +646,17 @@ InvalidLinkingObjectsPropertyException::InvalidLinkingObjectsPropertyException(T { switch (error_type) { case Type::OriginPropertyDoesNotExist: - m_what = util::format("Property '%1.%2' declared as origin of linking objects property '%3.%4' does not exist", + m_what = util::format("Property '%1.%2' declared as origin of linking objects property '%3.%4' does not exist.", property.object_type, property.link_origin_property_name, object_type, property.name); break; case Type::OriginPropertyIsNotALink: - m_what = util::format("Property '%1.%2' declared as origin of linking objects property '%3.%4' is not a link", + m_what = util::format("Property '%1.%2' declared as origin of linking objects property '%3.%4' is not a link.", property.object_type, property.link_origin_property_name, object_type, property.name); break; case Type::OriginPropertyInvalidLinkTarget: - m_what = util::format("Property '%1.%2' declared as origin of linking objects property '%3.%4' links to a different class", + m_what = util::format("Property '%1.%2' declared as origin of linking objects property '%3.%4' links to a different class.", property.object_type, property.link_origin_property_name, object_type, property.name); break; diff --git a/src/object-store/src/object_store.hpp b/src/object-store/src/object_store.hpp index 3ecfc956..37450c0b 100644 --- a/src/object-store/src/object_store.hpp +++ b/src/object-store/src/object_store.hpp @@ -19,17 +19,20 @@ #ifndef REALM_OBJECT_STORE_HPP #define REALM_OBJECT_STORE_HPP -#include "schema.hpp" #include "property.hpp" #include #include +#include +#include namespace realm { class Group; + class ObjectSchema; class ObjectSchemaValidationException; class Schema; + class StringData; class ObjectStore { public: diff --git a/src/object-store/src/parser/parser.cpp b/src/object-store/src/parser/parser.cpp index dab01c63..6b7c3396 100644 --- a/src/object-store/src/parser/parser.cpp +++ b/src/object-store/src/parser/parser.cpp @@ -201,14 +201,14 @@ template< typename Rule > struct action : nothing< Rule > {}; #ifdef REALM_PARSER_PRINT_TOKENS - #define DEBUG_PRINT_TOKEN(string) std::cout << string << std::endl + #define DEBUG_PRINT_TOKEN(string) do { std::cout << string << std::endl; while (0) #else - #define DEBUG_PRINT_TOKEN(string) + #define DEBUG_PRINT_TOKEN(string) do { static_cast(string); } while (0) #endif template<> struct action< and_op > { - static void apply( const input & in, ParserState & state ) + static void apply(const input&, ParserState& state) { DEBUG_PRINT_TOKEN(""); state.next_type = Predicate::Type::And; @@ -217,7 +217,7 @@ template<> struct action< and_op > template<> struct action< or_op > { - static void apply( const input & in, ParserState & state ) + static void apply(const input&, ParserState & state) { DEBUG_PRINT_TOKEN(""); state.next_type = Predicate::Type::Or; @@ -227,7 +227,7 @@ template<> struct action< or_op > #define EXPRESSION_ACTION(rule, type) \ template<> struct action< rule > { \ - static void apply( const input & in, ParserState & state ) { \ + static void apply(const input& in, ParserState& state) { \ DEBUG_PRINT_TOKEN(in.string()); \ state.add_expression(Expression(type, in.string())); }}; @@ -239,10 +239,10 @@ EXPRESSION_ACTION(true_value, Expression::Type::True) EXPRESSION_ACTION(false_value, Expression::Type::False) EXPRESSION_ACTION(null_value, Expression::Type::Null) EXPRESSION_ACTION(argument_index, Expression::Type::Argument) - + template<> struct action< true_pred > { - static void apply( const input & in, ParserState & state ) + static void apply(const input& in, ParserState & state) { DEBUG_PRINT_TOKEN(in.string()); state.current_group()->cpnd.sub_predicates.emplace_back(Predicate::Type::True); @@ -251,7 +251,7 @@ template<> struct action< true_pred > template<> struct action< false_pred > { - static void apply( const input & in, ParserState & state ) + static void apply(const input& in, ParserState & state) { DEBUG_PRINT_TOKEN(in.string()); state.current_group()->cpnd.sub_predicates.emplace_back(Predicate::Type::False); @@ -260,7 +260,7 @@ template<> struct action< false_pred > #define OPERATOR_ACTION(rule, oper) \ template<> struct action< rule > { \ - static void apply( const input & in, ParserState & state ) { \ + static void apply(const input& in, ParserState& state) { \ DEBUG_PRINT_TOKEN(in.string()); \ state.last_predicate()->cmpr.op = oper; }}; @@ -273,19 +273,19 @@ OPERATOR_ACTION(lt, Predicate::Operator::LessThan) OPERATOR_ACTION(begins, Predicate::Operator::BeginsWith) OPERATOR_ACTION(ends, Predicate::Operator::EndsWith) OPERATOR_ACTION(contains, Predicate::Operator::Contains) - + template<> struct action< case_insensitive > { - static void apply( const input & in, ParserState & state ) + static void apply(const input& in, ParserState & state) { DEBUG_PRINT_TOKEN(in.string()); state.last_predicate()->cmpr.option = Predicate::OperatorOption::CaseInsensitive; } }; - + template<> struct action< one< '(' > > { - static void apply( const input & in, ParserState & state ) + static void apply(const input&, ParserState & state) { DEBUG_PRINT_TOKEN(""); state.add_predicate_to_current_group(Predicate::Type::And); @@ -295,7 +295,7 @@ template<> struct action< one< '(' > > template<> struct action< group_pred > { - static void apply( const input & in, ParserState & state ) + static void apply(const input&, ParserState & state) { DEBUG_PRINT_TOKEN(""); state.group_stack.pop_back(); @@ -304,7 +304,7 @@ template<> struct action< group_pred > template<> struct action< not_pre > { - static void apply( const input & in, ParserState & state ) + static void apply(const input&, ParserState & state) { DEBUG_PRINT_TOKEN(""); state.negate_next = true; @@ -317,9 +317,9 @@ struct error_message_control : pegtl::normal< Rule > static const std::string error_message; template< typename Input, typename ... States > - static void raise( const Input & in, States && ... ) + static void raise(const Input& in, States&&...) { - throw pegtl::parse_error( error_message, in ); + throw pegtl::parse_error(error_message, in); } }; @@ -351,5 +351,3 @@ void analyze_grammar() } }} - - diff --git a/src/object-store/src/parser/query_builder.cpp b/src/object-store/src/parser/query_builder.cpp index 19d4883c..5618c6f4 100644 --- a/src/object-store/src/parser/query_builder.cpp +++ b/src/object-store/src/parser/query_builder.cpp @@ -21,16 +21,18 @@ #include "object_store.hpp" #include "schema.hpp" +#include "util/compiler.hpp" #include "util/format.hpp" #include #include #include -namespace realm { -namespace query_builder { +using namespace realm; using namespace parser; +using namespace query_builder; +namespace { template T stot(std::string const& s) { std::istringstream iss(s); @@ -45,7 +47,7 @@ T stot(std::string const& s) { // check a precondition and throw an exception if it is not met // this should be used iff the condition being false indicates a bug in the caller // of the function checking its preconditions -#define precondition(condition, message) if (!__builtin_expect(condition, 1)) { throw std::runtime_error(message); } +#define precondition(condition, message) if (!__builtin_expect(condition, 1)) { throw std::logic_error(message); } // FIXME: TrueExpression and FalseExpression should be supported by core in some way struct TrueExpression : realm::Expression { @@ -148,7 +150,7 @@ void add_numeric_constraint_to_query(Query& query, query.and_query(lhs != rhs); break; default: - throw std::runtime_error("Unsupported operator for numeric queries."); + throw std::logic_error("Unsupported operator for numeric queries."); } } @@ -162,7 +164,7 @@ void add_bool_constraint_to_query(Query &query, Predicate::Operator operatorType query.and_query(lhs != rhs); break; default: - throw std::runtime_error("Unsupported operator for numeric queries."); + throw std::logic_error("Unsupported operator for numeric queries."); } } @@ -188,7 +190,7 @@ void add_string_constraint_to_query(Query &query, query.and_query(column.not_equal(value, case_sensitive)); break; default: - throw std::runtime_error("Unsupported operator for string queries."); + throw std::logic_error("Unsupported operator for string queries."); } } @@ -205,7 +207,7 @@ void add_string_constraint_to_query(realm::Query &query, query.and_query(column.not_equal(value, case_sensitive)); break; default: - throw std::runtime_error("Substring comparison not supported for keypath substrings."); + throw std::logic_error("Substring comparison not supported for keypath substrings."); } } @@ -230,7 +232,7 @@ void add_binary_constraint_to_query(Query &query, query.not_equal(column.m_column, BinaryData(value)); break; default: - throw std::runtime_error("Unsupported operator for binary queries."); + throw std::logic_error("Unsupported operator for binary queries."); } } @@ -246,7 +248,7 @@ void add_binary_constraint_to_query(realm::Query &query, query.not_equal(column.m_column, BinaryData(value)); break; default: - throw std::runtime_error("Substring comparison not supported for keypath substrings."); + throw std::logic_error("Substring comparison not supported for keypath substrings."); } } @@ -258,22 +260,23 @@ void add_link_constraint_to_query(realm::Query &query, switch (op) { case Predicate::Operator::NotEqual: query.Not(); + REALM_FALLTHROUGH; case Predicate::Operator::Equal: { size_t col = prop_expr.prop->table_column; query.links_to(col, query.get_table()->get_link_target(col)->get(row_index)); break; } default: - throw std::runtime_error("Only 'equal' and 'not equal' operators supported for object comparison."); + throw std::logic_error("Only 'equal' and 'not equal' operators supported for object comparison."); } } -auto link_argument(const PropertyExpression &propExpr, const parser::Expression &argExpr, Arguments &args) +auto link_argument(const PropertyExpression&, const parser::Expression &argExpr, Arguments &args) { return args.object_index_for_argument(stot(argExpr.s)); } -auto link_argument(const parser::Expression &argExpr, const PropertyExpression &propExpr, Arguments &args) +auto link_argument(const parser::Expression &argExpr, const PropertyExpression&, Arguments &args) { return args.object_index_for_argument(stot(argExpr.s)); } @@ -281,7 +284,7 @@ auto link_argument(const parser::Expression &argExpr, const PropertyExpression & template struct ColumnGetter { - static Columns convert(TableGetter&& table, const PropertyExpression & expr, Arguments &args) + static Columns convert(TableGetter&& table, const PropertyExpression& expr, Arguments&) { return table()->template column(expr.prop->table_column); } @@ -295,7 +298,7 @@ struct ValueGetter { static Timestamp convert(TableGetter&&, const parser::Expression & value, Arguments &args) { if (value.type != parser::Expression::Type::Argument) { - throw std::runtime_error("You must pass in a date argument to compare"); + throw std::logic_error("You must pass in a date argument to compare"); } return args.timestamp_for_argument(stot(value.s)); } @@ -309,7 +312,7 @@ struct ValueGetter { return args.bool_for_argument(stot(value.s)); } if (value.type != parser::Expression::Type::True && value.type != parser::Expression::Type::False) { - throw std::runtime_error("Attempting to compare bool property to a non-bool value"); + throw std::logic_error("Attempting to compare bool property to a non-bool value"); } return value.type == parser::Expression::Type::True; } @@ -356,7 +359,7 @@ struct ValueGetter { return args.string_for_argument(stot(value.s)); } if (value.type != parser::Expression::Type::String) { - throw std::runtime_error("Attempting to compare String property to a non-String value"); + throw std::logic_error("Attempting to compare String property to a non-String value"); } return value.s; } @@ -369,7 +372,7 @@ struct ValueGetter { if (value.type == parser::Expression::Type::Argument) { return args.binary_for_argument(stot(value.s)); } - throw std::runtime_error("Binary properties must be compared against a binary argument."); + throw std::logic_error("Binary properties must be compared against a binary argument."); } }; @@ -382,7 +385,7 @@ auto value_of_type_for_query(TableGetter&& tables, Value&& value, Arguments &arg } template -void do_add_comparison_to_query(Query &query, const Schema &schema, const ObjectSchema &object_schema, Predicate::Comparison cmp, +void do_add_comparison_to_query(Query &query, Predicate::Comparison cmp, const PropertyExpression &expr, A &lhs, B &rhs, Arguments &args) { auto type = expr.prop->type; @@ -420,12 +423,12 @@ void do_add_comparison_to_query(Query &query, const Schema &schema, const Object add_link_constraint_to_query(query, cmp.op, expr, link_argument(lhs, rhs, args)); break; default: - throw std::runtime_error(util::format("Object type '%1' not supported", string_for_property_type(type))); + throw std::logic_error(util::format("Object type '%1' not supported", string_for_property_type(type))); } } template -void do_add_null_comparison_to_query(Query &query, Predicate::Operator op, const PropertyExpression &expr, Arguments &args) +void do_add_null_comparison_to_query(Query &query, Predicate::Operator op, const PropertyExpression &expr) { Columns column = expr.table_getter()->template column(expr.prop->table_column); switch (op) { @@ -436,12 +439,12 @@ void do_add_null_comparison_to_query(Query &query, Predicate::Operator op, const query.and_query(column == realm::null()); break; default: - throw std::runtime_error("Only 'equal' and 'not equal' operators supported when comparing against 'null'."); + throw std::logic_error("Only 'equal' and 'not equal' operators supported when comparing against 'null'."); } } template<> -void do_add_null_comparison_to_query(Query &query, Predicate::Operator op, const PropertyExpression &expr, Arguments &args) +void do_add_null_comparison_to_query(Query &query, Predicate::Operator op, const PropertyExpression &expr) { precondition(expr.indexes.empty(), "KeyPath queries not supported for data comparisons."); Columns column = expr.table_getter()->template column(expr.prop->table_column); @@ -453,60 +456,58 @@ void do_add_null_comparison_to_query(Query &query, Predicate::Operator o query.equal(expr.prop->table_column, realm::null()); break; default: - throw std::runtime_error("Only 'equal' and 'not equal' operators supported when comparing against 'null'."); + throw std::logic_error("Only 'equal' and 'not equal' operators supported when comparing against 'null'."); } } template<> -void do_add_null_comparison_to_query(Query &query, Predicate::Operator op, const PropertyExpression &expr, Arguments &args) +void do_add_null_comparison_to_query(Query &query, Predicate::Operator op, const PropertyExpression &expr) { precondition(expr.indexes.empty(), "KeyPath queries not supported for object comparisons."); switch (op) { case Predicate::Operator::NotEqual: - // for not equal we negate the query and then fallthrough query.Not(); + REALM_FALLTHROUGH; case Predicate::Operator::Equal: query.and_query(query.get_table()->column(expr.prop->table_column).is_null()); break; default: - throw std::runtime_error("Only 'equal' and 'not equal' operators supported for object comparison."); + throw std::logic_error("Only 'equal' and 'not equal' operators supported for object comparison."); } } -void do_add_null_comparison_to_query(Query &query, const Schema &schema, const ObjectSchema &object_schema, Predicate::Comparison cmp, - const PropertyExpression &expr, Arguments &args) +void do_add_null_comparison_to_query(Query &query, Predicate::Comparison cmp, const PropertyExpression &expr) { auto type = expr.prop->type; switch (type) { case realm::PropertyType::Bool: - do_add_null_comparison_to_query(query, cmp.op, expr, args); + do_add_null_comparison_to_query(query, cmp.op, expr); break; case realm::PropertyType::Date: - do_add_null_comparison_to_query(query, cmp.op, expr, args); + do_add_null_comparison_to_query(query, cmp.op, expr); break; case realm::PropertyType::Double: - do_add_null_comparison_to_query(query, cmp.op, expr, args); + do_add_null_comparison_to_query(query, cmp.op, expr); break; case realm::PropertyType::Float: - do_add_null_comparison_to_query(query, cmp.op, expr, args); + do_add_null_comparison_to_query(query, cmp.op, expr); break; case realm::PropertyType::Int: - do_add_null_comparison_to_query(query, cmp.op, expr, args); + do_add_null_comparison_to_query(query, cmp.op, expr); break; case realm::PropertyType::String: - do_add_null_comparison_to_query(query, cmp.op, expr, args); + do_add_null_comparison_to_query(query, cmp.op, expr); break; case realm::PropertyType::Data: - do_add_null_comparison_to_query(query, cmp.op, expr, args); + do_add_null_comparison_to_query(query, cmp.op, expr); break; case realm::PropertyType::Object: - do_add_null_comparison_to_query(query, cmp.op, expr, args); + do_add_null_comparison_to_query(query, cmp.op, expr); break; case realm::PropertyType::Array: - throw std::runtime_error("Comparing Lists to 'null' is not supported"); - default: { - throw std::runtime_error(std::string("Object type ") + string_for_property_type(type) + " not supported"); - } + throw std::logic_error("Comparing Lists to 'null' is not supported"); + default: + throw std::logic_error(util::format("Object type '%1' not supported", string_for_property_type(type))); } } @@ -528,23 +529,23 @@ void add_comparison_to_query(Query &query, const Predicate &pred, Arguments &arg if (t0 == parser::Expression::Type::KeyPath && t1 != parser::Expression::Type::KeyPath) { PropertyExpression expr(query, schema, object_schema, cmpr.expr[0].s); if (expression_is_null(cmpr.expr[1], args)) { - do_add_null_comparison_to_query(query, schema, *object_schema, cmpr, expr, args); + do_add_null_comparison_to_query(query, cmpr, expr); } else { - do_add_comparison_to_query(query, schema, *object_schema, cmpr, expr, expr, cmpr.expr[1], args); + do_add_comparison_to_query(query, cmpr, expr, expr, cmpr.expr[1], args); } } else if (t0 != parser::Expression::Type::KeyPath && t1 == parser::Expression::Type::KeyPath) { PropertyExpression expr(query, schema, object_schema, cmpr.expr[1].s); if (expression_is_null(cmpr.expr[0], args)) { - do_add_null_comparison_to_query(query, schema, *object_schema, cmpr, expr, args); + do_add_null_comparison_to_query(query, cmpr, expr); } else { - do_add_comparison_to_query(query, schema, *object_schema, cmpr, expr, cmpr.expr[0], expr, args); + do_add_comparison_to_query(query, cmpr, expr, cmpr.expr[0], expr, args); } } else { - throw std::runtime_error("Predicate expressions must compare a keypath and another keypath or a constant value"); + throw std::logic_error("Predicate expressions must compare a keypath and another keypath or a constant value"); } } @@ -591,9 +592,13 @@ void update_query_with_predicate(Query &query, const Predicate &pred, Arguments break; default: - throw std::runtime_error("Invalid predicate type"); + throw std::logic_error("Invalid predicate type"); } } +} // anonymous namespace + +namespace realm { +namespace query_builder { void apply_predicate(Query &query, const Predicate &predicate, Arguments &arguments, const Schema &schema, const std::string &objectType) { @@ -604,4 +609,5 @@ void apply_predicate(Query &query, const Predicate &predicate, Arguments &argume precondition(validateMessage.empty(), validateMessage.c_str()); } -}} +} +} diff --git a/src/object-store/src/property.hpp b/src/object-store/src/property.hpp index 7c26c750..f66adff2 100644 --- a/src/object-store/src/property.hpp +++ b/src/object-store/src/property.hpp @@ -22,7 +22,7 @@ #include namespace realm { - enum class PropertyType { + enum class PropertyType : unsigned char { Int = 0, Bool = 1, Float = 9, @@ -94,6 +94,8 @@ namespace realm { return "object"; case PropertyType::Array: return "array"; + case PropertyType::LinkingObjects: + return "linking objects"; #if __GNUC__ default: __builtin_unreachable(); diff --git a/src/object-store/src/results.cpp b/src/object-store/src/results.cpp index 5abcb4ab..35fb07f4 100644 --- a/src/object-store/src/results.cpp +++ b/src/object-store/src/results.cpp @@ -20,31 +20,21 @@ #include "impl/realm_coordinator.hpp" #include "impl/results_notifier.hpp" +#include "object_schema.hpp" #include "object_store.hpp" +#include "schema.hpp" #include "util/format.hpp" +#include "util/compiler.hpp" #include using namespace realm; -#ifdef __has_cpp_attribute -#define REALM_HAS_CCP_ATTRIBUTE(attr) __has_cpp_attribute(attr) -#else -#define REALM_HAS_CCP_ATTRIBUTE(attr) 0 -#endif - -#if REALM_HAS_CCP_ATTRIBUTE(clang::fallthrough) -#define REALM_FALLTHROUGH [[clang::fallthrough]] -#else -#define REALM_FALLTHROUGH -#endif - Results::Results() = default; Results::~Results() = default; -Results::Results(SharedRealm r, const ObjectSchema &o, Query q, SortOrder s) +Results::Results(SharedRealm r, Query q, SortOrder s) : m_realm(std::move(r)) -, m_object_schema(&o) , m_query(std::move(q)) , m_table(m_query.get_table().get()) , m_sort(std::move(s)) @@ -53,17 +43,15 @@ Results::Results(SharedRealm r, const ObjectSchema &o, Query q, SortOrder s) REALM_ASSERT(m_sort.column_indices.size() == m_sort.ascending.size()); } -Results::Results(SharedRealm r, const ObjectSchema &o, Table& table) +Results::Results(SharedRealm r, Table& table) : m_realm(std::move(r)) -, m_object_schema(&o) , m_table(&table) , m_mode(Mode::Table) { } -Results::Results(SharedRealm r, const ObjectSchema& o, LinkViewRef lv, util::Optional q, SortOrder s) +Results::Results(SharedRealm r, LinkViewRef lv, util::Optional q, SortOrder s) : m_realm(std::move(r)) -, m_object_schema(&o) , m_link_view(lv) , m_table(&lv->get_target_table()) , m_sort(std::move(s)) @@ -76,9 +64,8 @@ Results::Results(SharedRealm r, const ObjectSchema& o, LinkViewRef lv, util::Opt } } -Results::Results(SharedRealm r, const ObjectSchema& o, TableView tv, SortOrder s) +Results::Results(SharedRealm r, TableView tv, SortOrder s) : m_realm(std::move(r)) -, m_object_schema(&o) , m_table_view(std::move(tv)) , m_table(&m_table_view.get_parent()) , m_sort(std::move(s)) @@ -108,9 +95,9 @@ Results::Results(Results&& other) , m_link_view(std::move(other.m_link_view)) , m_table(other.m_table) , m_sort(std::move(other.m_sort)) -, m_live(other.m_live) , m_notifier(std::move(other.m_notifier)) , m_mode(other.m_mode) +, m_update_policy(other.m_update_policy) , m_has_used_table_view(other.m_has_used_table_view) , m_wants_background_updates(other.m_wants_background_updates) { @@ -130,18 +117,16 @@ bool Results::is_valid() const { if (m_realm) m_realm->verify_thread(); + if (m_table && !m_table->is_attached()) return false; - if (m_mode == Mode::TableView && (!m_table_view.is_attached() || (m_live && m_table_view.depends_on_deleted_object()))) - return false; - if (m_mode == Mode::LinkView && !m_link_view->is_attached()) - return false; - + return true; } void Results::validate_read() const { + // is_valid ensures that we're on the correct thread. if (!is_valid()) throw InvalidatedException(); } @@ -153,19 +138,6 @@ void Results::validate_write() const throw InvalidTransactionException("Must be in a write transaction"); } -void Results::set_live(bool live) -{ - validate_read(); - - if (!live && (m_mode == Mode::Table || m_mode == Mode::LinkView)) { - m_query = get_query(); - m_mode = Mode::Query; - } - - update_tableview(); - m_live = live; -} - size_t Results::size() { validate_read(); @@ -183,9 +155,28 @@ size_t Results::size() REALM_UNREACHABLE(); } +const ObjectSchema& Results::get_object_schema() const +{ + validate_read(); + + if (!m_object_schema) { + REALM_ASSERT(m_realm); + auto it = m_realm->config().schema->find(get_object_type()); + REALM_ASSERT(it != m_realm->config().schema->end()); + m_object_schema = &*it; + } + + return *m_object_schema; +} + + StringData Results::get_object_type() const noexcept { - return get_object_schema().name; + if (!m_table) { + return StringData(); + } + + return ObjectStore::object_type_for_table_name(m_table->get_name()); } RowExpr Results::get(size_t row_ndx) @@ -199,8 +190,8 @@ RowExpr Results::get(size_t row_ndx) break; case Mode::LinkView: if (update_linkview()) { - if (row_ndx < m_link_view->size()) - return m_link_view->get(row_ndx); + if (row_ndx < m_link_view->size()) + return m_link_view->get(row_ndx); break; } REALM_FALLTHROUGH; @@ -209,8 +200,7 @@ RowExpr Results::get(size_t row_ndx) update_tableview(); if (row_ndx >= m_table_view.size()) break; - // FIXME: If clear() was called on the underlying Table, then is_row_attached(row_ndx) will still return true (core issue #1837). - if (!m_live && (m_table_view.get_parent().is_empty() || !m_table_view.is_row_attached(row_ndx))) + if (m_update_policy == UpdatePolicy::Never && !m_table_view.is_row_attached(row_ndx)) return {}; return m_table_view.get(row_ndx); } @@ -260,6 +250,8 @@ util::Optional Results::last() bool Results::update_linkview() { + REALM_ASSERT(m_update_policy == UpdatePolicy::Auto); + if (m_sort) { m_query = get_query(); m_mode = Mode::Query; @@ -269,9 +261,13 @@ bool Results::update_linkview() return true; } -void Results::update_tableview() +void Results::update_tableview(bool wants_notifications) { - validate_read(); + if (m_update_policy == UpdatePolicy::Never) { + REALM_ASSERT(m_mode == Mode::TableView); + return; + } + switch (m_mode) { case Mode::Empty: case Mode::Table: @@ -286,10 +282,7 @@ void Results::update_tableview() m_mode = Mode::TableView; break; case Mode::TableView: - if (!m_live) { - return; - } - if (!m_notifier && !m_realm->is_in_transaction() && m_realm->can_deliver_notifications()) { + if (wants_notifications && !m_notifier && !m_realm->is_in_transaction() && m_realm->can_deliver_notifications()) { m_notifier = std::make_shared<_impl::ResultsNotifier>(*this); _impl::RealmCoordinator::register_notifier(m_notifier); } @@ -431,13 +424,16 @@ void Results::clear() validate_write(); update_tableview(); - if (m_live) { - m_table_view.clear(RemoveMode::unordered); - } - else { - // Copy the TableView because a non-live Results shouldn't have let its size() change. - TableView table_view_copy = m_table_view; - table_view_copy.clear(RemoveMode::unordered); + switch (m_update_policy) { + case UpdatePolicy::Auto: + m_table_view.clear(RemoveMode::unordered); + break; + case UpdatePolicy::Never: { + // Copy the TableView because a frozen Results shouldn't let its size() change. + TableView copy(m_table_view); + copy.clear(RemoveMode::unordered); + break; + } } break; case Mode::LinkView: @@ -464,7 +460,9 @@ Query Results::get_query() const // The TableView has no associated query so create one with no conditions that is restricted // to the rows in the TableView. - m_table_view.sync_if_needed(); + if (m_update_policy == UpdatePolicy::Auto) { + m_table_view.sync_if_needed(); + } return Query(*m_table, std::unique_ptr(new TableView(m_table_view))); } case Mode::LinkView: @@ -498,12 +496,43 @@ TableView Results::get_tableview() Results Results::sort(realm::SortOrder&& sort) const { REALM_ASSERT(sort.column_indices.size() == sort.ascending.size()); - return Results(m_realm, *m_object_schema, get_query(), std::move(sort)); + return Results(m_realm, get_query(), std::move(sort)); } Results Results::filter(Query&& q) const { - return Results(m_realm, *m_object_schema, get_query().and_query(std::move(q)), m_sort); + return Results(m_realm, get_query().and_query(std::move(q)), m_sort); +} + +Results Results::snapshot() const & +{ + validate_read(); + + return Results(*this).snapshot(); +} + +Results Results::snapshot() && +{ + validate_read(); + + switch (m_mode) { + case Mode::Empty: + return Results(); + + case Mode::Table: + case Mode::LinkView: + m_query = get_query(); + m_mode = Mode::Query; + + REALM_FALLTHROUGH; + case Mode::Query: + case Mode::TableView: + update_tableview(false); + m_notifier.reset(); + m_update_policy = UpdatePolicy::Never; + return std::move(*this); + } + REALM_UNREACHABLE(); } void Results::prepare_async() @@ -514,6 +543,9 @@ void Results::prepare_async() if (m_realm->is_in_transaction()) { throw InvalidTransactionException("Cannot create asynchronous query while in a write transaction"); } + if (m_update_policy == UpdatePolicy::Never) { + throw std::logic_error("Cannot create asynchronous query for snapshotted Results."); + } if (!m_notifier) { m_wants_background_updates = true; @@ -553,6 +585,7 @@ bool Results::is_in_table_order() const void Results::Internal::set_table_view(Results& results, realm::TableView &&tv) { + REALM_ASSERT(results.m_update_policy != UpdatePolicy::Never); // If the previous TableView was never actually used, then stop generating // new ones until the user actually uses the Results object again if (results.m_mode == Mode::TableView) { @@ -571,7 +604,7 @@ Results::OutOfBoundsIndexException::OutOfBoundsIndexException(size_t r, size_t c , requested(r), valid_count(c) {} Results::UnsupportedColumnTypeException::UnsupportedColumnTypeException(size_t column, const Table* table, const char* operation) -: std::runtime_error(util::format("Cannot %1 property '%2': operation not supported for '%3' properties", +: std::logic_error(util::format("Cannot %1 property '%2': operation not supported for '%3' properties", operation, table->get_column_name(column), string_for_property_type(static_cast(table->get_column_type(column))))) , column_index(column) diff --git a/src/object-store/src/results.hpp b/src/object-store/src/results.hpp index ec655a70..e91e70eb 100644 --- a/src/object-store/src/results.hpp +++ b/src/object-store/src/results.hpp @@ -49,10 +49,10 @@ public: // or a wrapper around a query and a sort order which creates and updates // the tableview as needed Results(); - Results(SharedRealm r, const ObjectSchema& o, Table& table); - Results(SharedRealm r, const ObjectSchema& o, Query q, SortOrder s = {}); - Results(SharedRealm r, const ObjectSchema& o, TableView tv, SortOrder s); - Results(SharedRealm r, const ObjectSchema& o, LinkViewRef lv, util::Optional q = {}, SortOrder s = {}); + Results(SharedRealm r, Table& table); + Results(SharedRealm r, Query q, SortOrder s = {}); + Results(SharedRealm r, TableView tv, SortOrder s); + Results(SharedRealm r, LinkViewRef lv, util::Optional q = {}, SortOrder s = {}); ~Results(); // Results is copyable and moveable @@ -65,7 +65,7 @@ public: SharedRealm get_realm() const { return m_realm; } // Object schema describing the vendored object type - const ObjectSchema &get_object_schema() const { return *m_object_schema; } + const ObjectSchema &get_object_schema() const; // Get a query which will match the same rows as is contained in this Results // Returned query will not be valid if the current mode is Empty @@ -83,9 +83,6 @@ public: // Get the LinkView this Results is derived from, if any LinkViewRef get_linkview() const { return m_link_view; } - // Set whether the TableView should sync if needed before accessing results - void set_live(bool live); - // Get the size of this results // Can be either O(1) or O(N) depending on the state of things size_t size(); @@ -114,6 +111,10 @@ public: Results filter(Query&& q) const; Results sort(SortOrder&& sort) const; + // Return a snapshot of this Results that never updates to reflect changes in the underlying data. + Results snapshot() const &; + Results snapshot() &&; + // Get the min/max/average/sum of the given column // All but sum() returns none when there are zero matching rows // sum() returns 0, except for when it returns none @@ -128,17 +129,20 @@ public: Empty, // Backed by nothing (for missing tables) Table, // Backed directly by a Table Query, // Backed by a query that has not yet been turned into a TableView - LinkView, // Backed directly by a LinkView - TableView // Backed by a TableView created from a Query + LinkView, // Backed directly by a LinkView + TableView, // Backed by a TableView created from a Query }; // Get the currrent mode of the Results // Ideally this would not be public but it's needed for some KVO stuff Mode get_mode() const { return m_mode; } + // Is this Results associated with a Realm that has not been invalidated? + bool is_valid() const; + // The Results object has been invalidated (due to the Realm being invalidated) // All non-noexcept functions can throw this - struct InvalidatedException : public std::runtime_error { - InvalidatedException() : std::runtime_error("Access to invalidated Results objects") {} + struct InvalidatedException : public std::logic_error { + InvalidatedException() : std::logic_error("Access to invalidated Results objects") {} }; // The input index parameter was out of bounds @@ -149,20 +153,20 @@ public: }; // The input Row object is not attached - struct DetatchedAccessorException : public std::runtime_error { - DetatchedAccessorException() : std::runtime_error("Atempting to access an invalid object") {} + struct DetatchedAccessorException : public std::logic_error { + DetatchedAccessorException() : std::logic_error("Atempting to access an invalid object") {} }; // The input Row object belongs to a different table - struct IncorrectTableException : public std::runtime_error { - IncorrectTableException(StringData e, StringData a, const std::string &error) - : std::runtime_error(error), expected(e), actual(a) {} + struct IncorrectTableException : public std::logic_error { + IncorrectTableException(StringData e, StringData a, const std::string &error) : + std::logic_error(error), expected(e), actual(a) {} const StringData expected; const StringData actual; }; // The requested aggregate operation is not supported for the column type - struct UnsupportedColumnTypeException : public std::runtime_error { + struct UnsupportedColumnTypeException : public std::logic_error { size_t column_index; StringData column_name; DataType column_type; @@ -187,27 +191,29 @@ public: friend class _impl::ResultsNotifier; static void set_table_view(Results& results, TableView&& tv); }; - - // Returns if this Results class is still valid - bool is_valid() const; private: + enum class UpdatePolicy { + Auto, // Update automatically to reflect changes in the underlying data. + Never, // Never update. + }; + SharedRealm m_realm; - const ObjectSchema *m_object_schema; + mutable const ObjectSchema *m_object_schema = nullptr; Query m_query; TableView m_table_view; LinkViewRef m_link_view; Table* m_table = nullptr; SortOrder m_sort; - bool m_live = true; _impl::CollectionNotifier::Handle<_impl::ResultsNotifier> m_notifier; Mode m_mode = Mode::Empty; + UpdatePolicy m_update_policy = UpdatePolicy::Auto; bool m_has_used_table_view = false; bool m_wants_background_updates = true; - void update_tableview(); + void update_tableview(bool wants_notifications = true); bool update_linkview(); void validate_read() const; diff --git a/src/object-store/src/schema.cpp b/src/object-store/src/schema.cpp index 79451ec7..ed02a902 100644 --- a/src/object-store/src/schema.cpp +++ b/src/object-store/src/schema.cpp @@ -18,8 +18,8 @@ #include "schema.hpp" +#include "object_schema.hpp" #include "object_store.hpp" -#include "property.hpp" #include @@ -118,10 +118,8 @@ void Schema::validate() const } // check indexable - if (prop.is_indexed) { - if (!prop.is_indexable()) { - exceptions.emplace_back(PropertyTypeNotIndexableException(object.name, prop)); - } + if (prop.is_indexed && !prop.is_indexable()) { + exceptions.emplace_back(PropertyTypeNotIndexableException(object.name, prop)); } } } diff --git a/src/object-store/src/schema.hpp b/src/object-store/src/schema.hpp index 3a4e026f..30ee153b 100644 --- a/src/object-store/src/schema.hpp +++ b/src/object-store/src/schema.hpp @@ -19,12 +19,12 @@ #ifndef REALM_SCHEMA_HPP #define REALM_SCHEMA_HPP -#include "object_schema.hpp" - #include #include namespace realm { +class ObjectSchema; + class Schema : private std::vector { private: using base = std::vector; diff --git a/src/object-store/src/shared_realm.cpp b/src/object-store/src/shared_realm.cpp index af6ac3af..228a92a6 100644 --- a/src/object-store/src/shared_realm.cpp +++ b/src/object-store/src/shared_realm.cpp @@ -21,6 +21,7 @@ #include "binding_context.hpp" #include "impl/realm_coordinator.hpp" #include "impl/transact_log_handler.hpp" +#include "object_schema.hpp" #include "object_store.hpp" #include "schema.hpp" #include "util/format.hpp" @@ -82,12 +83,12 @@ REALM_NOINLINE static void translate_file_exception(StringData path, bool read_o } catch (util::File::Exists const& ex) { throw RealmFileException(RealmFileException::Kind::Exists, ex.get_path(), - util::format("File at path '%1' already exists", ex.get_path()), + util::format("File at path '%1' already exists.", ex.get_path()), ex.what()); } catch (util::File::NotFound const& ex) { throw RealmFileException(RealmFileException::Kind::NotFound, ex.get_path(), - util::format("Directory at path '%1' does not exists", ex.get_path()), ex.what()); + util::format("Directory at path '%1' does not exist.", ex.get_path()), ex.what()); } catch (util::File::AccessError const& ex) { // Errors for `open()` include the path, but other errors don't. We @@ -100,7 +101,7 @@ REALM_NOINLINE static void translate_file_exception(StringData path, bool read_o underlying.replace(pos - 1, ex.get_path().size() + 2, ""); } throw RealmFileException(RealmFileException::Kind::AccessError, ex.get_path(), - util::format("Unable to open a realm at path '%1': %2", ex.get_path(), underlying), ex.what()); + util::format("Unable to open a realm at path '%1': %2.", ex.get_path(), underlying), ex.what()); } catch (IncompatibleLockFile const& ex) { throw RealmFileException(RealmFileException::Kind::IncompatibleLockFile, path, @@ -314,7 +315,7 @@ void Realm::verify_thread() const void Realm::verify_in_write() const { if (!is_in_transaction()) { - throw InvalidTransactionException("Cannot modify persisted objects outside of a write transaction."); + throw InvalidTransactionException("Cannot modify managed objects outside of a write transaction."); } } @@ -510,4 +511,4 @@ util::Optional Realm::file_format_upgraded_from_version() const } MismatchedConfigException::MismatchedConfigException(StringData message, StringData path) -: std::runtime_error(util::format(message.data(), path)) { } +: std::logic_error(util::format(message.data(), path)) { } diff --git a/src/object-store/src/shared_realm.hpp b/src/object-store/src/shared_realm.hpp index e6e0b5a7..562ffc69 100644 --- a/src/object-store/src/shared_realm.hpp +++ b/src/object-store/src/shared_realm.hpp @@ -228,19 +228,19 @@ namespace realm { std::string m_underlying; }; - class MismatchedConfigException : public std::runtime_error { + class MismatchedConfigException : public std::logic_error { public: MismatchedConfigException(StringData message, StringData path); }; - class InvalidTransactionException : public std::runtime_error { + class InvalidTransactionException : public std::logic_error { public: - InvalidTransactionException(std::string message) : std::runtime_error(move(message)) {} + InvalidTransactionException(std::string message) : std::logic_error(move(message)) {} }; - class IncorrectThreadException : public std::runtime_error { + class IncorrectThreadException : public std::logic_error { public: - IncorrectThreadException() : std::runtime_error("Realm accessed from incorrect thread.") {} + IncorrectThreadException() : std::logic_error("Realm accessed from incorrect thread.") {} }; class UninitializedRealmException : public std::runtime_error { @@ -248,9 +248,9 @@ namespace realm { UninitializedRealmException(std::string message) : std::runtime_error(move(message)) {} }; - class InvalidEncryptionKeyException : public std::runtime_error { + class InvalidEncryptionKeyException : public std::logic_error { public: - InvalidEncryptionKeyException() : std::runtime_error("Encryption key must be 64 bytes.") {} + InvalidEncryptionKeyException() : std::logic_error("Encryption key must be 64 bytes.") {} }; } diff --git a/src/object-store/src/util/compiler.hpp b/src/object-store/src/util/compiler.hpp new file mode 100644 index 00000000..ee9a0d01 --- /dev/null +++ b/src/object-store/src/util/compiler.hpp @@ -0,0 +1,34 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef REALM_UTIL_COMPILER_HPP +#define REALM_UTIL_COMPILER_HPP + +#ifdef __has_cpp_attribute +#define REALM_HAS_CPP_ATTRIBUTE(attr) __has_cpp_attribute(attr) +#else +#define REALM_HAS_CPP_ATTRIBUTE(attr) 0 +#endif + +#if REALM_HAS_CPP_ATTRIBUTE(clang::fallthrough) +#define REALM_FALLTHROUGH [[clang::fallthrough]] +#else +#define REALM_FALLTHROUGH +#endif + +#endif // REALM_UTIL_COMPILER_HPP diff --git a/src/object-store/tests/list.cpp b/src/object-store/tests/list.cpp index 4495762a..b98a57f4 100644 --- a/src/object-store/tests/list.cpp +++ b/src/object-store/tests/list.cpp @@ -61,7 +61,7 @@ TEST_CASE("list") { SECTION("add_notification_block()") { CollectionChangeSet change; - List lst(r, *r->config().schema->find("origin"), lv); + List lst(r, lv); auto write = [&](auto&& f) { r->begin_transaction(); @@ -72,7 +72,7 @@ TEST_CASE("list") { }; auto require_change = [&] { - auto token = lst.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr err) { + auto token = lst.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) { change = c; }); advance_and_notify(*r); @@ -81,7 +81,7 @@ TEST_CASE("list") { auto require_no_change = [&] { bool first = true; - auto token = lst.add_notification_callback([&, first](CollectionChangeSet c, std::exception_ptr err) mutable { + auto token = lst.add_notification_callback([&, first](CollectionChangeSet, std::exception_ptr) mutable { REQUIRE(first); first = false; }); @@ -190,7 +190,7 @@ TEST_CASE("list") { auto get_list = [&] { auto r = Realm::get_shared_realm(config); auto lv = r->read_group()->get_table("class_origin")->get_linklist(0, 0); - return List(r, *r->config().schema->find("origin"), lv); + return List(r, lv); }; auto change_list = [&] { r->begin_transaction(); @@ -250,7 +250,7 @@ TEST_CASE("list") { lv2->add(0); r->commit_transaction(); - List lst2(r, *r->config().schema->find("other_origin"), lv2); + List lst2(r, lv2); // Add a callback for list1, advance the version, then add a // callback for list2, so that the notifiers added at each source @@ -299,7 +299,7 @@ TEST_CASE("list") { } SECTION("sorted add_notification_block()") { - List lst(r, *r->config().schema->find("origin"), lv); + List lst(r, lv); Results results = lst.sort({{0}, {false}}); int notification_calls = 0; @@ -355,7 +355,7 @@ TEST_CASE("list") { } SECTION("filtered add_notification_block()") { - List lst(r, *r->config().schema->find("origin"), lv); + List lst(r, lv); Results results = lst.filter(target->where().less(0, 9)); int notification_calls = 0; @@ -420,8 +420,8 @@ TEST_CASE("list") { } SECTION("sort()") { - auto objectschema = &*r->config().schema->find("origin"); - List list(r, *objectschema, lv); + auto objectschema = &*r->config().schema->find("target"); + List list(r, lv); auto results = list.sort({{0}, {false}}); REQUIRE(&results.get_object_schema() == objectschema); @@ -435,8 +435,8 @@ TEST_CASE("list") { } SECTION("filter()") { - auto objectschema = &*r->config().schema->find("origin"); - List list(r, *objectschema, lv); + auto objectschema = &*r->config().schema->find("target"); + List list(r, lv); auto results = list.filter(target->where().greater(0, 5)); REQUIRE(&results.get_object_schema() == objectschema); @@ -447,4 +447,41 @@ TEST_CASE("list") { REQUIRE(results.get(i).get_index() == i + 6); } } + + SECTION("snapshot()") { + auto objectschema = &*r->config().schema->find("target"); + List list(r, lv); + + auto snapshot = list.snapshot(); + REQUIRE(&snapshot.get_object_schema() == objectschema); + REQUIRE(snapshot.get_mode() == Results::Mode::TableView); + REQUIRE(snapshot.size() == 10); + + r->begin_transaction(); + for (size_t i = 0; i < 5; ++i) { + list.remove(0); + } + REQUIRE(snapshot.size() == 10); + for (size_t i = 0; i < snapshot.size(); ++i) { + REQUIRE(snapshot.get(i).is_attached()); + } + for (size_t i = 0; i < 5; ++i) { + target->move_last_over(i); + } + REQUIRE(snapshot.size() == 10); + for (size_t i = 0; i < 5; ++i) { + REQUIRE(!snapshot.get(i).is_attached()); + } + for (size_t i = 5; i < 10; ++i) { + REQUIRE(snapshot.get(i).is_attached()); + } + list.add(0); + REQUIRE(snapshot.size() == 10); + } + + SECTION("get_object_schema()") { + List list(r, lv); + auto objectschema = &*r->config().schema->find("target"); + REQUIRE(&list.get_object_schema() == objectschema); + } } diff --git a/src/object-store/tests/results.cpp b/src/object-store/tests/results.cpp index 001a144a..18bf1243 100644 --- a/src/object-store/tests/results.cpp +++ b/src/object-store/tests/results.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include @@ -47,7 +48,7 @@ TEST_CASE("[results] notifications") { table->set_int(0, i, i * 2); r->commit_transaction(); - Results results(r, *config.schema->find("object"), table->where().greater(0, 0).less(0, 10)); + Results results(r, table->where().greater(0, 0).less(0, 10)); SECTION("unsorted notifications") { int notification_calls = 0; @@ -432,7 +433,7 @@ TEST_CASE("[results] async error handling") { auto r = Realm::get_shared_realm(config); auto coordinator = _impl::RealmCoordinator::get_existing_coordinator(config.path); - Results results(r, *config.schema->find("object"), *r->read_group()->get_table("class_object")); + Results results(r, *r->read_group()->get_table("class_object")); class OpenFileLimiter { public: @@ -546,10 +547,10 @@ TEST_CASE("[results] notifications after move") { auto r = Realm::get_shared_realm(config); auto table = r->read_group()->get_table("class_object"); - auto results = std::make_unique(r, *config.schema->find("object"), *table); + auto results = std::make_unique(r, *table); int notification_calls = 0; - auto token = results->add_notification_callback([&](CollectionChangeSet c, std::exception_ptr err) { + auto token = results->add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) { REQUIRE_FALSE(err); ++notification_calls; }); @@ -595,7 +596,7 @@ TEST_CASE("[results] error messages") { auto r = Realm::get_shared_realm(config); auto table = r->read_group()->get_table("class_object"); - Results results(r, *config.schema->find("object"), *table); + Results results(r, *table); r->begin_transaction(); table->add_empty_row(); @@ -609,3 +610,307 @@ TEST_CASE("[results] error messages") { REQUIRE_THROWS_WITH(results.sum(0), "Cannot sum property 'value': operation not supported for 'string' properties"); } } + +TEST_CASE("results: snapshots") { + InMemoryTestFile config; + config.cache = false; + config.automatic_change_notifications = false; + config.schema = std::make_unique(Schema{ + {"object", "", { + {"value", PropertyType::Int}, + {"array", PropertyType::Array, "linked to object"} + }}, + {"linked to object", "", { + {"value", PropertyType::Int} + }} + }); + + auto r = Realm::get_shared_realm(config); + + auto write = [&](auto&& f) { + r->begin_transaction(); + f(); + r->commit_transaction(); + advance_and_notify(*r); + }; + + SECTION("snapshot of empty Results") { + Results results; + auto snapshot = results.snapshot(); + REQUIRE(snapshot.size() == 0); + } + + SECTION("snapshot of Results based on Table") { + auto table = r->read_group()->get_table("class_object"); + Results results(r, *table); + + { + // A newly-added row should not appear in the snapshot. + auto snapshot = results.snapshot(); + REQUIRE(results.size() == 0); + REQUIRE(snapshot.size() == 0); + write([=]{ + table->add_empty_row(); + }); + REQUIRE(results.size() == 1); + REQUIRE(snapshot.size() == 0); + } + + { + // Removing a row present in the snapshot should not affect the size of the snapshot, + // but will result in the snapshot returning a detached row accessor. + auto snapshot = results.snapshot(); + REQUIRE(results.size() == 1); + REQUIRE(snapshot.size() == 1); + write([=]{ + table->move_last_over(0); + }); + REQUIRE(results.size() == 0); + REQUIRE(snapshot.size() == 1); + REQUIRE(!snapshot.get(0).is_attached()); + + // Adding a row at the same index that was formerly present in the snapshot shouldn't + // affect the state of the snapshot. + write([=]{ + table->add_empty_row(); + }); + REQUIRE(snapshot.size() == 1); + REQUIRE(!snapshot.get(0).is_attached()); + } + } + + SECTION("snapshot of Results based on LinkView") { + auto object = r->read_group()->get_table("class_object"); + auto linked_to = r->read_group()->get_table("class_linked to object"); + + write([=]{ + object->add_empty_row(); + }); + + LinkViewRef lv = object->get_linklist(1, 0); + Results results(r, lv); + + { + // A newly-added row should not appear in the snapshot. + auto snapshot = results.snapshot(); + REQUIRE(results.size() == 0); + REQUIRE(snapshot.size() == 0); + write([=]{ + lv->add(linked_to->add_empty_row()); + }); + REQUIRE(results.size() == 1); + REQUIRE(snapshot.size() == 0); + } + + { + // Removing a row from the link list should not affect the snapshot. + auto snapshot = results.snapshot(); + REQUIRE(results.size() == 1); + REQUIRE(snapshot.size() == 1); + write([=]{ + lv->remove(0); + }); + REQUIRE(results.size() == 0); + REQUIRE(snapshot.size() == 1); + REQUIRE(snapshot.get(0).is_attached()); + + // Removing a row present in the snapshot from its table should result in the snapshot + // returning a detached row accessor. + write([=]{ + linked_to->remove(0); + }); + REQUIRE(snapshot.size() == 1); + REQUIRE(!snapshot.get(0).is_attached()); + + // Adding a new row to the link list shouldn't affect the state of the snapshot. + write([=]{ + lv->add(linked_to->add_empty_row()); + }); + REQUIRE(snapshot.size() == 1); + REQUIRE(!snapshot.get(0).is_attached()); + } + } + + SECTION("snapshot of Results based on Query") { + auto table = r->read_group()->get_table("class_object"); + Query q = table->column(0) > 0; + Results results(r, std::move(q)); + + { + // A newly-added row should not appear in the snapshot. + auto snapshot = results.snapshot(); + REQUIRE(results.size() == 0); + REQUIRE(snapshot.size() == 0); + write([=]{ + table->set_int(0, table->add_empty_row(), 1); + }); + REQUIRE(results.size() == 1); + REQUIRE(snapshot.size() == 0); + } + + { + // Updating a row to no longer match the query criteria should not affect the snapshot. + auto snapshot = results.snapshot(); + REQUIRE(results.size() == 1); + REQUIRE(snapshot.size() == 1); + write([=]{ + table->set_int(0, 0, 0); + }); + REQUIRE(results.size() == 0); + REQUIRE(snapshot.size() == 1); + REQUIRE(snapshot.get(0).is_attached()); + + // Removing a row present in the snapshot from its table should result in the snapshot + // returning a detached row accessor. + write([=]{ + table->remove(0); + }); + REQUIRE(snapshot.size() == 1); + REQUIRE(!snapshot.get(0).is_attached()); + + // Adding a new row that matches the query criteria shouldn't affect the state of the snapshot. + write([=]{ + table->set_int(0, table->add_empty_row(), 1); + }); + REQUIRE(snapshot.size() == 1); + REQUIRE(!snapshot.get(0).is_attached()); + } + } + + SECTION("snapshot of Results based on TableView from query") { + auto table = r->read_group()->get_table("class_object"); + Query q = table->column(0) > 0; + Results results(r, q.find_all(), {}); + + { + // A newly-added row should not appear in the snapshot. + auto snapshot = results.snapshot(); + REQUIRE(results.size() == 0); + REQUIRE(snapshot.size() == 0); + write([=]{ + table->set_int(0, table->add_empty_row(), 1); + }); + REQUIRE(results.size() == 1); + REQUIRE(snapshot.size() == 0); + } + + { + // Updating a row to no longer match the query criteria should not affect the snapshot. + auto snapshot = results.snapshot(); + REQUIRE(results.size() == 1); + REQUIRE(snapshot.size() == 1); + write([=]{ + table->set_int(0, 0, 0); + }); + REQUIRE(results.size() == 0); + REQUIRE(snapshot.size() == 1); + REQUIRE(snapshot.get(0).is_attached()); + + // Removing a row present in the snapshot from its table should result in the snapshot + // returning a detached row accessor. + write([=]{ + table->remove(0); + }); + REQUIRE(snapshot.size() == 1); + REQUIRE(!snapshot.get(0).is_attached()); + + // Adding a new row that matches the query criteria shouldn't affect the state of the snapshot. + write([=]{ + table->set_int(0, table->add_empty_row(), 1); + }); + REQUIRE(snapshot.size() == 1); + REQUIRE(!snapshot.get(0).is_attached()); + } + } + + SECTION("snapshot of Results based on TableView from backlinks") { + auto object = r->read_group()->get_table("class_object"); + auto linked_to = r->read_group()->get_table("class_linked to object"); + + write([=]{ + linked_to->add_empty_row(); + }); + + TableView backlinks = linked_to->get_backlink_view(0, object.get(), 1); + Results results(r, std::move(backlinks), {}); + + auto lv = object->get_linklist(1, object->add_empty_row()); + + { + // A newly-added row should not appear in the snapshot. + auto snapshot = results.snapshot(); + REQUIRE(results.size() == 0); + REQUIRE(snapshot.size() == 0); + write([=]{ + lv->add(0); + }); + REQUIRE(results.size() == 1); + REQUIRE(snapshot.size() == 0); + } + + { + // Removing the link should not affect the snapshot. + auto snapshot = results.snapshot(); + REQUIRE(results.size() == 1); + REQUIRE(snapshot.size() == 1); + write([=]{ + lv->remove(0); + }); + REQUIRE(results.size() == 0); + REQUIRE(snapshot.size() == 1); + REQUIRE(snapshot.get(0).is_attached()); + + // Removing a row present in the snapshot from its table should result in the snapshot + // returning a detached row accessor. + write([=]{ + object->remove(0); + }); + REQUIRE(snapshot.size() == 1); + REQUIRE(!snapshot.get(0).is_attached()); + + // Adding a new link shouldn't affect the state of the snapshot. + write([=]{ + object->add_empty_row(); + auto lv = object->get_linklist(1, object->add_empty_row()); + lv->add(0); + }); + REQUIRE(snapshot.size() == 1); + REQUIRE(!snapshot.get(0).is_attached()); + } + } + + SECTION("snapshot of Results with notification callback registered") { + auto table = r->read_group()->get_table("class_object"); + Query q = table->column(0) > 0; + Results results(r, q.find_all(), {}); + + auto token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) { + REQUIRE_FALSE(err); + }); + advance_and_notify(*r); + + SECTION("snapshot of lvalue") { + auto snapshot = results.snapshot(); + write([=] { + table->set_int(0, table->add_empty_row(), 1); + }); + REQUIRE(snapshot.size() == 0); + } + + SECTION("snapshot of rvalue") { + auto snapshot = std::move(results).snapshot(); + write([=] { + table->set_int(0, table->add_empty_row(), 1); + }); + REQUIRE(snapshot.size() == 0); + } + } + + SECTION("adding notification callback to snapshot throws") { + auto table = r->read_group()->get_table("class_object"); + Query q = table->column(0) > 0; + Results results(r, q.find_all(), {}); + auto snapshot = results.snapshot(); + CHECK_THROWS(snapshot.add_notification_callback([](CollectionChangeSet, std::exception_ptr) {})); + } +} diff --git a/src/object-store/tests/transaction_log_parsing.cpp b/src/object-store/tests/transaction_log_parsing.cpp index 4ff97415..bf4d71fa 100644 --- a/src/object-store/tests/transaction_log_parsing.cpp +++ b/src/object-store/tests/transaction_log_parsing.cpp @@ -59,7 +59,7 @@ private: Group const& m_group; LinkViewRef m_linkview; - std::vector m_initial; + std::vector m_initial; void validate(CollectionChangeSet const& info) { diff --git a/src/object-store/tests/util/test_file.cpp b/src/object-store/tests/util/test_file.cpp index 0c6efa99..8e63c4cb 100644 --- a/src/object-store/tests/util/test_file.cpp +++ b/src/object-store/tests/util/test_file.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #endif TestFile::TestFile() @@ -60,6 +61,12 @@ public: if (value == 2) return; + if (value & 1) { + // Synchronize on the first handover of a given coordinator. + value &= ~1; + m_signal.load(); + } + auto c = reinterpret_cast(value); c->on_change(); m_signal.store(1, std::memory_order_relaxed); @@ -72,20 +79,29 @@ public: m_thread.join(); } - void on_change(realm::_impl::RealmCoordinator* c) + void on_change(const std::shared_ptr& c) { - m_signal.store(reinterpret_cast(c), std::memory_order_relaxed); + auto& it = m_published_coordinators[c.get()]; + if (it.lock()) { + m_signal.store(reinterpret_cast(c.get()), std::memory_order_relaxed); + } else { + // Synchronize on the first handover of a given coordinator. + it = c; + m_signal = reinterpret_cast(c.get()) | 1; + } + while (m_signal.load(std::memory_order_relaxed) != 1) ; } private: std::atomic m_signal{0}; std::thread m_thread; + std::map> m_published_coordinators; } s_worker; void advance_and_notify(realm::Realm& realm) { - s_worker.on_change(realm::_impl::RealmCoordinator::get_existing_coordinator(realm.config().path).get()); + s_worker.on_change(realm::_impl::RealmCoordinator::get_existing_coordinator(realm.config().path)); realm.notify(); }