diff --git a/.gitmodules b/.gitmodules index f0d97181..1475d7b4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,12 +1,10 @@ -[submodule "src/object-store/external/pegtl"] - path = src/object-store/external/pegtl - url = https://github.com/ColinH/PEGTL -[submodule "src/object-store/external/catch"] - path = src/object-store/external/catch - url = https://github.com/philsquared/Catch [submodule "vendor/GCDWebServer"] path = vendor/GCDWebServer url = https://github.com/swisspol/GCDWebServer.git [submodule "docs/jsdoc-template"] path = docs/jsdoc-template url = https://github.com/realm/realm-jsdoc.git +[submodule "src/object-store"] + path = src/object-store + url = https://github.com/realm/realm-object-store.git + branch = js diff --git a/src/object-store b/src/object-store new file mode 160000 index 00000000..c34b3db8 --- /dev/null +++ b/src/object-store @@ -0,0 +1 @@ +Subproject commit c34b3db85d86bf542fb57cd34f393efeac87b929 diff --git a/src/object-store/.gitignore b/src/object-store/.gitignore deleted file mode 100644 index 3ea88b23..00000000 --- a/src/object-store/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -# CMake -.ninja_deps -.ninja_log -CMakeCache.txt -CMakeFiles/ -Makefile -build.ninja -cmake_install.cmake -rules.ninja - -# Build products -src/librealm-object-store.dylib -src/librealm-object-store-static.a -tests/tests diff --git a/src/object-store/.gitmodules b/src/object-store/.gitmodules deleted file mode 100644 index 44b8f2ab..00000000 --- a/src/object-store/.gitmodules +++ /dev/null @@ -1,6 +0,0 @@ -[submodule "external/pegtl"] - path = external/pegtl - url = https://github.com/ColinH/PEGTL -[submodule "external/catch"] - path = external/catch - url = https://github.com/philsquared/Catch diff --git a/src/object-store/CMake/CodeCoverage.cmake b/src/object-store/CMake/CodeCoverage.cmake deleted file mode 100644 index b8308286..00000000 --- a/src/object-store/CMake/CodeCoverage.cmake +++ /dev/null @@ -1,49 +0,0 @@ -find_program(LCOV_PATH lcov) -find_program(GENHTML_PATH genhtml) -find_program(GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/tests) - -set(CMAKE_CXX_FLAGS_COVERAGE "-g -O0 -fprofile-arcs -ftest-coverage" - CACHE STRING "Flags used by the C++ compiler during coverage builds.") -mark_as_advanced(CMAKE_CXX_FLAGS_COVERAGE) - -if(CMAKE_BUILD_TYPE STREQUAL "Coverage") - if(NOT (LCOV_PATH AND GENHTML_PATH AND GCOVR_PATH)) - message(FATAL_ERROR "Generating a coverage report requires lcov and gcovr") - endif() - - function(create_coverage_target targetname testrunner) - add_custom_target(${targetname} - # Clear previous coverage information - COMMAND ${LCOV_PATH} --directory . --zerocounters - - # Run the tests - COMMAND ${testrunner} - - # Generate new coverage report - COMMAND ${LCOV_PATH} --directory . --capture --output-file coverage.info - COMMAND ${LCOV_PATH} --extract coverage.info '${CMAKE_SOURCE_DIR}/src/*' --output-file coverage.info.cleaned - COMMAND ${GENHTML_PATH} -o coverage coverage.info.cleaned - COMMAND ${CMAKE_COMMAND} -E remove coverage.info coverage.info.cleaned - - COMMAND echo Open coverage/index.html in your browser to view the coverage report. - - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - ) - - add_custom_target(${targetname}-cobertura - COMMAND ${testrunner} - COMMAND ${GCOVR_PATH} -x -r ${CMAKE_SOURCE_DIR}/src -o coverage.xml - COMMAND echo Code coverage report written to coverage.xml - - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - ) - endfunction() -else() - function(create_coverage_target targetname testrunner) - add_custom_target(${targetname} - COMMAND echo "Configure with -DCMAKE_BUILD_TYPE=Coverage to generate coverage reports") - - add_custom_target(${targetname}-cobertura - COMMAND echo "Configure with -DCMAKE_BUILD_TYPE=Coverage to generate coverage reports") - endfunction() -endif() diff --git a/src/object-store/CMake/CompilerFlags.cmake b/src/object-store/CMake/CompilerFlags.cmake deleted file mode 100644 index be24ea56..00000000 --- a/src/object-store/CMake/CompilerFlags.cmake +++ /dev/null @@ -1,40 +0,0 @@ -set(CMAKE_CXX_STANDARD 14) -set(CMAKE_CXX_STANDARD_REQUIRED on) -set(CMAKE_CXX_EXTENSIONS off) -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} 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") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fdiagnostics-color=always") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always") - endif() -endif() diff --git a/src/object-store/CMake/RealmCore.cmake b/src/object-store/CMake/RealmCore.cmake deleted file mode 100644 index 42e2a7e4..00000000 --- a/src/object-store/CMake/RealmCore.cmake +++ /dev/null @@ -1,126 +0,0 @@ -include(ExternalProject) - -if(${CMAKE_GENERATOR} STREQUAL "Unix Makefiles") - set(MAKE_EQUAL_MAKE "MAKE=$(MAKE)") -endif() - -if(SANITIZE_ADDRESS) - set(EXPORT_MAKEFLAGS export MAKEFLAGS='EXTRA_CFLAGS=-fsanitize=address EXTRA_LDFLAGS=-fsanitize=address') -else() - set(EXPORT_MAKEFLAGS true) -endif() - -if (${CMAKE_VERSION} VERSION_GREATER "3.4.0") - set(USES_TERMINAL_BUILD USES_TERMINAL_BUILD 1) -endif() - -function(use_realm_core version_or_path_to_source) - if("${version_or_path_to_source}" MATCHES "^[0-9]+(\\.[0-9])+") - if(APPLE) - download_realm_core(${version_or_path_to_source}) - else() - clone_and_build_realm_core("v${version_or_path_to_source}") - endif() - else() - build_existing_realm_core(${version_or_path_to_source}) - endif() - set(REALM_CORE_INCLUDE_DIR ${REALM_CORE_INCLUDE_DIR} PARENT_SCOPE) -endfunction() - -function(download_realm_core core_version) - 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}") - set(core_tarball "${core_directory_parent}/${core_tarball_name}") - - if (NOT EXISTS ${core_tarball}) - if (NOT EXISTS ${core_temp_tarball}) - message("Downloading core ${core_version} from ${core_url}.") - file(DOWNLOAD ${core_url} ${core_temp_tarball}.tmp SHOW_PROGRESS) - file(RENAME ${core_temp_tarball}.tmp ${core_temp_tarball}) - endif() - file(COPY ${core_temp_tarball} DESTINATION ${core_directory_parent}) - endif() - - set(core_library_debug ${core_directory}/librealm-dbg.a) - set(core_library_release ${core_directory}/librealm.a) - set(core_libraries ${core_library_debug} ${core_library_release}) - - add_custom_command( - COMMENT "Extracting ${core_tarball_name}" - OUTPUT ${core_libraries} - DEPENDS ${core_tarball} - COMMAND ${CMAKE_COMMAND} -E tar xf ${core_tarball} - COMMAND ${CMAKE_COMMAND} -E remove_directory ${core_directory} - COMMAND ${CMAKE_COMMAND} -E rename core ${core_directory} - COMMAND ${CMAKE_COMMAND} -E touch_nocreate ${core_libraries}) - - add_custom_target(realm-core DEPENDS ${core_libraries}) - - add_library(realm STATIC IMPORTED) - add_dependencies(realm realm-core) - set_property(TARGET realm PROPERTY IMPORTED_LOCATION_DEBUG ${core_library_debug}) - set_property(TARGET realm PROPERTY IMPORTED_LOCATION_COVERAGE ${core_library_debug}) - set_property(TARGET realm PROPERTY IMPORTED_LOCATION_RELEASE ${core_library_release}) - set_property(TARGET realm PROPERTY IMPORTED_LOCATION ${core_library_release}) - - set(REALM_CORE_INCLUDE_DIR ${core_directory}/include PARENT_SCOPE) -endfunction() - -macro(define_built_realm_core_target core_directory) - set(core_library_debug ${core_directory}/src/realm/librealm-dbg.a) - set(core_library_release ${core_directory}/src/realm/librealm.a) - set(core_libraries ${core_library_debug} ${core_library_release}) - - ExternalProject_Add_Step(realm-core ensure-libraries - COMMAND ${CMAKE_COMMAND} -E touch_nocreate ${core_libraries} - OUTPUT ${core_libraries} - DEPENDEES build - ) - - add_library(realm STATIC IMPORTED) - add_dependencies(realm realm-core) - - set_property(TARGET realm PROPERTY IMPORTED_LOCATION_DEBUG ${core_library_debug}) - set_property(TARGET realm PROPERTY IMPORTED_LOCATION_COVERAGE ${core_library_debug}) - set_property(TARGET realm PROPERTY IMPORTED_LOCATION_RELEASE ${core_library_release}) - set_property(TARGET realm PROPERTY IMPORTED_LOCATION ${core_library_release}) - - set(REALM_CORE_INCLUDE_DIR ${core_directory}/src PARENT_SCOPE) -endmacro() - -function(clone_and_build_realm_core branch) - set(core_prefix_directory "${CMAKE_CURRENT_SOURCE_DIR}${CMAKE_FILES_DIRECTORY}/realm-core") - ExternalProject_Add(realm-core - GIT_REPOSITORY "git@github.com:realm/realm-core.git" - GIT_TAG ${branch} - PREFIX ${core_prefix_directory} - BUILD_IN_SOURCE 1 - CONFIGURE_COMMAND "" - BUILD_COMMAND ${EXPORT_MAKEFLAGS} && make -C src/realm librealm.a librealm-dbg.a - INSTALL_COMMAND "" - ${USES_TERMINAL_BUILD} - ) - - ExternalProject_Get_Property(realm-core SOURCE_DIR) - define_built_realm_core_target(${SOURCE_DIR}) -endfunction() - -function(build_existing_realm_core core_directory) - get_filename_component(core_directory ${core_directory} ABSOLUTE) - ExternalProject_Add(realm-core - URL "" - PREFIX ${CMAKE_CURRENT_SOURCE_DIR}${CMAKE_FILES_DIRECTORY}/realm-core - SOURCE_DIR ${core_directory} - BUILD_IN_SOURCE 1 - BUILD_ALWAYS 1 - CONFIGURE_COMMAND "" - BUILD_COMMAND ${EXPORT_MAKEFLAGS} && make -C src/realm librealm.a librealm-dbg.a - INSTALL_COMMAND "" - ${USES_TERMINAL_BUILD} - ) - - define_built_realm_core_target(${core_directory}) -endfunction() diff --git a/src/object-store/CMake/Sanitizers.cmake b/src/object-store/CMake/Sanitizers.cmake deleted file mode 100644 index 202f1a68..00000000 --- a/src/object-store/CMake/Sanitizers.cmake +++ /dev/null @@ -1,20 +0,0 @@ -option(SANITIZE_ADDRESS "build with ASan") -option(SANITIZE_THREAD "build with TSan") -option(SANITIZE_UNDEFINED "build with UBSan") - -if(SANITIZE_ADDRESS) - set(SANITIZER_FLAGS "${SANITIZER_FLAGS} -fsanitize=address") -endif() - -if(SANITIZE_THREAD) - set(SANITIZER_FLAGS "${SANITIZER_FLAGS} -fsanitize=thread") -endif() - -if(SANITIZE_UNDEFINED) - set(SANITIZER_FLAGS "${SANITIZER_FLAGS} -fsanitize=undefined") -endif() - -if(SANITIZE_ADDRESS OR SANITIZE_THREAD OR SANITIZE_UNDEFINED) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SANITIZER_FLAGS} -fno-omit-frame-pointer") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${SANITIZER_FLAGS}") -endif() diff --git a/src/object-store/CMakeLists.txt b/src/object-store/CMakeLists.txt deleted file mode 100644 index 91fa56db..00000000 --- a/src/object-store/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -cmake_minimum_required(VERSION 3.2.0) - -set(CMAKE_BUILD_TYPE Debug CACHE STRING "") -project(realm-object-store) - -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake") - -include(CodeCoverage) -include(CompilerFlags) -include(Sanitizers) - -include(RealmCore) -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) - -add_subdirectory(src) -add_subdirectory(tests) diff --git a/src/object-store/README.md b/src/object-store/README.md deleted file mode 100644 index 081efcc6..00000000 --- a/src/object-store/README.md +++ /dev/null @@ -1,56 +0,0 @@ -# Realm Object Store - -Cross-platform code used accross bindings. Binding developers can choose to use some or all the included functionality: -- `object_store`/`schema`/`object_schema`/`property` - contains the structures and logic used to setup and modify realm files and their schema. -- `shared_realm` - wraps the object_store apis to provide transactions, notifications, realm caching, migrations, and other higher level functionality. -- `object_accessor`/`results`/`list` - accessor classes, object creation/update pipeline, and helpers for creating platform specific property getters and setters. -- `parser`/`query_builder` - cross platform query parser and query builder - requires and object_accessor specialization for argument support. Depends on https://github.com/ColinH/PEGTL - -## Building - -The object store's build system currently only suports building for OS X. The object store itself can build for all Apple -platforms when integrated into a binding. - -1. Download PEGTL dependency - ``` - git submodule update --init - ``` - -2. Install CMake. You can download an installer for OS X from the [CMake download page](https://cmake.org/download/), or install via [Homebrew](http://brew.sh): - ``` - brew install cmake - ``` - -3. Generate build files: - - ``` - cmake . - ``` - -4. Build: - - ``` - make - ``` - -If you wish to build against a local version of core you can invoke `cmake` like so: - -``` -cmake -DREALM_CORE_VERSION=/path/to/realm-core -``` - -The given core tree will be built as part of the object store build. - -### Building with Sanitizers - -The object store can be built using ASan, TSan and/or UBSan by specifying `-DSANITIZE_ADDRESS=1`, `-DSANITIZE_THREAD=1`, or `-DSANITIZE_UNDEFINED=1` when inoking CMake. -Building with ASan requires specifying a path to core with `-DREAM_CORE_VERSION` as core needs to also be built with ASan enabled. - -On OS X, the Xcode-provided copy of Clang only comes with ASan, and using TSan or UBSan requires a custom build of Clang. -If you have installed Clang as an external Xcode toolchain (using the `install-xcode-toolchain` when building LLVM), note that you'll have to specify `-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++` when running `cmake` to stop cmake from being too clever. - -## Testing - -``` -make run-tests -``` diff --git a/src/object-store/external/catch b/src/object-store/external/catch deleted file mode 160000 index f294c984..00000000 --- a/src/object-store/external/catch +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f294c9847272b1b92c5119a6f711e57113b5f231 diff --git a/src/object-store/external/pegtl b/src/object-store/external/pegtl deleted file mode 160000 index 3c4128a7..00000000 --- a/src/object-store/external/pegtl +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3c4128a7e7e1288eb746418ea60c41477358f26a diff --git a/src/object-store/src/CMakeLists.txt b/src/object-store/src/CMakeLists.txt deleted file mode 100644 index 19c5f766..00000000 --- a/src/object-store/src/CMakeLists.txt +++ /dev/null @@ -1,80 +0,0 @@ -set(SOURCES - collection_notifications.cpp - index_set.cpp - list.cpp - object_schema.cpp - object_store.cpp - results.cpp - schema.cpp - shared_realm.cpp - impl/collection_change_builder.cpp - impl/collection_notifier.cpp - impl/list_notifier.cpp - impl/realm_coordinator.cpp - impl/results_notifier.cpp - impl/transact_log_handler.cpp - parser/parser.cpp - parser/query_builder.cpp - util/format.cpp - util/thread_id.cpp) - -set(HEADERS - collection_notifications.hpp - index_set.hpp - list.hpp - object_schema.hpp - object_store.hpp - property.hpp - results.hpp - schema.hpp - shared_realm.hpp - impl/collection_change_builder.hpp - impl/collection_notifier.hpp - impl/external_commit_helper.hpp - impl/list_notifier.hpp - impl/realm_coordinator.hpp - impl/results_notifier.hpp - impl/transact_log_handler.hpp - impl/weak_realm_notifier.hpp - impl/weak_realm_notifier_base.hpp - 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) - -if(APPLE) - list(APPEND SOURCES - impl/apple/weak_realm_notifier.cpp - impl/apple/external_commit_helper.cpp) - list(APPEND HEADERS - impl/apple/weak_realm_notifier.hpp - impl/apple/external_commit_helper.hpp) - find_library(CF_LIBRARY CoreFoundation) -else() - list(APPEND SOURCES - impl/generic/external_commit_helper.cpp) - list(APPEND HEADERS - impl/generic/weak_realm_notifier.hpp - impl/generic/external_commit_helper.hpp) -endif() - -set(INCLUDE_DIRS ${REALM_CORE_INCLUDE_DIR} ${PEGTL_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) - -# An object library to group together the compilation of the source files. -add_library(realm-object-store-objects OBJECT ${SOURCES} ${HEADERS}) -add_dependencies(realm-object-store-objects realm) -set_target_properties(realm-object-store-objects PROPERTIES POSITION_INDEPENDENT_CODE 1) -target_include_directories(realm-object-store-objects PUBLIC ${INCLUDE_DIRS}) - -# A static library, aggregating the prebuilt object files. -add_library(realm-object-store-static STATIC $) -target_include_directories(realm-object-store-static PUBLIC ${INCLUDE_DIRS}) -target_link_libraries(realm-object-store-static PUBLIC realm ${CF_LIBRARY}) - -# A dynamic library, linking together the prebuilt object files. -add_library(realm-object-store SHARED $ placeholder.cpp) -target_include_directories(realm-object-store PUBLIC ${INCLUDE_DIRS}) -target_link_libraries(realm-object-store PRIVATE realm ${CF_LIBRARY}) diff --git a/src/object-store/src/binding_context.hpp b/src/object-store/src/binding_context.hpp deleted file mode 100644 index 8290144b..00000000 --- a/src/object-store/src/binding_context.hpp +++ /dev/null @@ -1,158 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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 BINDING_CONTEXT_HPP -#define BINDING_CONTEXT_HPP - -#include "index_set.hpp" - -#include -#include - -namespace realm { -// BindingContext is the extension point for adding binding-specific behavior to -// a SharedRealm. It can be used to store additonal data associated with the -// Realm which is needed by the binding, and there are several methods which -// can be overridden to receive notifications of state changes within the Realm. -// -// A simple implementation which lets the user register functions to be -// called on refresh could look like the following: -// -// class BindingContextImplementation : public BindingContext { -// public: -// // A token returned from add_notification that can be used to remove the -// // notification later -// struct token : private std::list>::iterator { -// token(std::list>::iterator it) : std::list>::iterator(it) { } -// friend class DelegateImplementation; -// }; -// -// token add_notification(std::function func) -// { -// m_registered_notifications.push_back(std::move(func)); -// return token(std::prev(m_registered_notifications.end())); -// } -// -// void remove_notification(token entry) -// { -// m_registered_notifications.erase(entry); -// } -// -// // Override the did_change method to call each registered notification -// void did_change(std::vector const&, std::vector const&) override -// { -// // Loop oddly so that unregistering a notification from within the -// // registered function works -// for (auto it = m_registered_notifications.begin(); it != m_registered_notifications.end(); ) { -// (*it++)(); -// } -// } -// -// private: -// std::list> m_registered_notifications; -// }; -class BindingContext { -public: - virtual ~BindingContext() = default; - - // If the user adds a notification handler to the Realm, will it ever - // actually be called? - virtual bool can_deliver_notifications() const noexcept { return true; } - - // Called by the Realm when a write transaction is committed to the file by - // a different Realm instance (possibly in a different process) - virtual void changes_available() { } - - struct ObserverState; - - // Override this function if you want to receive detailed information about - // external changes to a specific set of objects. - // This is called before each operation which may advance the read - // transaction to include - // ObserverStates for each row for which detailed change information is - // desired. - virtual std::vector get_observed_rows() { return {}; } - - // Called immediately before the read transaction is advanced if detailed - // change information was requested (by returning a non-empty array from - // get_observed_rows()). - // The observers vector is the vector returned by get_observed_row(), - // updated with change information. The invalidated vector is a list of the - // `info` fields of observed rows which will be deleted. - virtual void will_change(std::vector const& observers, - std::vector const& invalidated); - - // Called immediately after the read transaction version is advanced. Unlike - // will_change(), this is called even if detailed change information was not - // requested or if the Realm is not actually in a read transactuib, although - // both vectors will be empty in that case. - virtual void did_change(std::vector const& observers, - std::vector const& invalidated); - - // Change information for a single field of a row - struct ColumnInfo { - // Did this column change? - bool changed = false; - // For LinkList columns, what kind of change occurred? - // Always None for other column types - enum class Kind { - None, // No change - Set, // The entries at `indices` were assigned to - Insert, // New values were inserted at each of the indices given - Remove, // Values were removed at each of the indices given - SetAll // The entire LinkList has been replaced with a new set of values - } kind = Kind::None; - // The indices where things happened for Set, Insert and Remove - // Not used for None and SetAll - IndexSet indices; - }; - - // Information about an observed row in a table - // - // Each object which needs detailed change information should have an - // ObserverState entry in the vector returned from get_observed_rows(), with - // the initial table and row indexes set (and optionally the info field). - // The Realm parses the transaction log, and populates the `changes` vector - // in each ObserverState with information about what changes were made. - struct ObserverState { - // Initial table and row which is observed - // May be updated by row insertions and removals - size_t table_ndx; - size_t row_ndx; - - // Opaque userdata for the delegate's use - void* info; - - // Populated with information about which columns were changed - // May be shorter than the actual number of columns if the later columns - // are not modified - std::vector changes; - - // Simple lexographic ordering - friend bool operator<(ObserverState const& lft, ObserverState const& rgt) - { - return std::tie(lft.table_ndx, lft.row_ndx) < std::tie(rgt.table_ndx, rgt.row_ndx); - } - }; -}; - -inline void BindingContext::will_change(std::vector const&, std::vector const&) { } -inline void BindingContext::did_change(std::vector const&, std::vector const&) { } -} // namespace realm - -#endif /* BINDING_CONTEXT_HPP */ diff --git a/src/object-store/src/collection_notifications.cpp b/src/object-store/src/collection_notifications.cpp deleted file mode 100644 index 12fca831..00000000 --- a/src/object-store/src/collection_notifications.cpp +++ /dev/null @@ -1,56 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "collection_notifications.hpp" - -#include "impl/collection_notifier.hpp" - -using namespace realm; -using namespace realm::_impl; - -NotificationToken::NotificationToken(std::shared_ptr<_impl::CollectionNotifier> notifier, size_t token) -: m_notifier(std::move(notifier)), m_token(token) -{ -} - -NotificationToken::~NotificationToken() -{ - // m_notifier itself (and not just the pointed-to thing) needs to be accessed - // atomically to ensure that there are no data races when the token is - // destroyed after being modified on a different thread. - // This is needed despite the token not being thread-safe in general as - // users find it very surprising for obj-c objects to care about what - // thread they are deallocated on. - if (auto notifier = m_notifier.exchange({})) { - notifier->remove_callback(m_token); - } -} - -NotificationToken::NotificationToken(NotificationToken&&) = default; - -NotificationToken& NotificationToken::operator=(realm::NotificationToken&& rgt) -{ - if (this != &rgt) { - if (auto notifier = m_notifier.exchange({})) { - notifier->remove_callback(m_token); - } - m_notifier = std::move(rgt.m_notifier); - m_token = rgt.m_token; - } - return *this; -} diff --git a/src/object-store/src/collection_notifications.hpp b/src/object-store/src/collection_notifications.hpp deleted file mode 100644 index ed75c315..00000000 --- a/src/object-store/src/collection_notifications.hpp +++ /dev/null @@ -1,71 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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_COLLECTION_NOTIFICATIONS_HPP -#define REALM_COLLECTION_NOTIFICATIONS_HPP - -#include "index_set.hpp" -#include "util/atomic_shared_ptr.hpp" - -#include -#include -#include -#include - -namespace realm { -namespace _impl { - class CollectionNotifier; -} - -// A token which keeps an asynchronous query alive -struct NotificationToken { - NotificationToken() = default; - NotificationToken(std::shared_ptr<_impl::CollectionNotifier> notifier, size_t token); - ~NotificationToken(); - - NotificationToken(NotificationToken&&); - NotificationToken& operator=(NotificationToken&&); - - NotificationToken(NotificationToken const&) = delete; - NotificationToken& operator=(NotificationToken const&) = delete; - -private: - util::AtomicSharedPtr<_impl::CollectionNotifier> m_notifier; - size_t m_token; -}; - -struct CollectionChangeSet { - struct Move { - size_t from; - size_t to; - - bool operator==(Move m) const { return from == m.from && to == m.to; } - }; - - IndexSet deletions; - IndexSet insertions; - IndexSet modifications; - std::vector moves; - - bool empty() const { return deletions.empty() && insertions.empty() && modifications.empty() && moves.empty(); } -}; - -using CollectionChangeCallback = std::function; -} // namespace realm - -#endif // REALM_COLLECTION_NOTIFICATIONS_HPP diff --git a/src/object-store/src/impl/android/external_commit_helper.cpp b/src/object-store/src/impl/android/external_commit_helper.cpp deleted file mode 100644 index 86d3b24d..00000000 --- a/src/object-store/src/impl/android/external_commit_helper.cpp +++ /dev/null @@ -1,202 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "impl/external_commit_helper.hpp" -#include "impl/realm_coordinator.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __ANDROID__ -#include -#define ANDROID_LOG __android_log_print -#else -#define ANDROID_LOG(...) -#endif - -using namespace realm; -using namespace realm::_impl; - -#define LOGE(fmt...) do { \ - fprintf(stderr, fmt); \ - ANDROID_LOG(ANDROID_LOG_ERROR, "REALM", fmt); \ -} while (0) - -namespace { -// Write a byte to a pipe to notify anyone waiting for data on the pipe -void notify_fd(int fd) -{ - while (true) { - char c = 0; - ssize_t ret = write(fd, &c, 1); - if (ret == 1) { - break; - } - - // If the pipe's buffer is full, we need to read some of the old data in - // it to make space. We don't just read in the code waiting for - // notifications so that we can notify multiple waiters with a single - // write. - assert(ret == -1 && errno == EAGAIN); - char buff[1024]; - read(fd, buff, sizeof buff); - } -} -} // anonymous namespace - -void ExternalCommitHelper::FdHolder::close() -{ - if (m_fd != -1) { - ::close(m_fd); - } - m_fd = -1; -} - -ExternalCommitHelper::ExternalCommitHelper(RealmCoordinator& parent) -: m_parent(parent) -{ - m_epfd = epoll_create(1); - if (m_epfd == -1) { - throw std::system_error(errno, std::system_category()); - } - - auto path = parent.get_path() + ".note"; - - // Create and open the named pipe - int ret = mkfifo(path.c_str(), 0600); - if (ret == -1) { - int err = errno; - if (err == ENOTSUP) { - // Filesystem doesn't support named pipes, so try putting it in tmp instead - // Hash collisions are okay here because they just result in doing - // extra work, as opposed to correctness problems - std::ostringstream ss; - - std::string tmp_dir(getenv("TMPDIR")); - ss << tmp_dir; - if (tmp_dir.back() != '/') - ss << '/'; - ss << "realm_" << std::hash()(path) << ".note"; - path = ss.str(); - ret = mkfifo(path.c_str(), 0600); - err = errno; - } - // the fifo already existing isn't an error - if (ret == -1 && err != EEXIST) { - throw std::system_error(err, std::system_category()); - } - } - - m_notify_fd = open(path.c_str(), O_RDWR); - if (m_notify_fd == -1) { - throw std::system_error(errno, std::system_category()); - } - - // Make writing to the pipe return -1 when the pipe's buffer is full - // rather than blocking until there's space available - ret = fcntl(m_notify_fd, F_SETFL, O_NONBLOCK); - if (ret == -1) { - throw std::system_error(errno, std::system_category()); - } - - // Create the anonymous pipe - int pipe_fd[2]; - ret = pipe(pipe_fd); - if (ret == -1) { - throw std::system_error(errno, std::system_category()); - } - - m_shutdown_read_fd = pipe_fd[0]; - m_shutdown_write_fd = pipe_fd[1]; - - m_thread = std::thread([=] { - try { - listen(); - } - catch (std::exception const& e) { - LOGE("uncaught exception in notifier thread: %s: %s\n", typeid(e).name(), e.what()); - throw; - } - catch (...) { - LOGE("uncaught exception in notifier thread\n"); - throw; - } - }); -} - -ExternalCommitHelper::~ExternalCommitHelper() -{ - notify_fd(m_shutdown_write_fd); - m_thread.join(); // Wait for the thread to exit -} - -void ExternalCommitHelper::listen() -{ - pthread_setname_np(pthread_self(), "Realm notification listener"); - - int ret; - - struct epoll_event event[2]; - - event[0].events = EPOLLIN | EPOLLET; - event[0].data.fd = m_notify_fd; - ret = epoll_ctl(m_epfd, EPOLL_CTL_ADD, m_notify_fd, &event[0]); - assert(ret == 0); - - event[1].events = EPOLLIN; - event[1].data.fd = m_shutdown_read_fd; - ret = epoll_ctl(m_epfd, EPOLL_CTL_ADD, m_shutdown_read_fd, &event[1]); - assert(ret == 0); - - while (true) { - struct epoll_event ev; - ret = epoll_wait(m_epfd, &ev, 1, -1); - - if (ret == -1 && errno == EINTR) { - // Interrupted system call, try again. - continue; - } - - assert(ret >= 0); - if (ret == 0) { - // Spurious wakeup; just wait again - continue; - } - - if (ev.data.u32 == (uint32_t)m_shutdown_read_fd) { - return; - } - assert(ev.data.u32 == (uint32_t)m_notify_fd); - - m_parent.on_change(); - } -} - - -void ExternalCommitHelper::notify_others() -{ - notify_fd(m_notify_fd); -} diff --git a/src/object-store/src/impl/android/external_commit_helper.hpp b/src/object-store/src/impl/android/external_commit_helper.hpp deleted file mode 100644 index 8e78c65f..00000000 --- a/src/object-store/src/impl/android/external_commit_helper.hpp +++ /dev/null @@ -1,76 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include - -namespace realm { - -namespace _impl { -class RealmCoordinator; - -class ExternalCommitHelper { -public: - ExternalCommitHelper(RealmCoordinator& parent); - ~ExternalCommitHelper(); - - void notify_others(); - -private: - // A RAII holder for a file descriptor which automatically closes the wrapped - // fd when it's deallocated - class FdHolder { - public: - FdHolder() = default; - ~FdHolder() { close(); } - operator int() const { return m_fd; } - - FdHolder& operator=(int new_fd) { - close(); - m_fd = new_fd; - return *this; - } - - private: - int m_fd = -1; - void close(); - - FdHolder& operator=(FdHolder const&) = delete; - FdHolder(FdHolder const&) = delete; - }; - - void listen(); - - RealmCoordinator& m_parent; - - // The listener thread - std::thread m_thread; - - // Read-write file descriptor for the named pipe which is waited on for - // changes and written to when a commit is made - FdHolder m_notify_fd; - // File descriptor for epoll - FdHolder m_epfd; - // The two ends of an anonymous pipe used to notify the kqueue() thread that - // it should be shut down. - FdHolder m_shutdown_read_fd; - FdHolder m_shutdown_write_fd; -}; - -} // namespace _impl -} // namespace realm - diff --git a/src/object-store/src/impl/android/weak_realm_notifier.cpp b/src/object-store/src/impl/android/weak_realm_notifier.cpp deleted file mode 100644 index 9722751c..00000000 --- a/src/object-store/src/impl/android/weak_realm_notifier.cpp +++ /dev/null @@ -1,143 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "impl/weak_realm_notifier.hpp" -#include "shared_realm.hpp" - -#include -#include -#include -#include -#include - -#define LOGE(fmt...) do { \ - fprintf(stderr, fmt); \ - __android_log_print(ANDROID_LOG_ERROR, "REALM", fmt); \ -} while (0) - -namespace realm { -namespace _impl { - -WeakRealmNotifier::WeakRealmNotifier(const std::shared_ptr& realm, bool cache) -: WeakRealmNotifierBase(realm, cache) -, m_thread_has_looper(false) -{ - ALooper* looper = ALooper_forThread(); - if (!looper) { - return; - } - - int message_pipe[2]; - if (pipe2(message_pipe, O_CLOEXEC | O_NONBLOCK)) { - LOGE("could not create WeakRealmNotifier ALooper message pipe: %s", strerror(errno)); - return; - } - - if (ALooper_addFd(looper, message_pipe[0], 3 /* LOOPER_ID_USER */, ALOOPER_EVENT_INPUT | ALOOPER_EVENT_HANGUP, &looper_callback, nullptr) != 1) { - LOGE("Error adding WeakRealmNotifier callback to looper."); - ::close(message_pipe[0]); - ::close(message_pipe[1]); - - return; - } - - m_message_pipe.read = message_pipe[0]; - m_message_pipe.write = message_pipe[1]; - m_thread_has_looper = true; -} - -WeakRealmNotifier::WeakRealmNotifier(WeakRealmNotifier&& rgt) -: WeakRealmNotifierBase(std::move(rgt)) -, m_message_pipe(std::move(rgt.m_message_pipe)) -{ - bool flag = true; - m_thread_has_looper = rgt.m_thread_has_looper.compare_exchange_strong(flag, false); -} - -WeakRealmNotifier& WeakRealmNotifier::operator=(WeakRealmNotifier&& rgt) -{ - close(); - - WeakRealmNotifierBase::operator=(std::move(rgt)); - m_message_pipe = std::move(rgt.m_message_pipe); - - bool flag = true; - m_thread_has_looper = rgt.m_thread_has_looper.compare_exchange_strong(flag, false); - - return *this; -} - -void WeakRealmNotifier::close() -{ - bool flag = true; - if (m_thread_has_looper.compare_exchange_strong(flag, false)) { - // closing one end of the pipe here will trigger ALOOPER_EVENT_HANGUP in the callback - // which will do the rest of the cleanup - ::close(m_message_pipe.write); - } -} - -void WeakRealmNotifier::notify() -{ - if (m_thread_has_looper && !expired()) { - - // we need to pass the weak Realm pointer to the other thread. - // to do so we allocate a weak pointer on the heap and send its address over a pipe. - // the other end of the pipe is read by the realm thread. when it's done with the pointer, it deletes it. - auto realm_ptr = new std::weak_ptr(realm()); - if (write(m_message_pipe.write, &realm_ptr, sizeof(realm_ptr)) != sizeof(realm_ptr)) { - delete realm_ptr; - LOGE("Buffer overrun when writing to WeakRealmNotifier's ALooper message pipe."); - } - } -} - -int WeakRealmNotifier::looper_callback(int fd, int events, void* data) -{ - if ((events & ALOOPER_EVENT_INPUT) != 0) { - // this is a pointer to a heap-allocated weak Realm pointer created by the notifiying thread. - // the actual address to the pointer is communicated over a pipe. - // we have to delete it so as to not leak, using the same memory allocation facilities it was allocated with. - std::weak_ptr* realm_ptr = nullptr; - while (read(fd, &realm_ptr, sizeof(realm_ptr)) == sizeof(realm_ptr)) { - if (auto realm = realm_ptr->lock()) { - if (!realm->is_closed()) { - realm->notify(); - } - } - - delete realm_ptr; - } - } - - if ((events & ALOOPER_EVENT_HANGUP) != 0) { - // this callback is always invoked on the looper thread so it's fine to get the looper like this - ALooper_removeFd(ALooper_forThread(), fd); - ::close(fd); - } - - if ((events & ALOOPER_EVENT_ERROR) != 0) { - LOGE("Unexpected error on WeakRealmNotifier's ALooper message pipe."); - } - - // return 1 to continue receiving events - return 1; -} - -} -} diff --git a/src/object-store/src/impl/android/weak_realm_notifier.hpp b/src/object-store/src/impl/android/weak_realm_notifier.hpp deleted file mode 100644 index ebb30da6..00000000 --- a/src/object-store/src/impl/android/weak_realm_notifier.hpp +++ /dev/null @@ -1,58 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "impl/weak_realm_notifier_base.hpp" - -#include - -namespace realm { -class Realm; - -namespace _impl { - -class WeakRealmNotifier : public WeakRealmNotifierBase { -public: - WeakRealmNotifier(const std::shared_ptr& realm, bool cache); - ~WeakRealmNotifier() { close(); } - - WeakRealmNotifier(WeakRealmNotifier&&); - WeakRealmNotifier& operator=(WeakRealmNotifier&&); - - WeakRealmNotifier(const WeakRealmNotifier&) = delete; - WeakRealmNotifier& operator=(const WeakRealmNotifier&) = delete; - - // Asyncronously call notify() on the Realm on the appropriate thread - void notify(); - -private: - void close(); - - static int looper_callback(int fd, int events, void* data); - - std::atomic m_thread_has_looper; - - // pipe file descriptor pair we use to signal ALooper - struct { - int read = -1; - int write = -1; - } m_message_pipe; -}; - -} // namespace _impl -} // namespace realm - diff --git a/src/object-store/src/impl/apple/external_commit_helper.cpp b/src/object-store/src/impl/apple/external_commit_helper.cpp deleted file mode 100644 index 5195a3e3..00000000 --- a/src/object-store/src/impl/apple/external_commit_helper.cpp +++ /dev/null @@ -1,226 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "impl/external_commit_helper.hpp" - -#include "impl/realm_coordinator.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace realm; -using namespace realm::_impl; - -namespace { -// Write a byte to a pipe to notify anyone waiting for data on the pipe -void notify_fd(int fd, int read_fd) -{ - while (true) { - char c = 0; - ssize_t ret = write(fd, &c, 1); - if (ret == 1) { - break; - } - - // If the pipe's buffer is full, we need to read some of the old data in - // it to make space. We don't just read in the code waiting for - // notifications so that we can notify multiple waiters with a single - // write. - assert(ret == -1 && errno == EAGAIN); - char buff[1024]; - read(read_fd, buff, sizeof buff); - } -} -} // anonymous namespace - -void ExternalCommitHelper::FdHolder::close() -{ - if (m_fd != -1) { - ::close(m_fd); - } - m_fd = -1; -} - -// Inter-thread and inter-process notifications of changes are done using a -// named pipe in the filesystem next to the Realm file. Everyone who wants to be -// notified of commits waits for data to become available on the pipe, and anyone -// who commits a write transaction writes data to the pipe after releasing the -// write lock. Note that no one ever actually *reads* from the pipe: the data -// actually written is meaningless, and trying to read from a pipe from multiple -// processes at once is fraught with race conditions. - -// When a RLMRealm instance is created, we add a CFRunLoopSource to the current -// thread's runloop. On each cycle of the run loop, the run loop checks each of -// its sources for work to do, which in the case of CFRunLoopSource is just -// checking if CFRunLoopSourceSignal has been called since the last time it ran, -// and if so invokes the function pointer supplied when the source is created, -// which in our case just invokes `[realm handleExternalChange]`. - -// Listening for external changes is done using kqueue() on a background thread. -// kqueue() lets us efficiently wait until the amount of data which can be read -// from one or more file descriptors has changed, and tells us which of the file -// descriptors it was that changed. We use this to wait on both the shared named -// pipe, and a local anonymous pipe. When data is written to the named pipe, we -// signal the runloop source and wake up the target runloop, and when data is -// written to the anonymous pipe the background thread removes the runloop -// source from the runloop and and shuts down. -ExternalCommitHelper::ExternalCommitHelper(RealmCoordinator& parent) -: m_parent(parent) -{ - m_kq = kqueue(); - if (m_kq == -1) { - throw std::system_error(errno, std::system_category()); - } - -#if !TARGET_OS_TV - auto path = parent.get_path() + ".note"; - - // Create and open the named pipe - int ret = mkfifo(path.c_str(), 0600); - if (ret == -1) { - int err = errno; - if (err == ENOTSUP) { - // Filesystem doesn't support named pipes, so try putting it in tmp instead - // Hash collisions are okay here because they just result in doing - // extra work, as opposed to correctness problems - std::ostringstream ss; - ss << getenv("TMPDIR"); - ss << "realm_" << std::hash()(path) << ".note"; - path = ss.str(); - ret = mkfifo(path.c_str(), 0600); - err = errno; - } - // the fifo already existing isn't an error - if (ret == -1 && err != EEXIST) { - throw std::system_error(err, std::system_category()); - } - } - - m_notify_fd = open(path.c_str(), O_RDWR); - if (m_notify_fd == -1) { - throw std::system_error(errno, std::system_category()); - } - - // Make writing to the pipe return -1 when the pipe's buffer is full - // rather than blocking until there's space available - ret = fcntl(m_notify_fd, F_SETFL, O_NONBLOCK); - if (ret == -1) { - throw std::system_error(errno, std::system_category()); - } - -#else // !TARGET_OS_TV - - // tvOS does not support named pipes, so use an anonymous pipe instead - int notification_pipe[2]; - int ret = pipe(notification_pipe); - if (ret == -1) { - throw std::system_error(errno, std::system_category()); - } - - m_notify_fd = notification_pipe[0]; - m_notify_fd_write = notification_pipe[1]; - -#endif // TARGET_OS_TV - - // Create the anonymous pipe for shutdown notifications - int shutdown_pipe[2]; - ret = pipe(shutdown_pipe); - if (ret == -1) { - throw std::system_error(errno, std::system_category()); - } - - m_shutdown_read_fd = shutdown_pipe[0]; - m_shutdown_write_fd = shutdown_pipe[1]; - - m_thread = std::async(std::launch::async, [=] { - try { - listen(); - } - catch (std::exception const& e) { - fprintf(stderr, "uncaught exception in notifier thread: %s: %s\n", typeid(e).name(), e.what()); - asl_log(nullptr, nullptr, ASL_LEVEL_ERR, "uncaught exception in notifier thread: %s: %s", typeid(e).name(), e.what()); - throw; - } - catch (...) { - fprintf(stderr, "uncaught exception in notifier thread\n"); - asl_log(nullptr, nullptr, ASL_LEVEL_ERR, "uncaught exception in notifier thread"); - throw; - } - }); -} - -ExternalCommitHelper::~ExternalCommitHelper() -{ - notify_fd(m_shutdown_write_fd, m_shutdown_read_fd); - m_thread.wait(); // Wait for the thread to exit -} - -void ExternalCommitHelper::listen() -{ - pthread_setname_np("RLMRealm notification listener"); - - // Set up the kqueue - // EVFILT_READ indicates that we care about data being available to read - // on the given file descriptor. - // EV_CLEAR makes it wait for the amount of data available to be read to - // change rather than just returning when there is any data to read. - struct kevent ke[2]; - EV_SET(&ke[0], m_notify_fd, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, 0); - EV_SET(&ke[1], m_shutdown_read_fd, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, 0); - int ret = kevent(m_kq, ke, 2, nullptr, 0, nullptr); - assert(ret == 0); - - while (true) { - struct kevent event; - // Wait for data to become on either fd - // Return code is number of bytes available or -1 on error - ret = kevent(m_kq, nullptr, 0, &event, 1, nullptr); - assert(ret >= 0); - if (ret == 0) { - // Spurious wakeup; just wait again - continue; - } - - // Check which file descriptor had activity: if it's the shutdown - // pipe, then someone called -stop; otherwise it's the named pipe - // and someone committed a write transaction - if (event.ident == (uint32_t)m_shutdown_read_fd) { - return; - } - assert(event.ident == (uint32_t)m_notify_fd); - - m_parent.on_change(); - } -} - -void ExternalCommitHelper::notify_others() -{ - if (m_notify_fd_write != -1) { - notify_fd(m_notify_fd_write, m_notify_fd); - } - else { - notify_fd(m_notify_fd, m_notify_fd); - } -} diff --git a/src/object-store/src/impl/apple/external_commit_helper.hpp b/src/object-store/src/impl/apple/external_commit_helper.hpp deleted file mode 100644 index c87d8b24..00000000 --- a/src/object-store/src/impl/apple/external_commit_helper.hpp +++ /dev/null @@ -1,80 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include - -namespace realm { -class Realm; - -namespace _impl { -class RealmCoordinator; - -class ExternalCommitHelper { -public: - ExternalCommitHelper(RealmCoordinator& parent); - ~ExternalCommitHelper(); - - void notify_others(); - -private: - // A RAII holder for a file descriptor which automatically closes the wrapped - // fd when it's deallocated - class FdHolder { - public: - FdHolder() = default; - ~FdHolder() { close(); } - operator int() const { return m_fd; } - - FdHolder& operator=(int newFd) { - close(); - m_fd = newFd; - return *this; - } - - private: - int m_fd = -1; - void close(); - - FdHolder& operator=(FdHolder const&) = delete; - FdHolder(FdHolder const&) = delete; - }; - - void listen(); - - RealmCoordinator& m_parent; - - // The listener thread - std::future m_thread; - - // Pipe which is waited on for changes and written to when there is a new - // commit to notify others of. When using a named pipe m_notify_fd is - // read-write and m_notify_fd_write is unused; when using an anonymous pipe - // (on tvOS) m_notify_fd is read-only and m_notify_fd_write is write-only. - FdHolder m_notify_fd; - FdHolder m_notify_fd_write; - - // File descriptor for the kqueue - FdHolder m_kq; - - // The two ends of an anonymous pipe used to notify the kqueue() thread that - // it should be shut down. - FdHolder m_shutdown_read_fd; - FdHolder m_shutdown_write_fd; -}; -} // namespace _impl -} // namespace realm diff --git a/src/object-store/src/impl/apple/weak_realm_notifier.cpp b/src/object-store/src/impl/apple/weak_realm_notifier.cpp deleted file mode 100644 index 930c3fe7..00000000 --- a/src/object-store/src/impl/apple/weak_realm_notifier.cpp +++ /dev/null @@ -1,103 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "impl/weak_realm_notifier.hpp" - -#include "shared_realm.hpp" - -#include - -using namespace realm; -using namespace realm::_impl; - -WeakRealmNotifier::WeakRealmNotifier(const std::shared_ptr& realm, bool cache) -: WeakRealmNotifierBase(realm, cache) -{ - struct RefCountedWeakPointer { - std::weak_ptr realm; - std::atomic ref_count; - }; - - CFRunLoopSourceContext ctx{}; - ctx.info = new RefCountedWeakPointer{realm, {0}}; - ctx.perform = [](void* info) { - if (auto realm = static_cast(info)->realm.lock()) { - realm->notify(); - } - }; - ctx.retain = [](const void* info) { - static_cast(const_cast(info))->ref_count.fetch_add(1, std::memory_order_relaxed); - return info; - }; - ctx.release = [](const void* info) { - auto ptr = static_cast(const_cast(info)); - if (ptr->ref_count.fetch_add(-1, std::memory_order_acq_rel) == 1) { - delete ptr; - } - }; - - m_runloop = CFRunLoopGetCurrent(); - CFRetain(m_runloop); - m_signal = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &ctx); - CFRunLoopAddSource(m_runloop, m_signal, kCFRunLoopDefaultMode); -} - -WeakRealmNotifier::WeakRealmNotifier(WeakRealmNotifier&& rgt) -: WeakRealmNotifierBase(std::move(rgt)) -, m_runloop(rgt.m_runloop) -, m_signal(rgt.m_signal) -{ - rgt.m_runloop = nullptr; - rgt.m_signal = nullptr; -} - -WeakRealmNotifier& WeakRealmNotifier::operator=(WeakRealmNotifier&& rgt) -{ - WeakRealmNotifierBase::operator=(std::move(rgt)); - - invalidate(); - m_runloop = rgt.m_runloop; - m_signal = rgt.m_signal; - rgt.m_runloop = nullptr; - rgt.m_signal = nullptr; - - return *this; -} - -WeakRealmNotifier::~WeakRealmNotifier() -{ - invalidate(); -} - -void WeakRealmNotifier::invalidate() -{ - if (m_signal) { - CFRunLoopSourceInvalidate(m_signal); - CFRelease(m_signal); - CFRelease(m_runloop); - } -} - -void WeakRealmNotifier::notify() -{ - CFRunLoopSourceSignal(m_signal); - // Signalling the source makes it run the next time the runloop gets - // to it, but doesn't make the runloop start if it's currently idle - // waiting for events - CFRunLoopWakeUp(m_runloop); -} diff --git a/src/object-store/src/impl/apple/weak_realm_notifier.hpp b/src/object-store/src/impl/apple/weak_realm_notifier.hpp deleted file mode 100644 index be940dcb..00000000 --- a/src/object-store/src/impl/apple/weak_realm_notifier.hpp +++ /dev/null @@ -1,50 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "impl/weak_realm_notifier_base.hpp" - -#include - -namespace realm { -class Realm; - -namespace _impl { - -class WeakRealmNotifier : public WeakRealmNotifierBase { -public: - WeakRealmNotifier(const std::shared_ptr& realm, bool cache); - ~WeakRealmNotifier(); - - WeakRealmNotifier(WeakRealmNotifier&&); - WeakRealmNotifier& operator=(WeakRealmNotifier&&); - - WeakRealmNotifier(const WeakRealmNotifier&) = delete; - WeakRealmNotifier& operator=(const WeakRealmNotifier&) = delete; - - // Asynchronously call notify() on the Realm on the appropriate thread - void notify(); - -private: - void invalidate(); - - CFRunLoopRef m_runloop; - CFRunLoopSourceRef m_signal; -}; - -} // namespace _impl -} // namespace realm diff --git a/src/object-store/src/impl/collection_change_builder.cpp b/src/object-store/src/impl/collection_change_builder.cpp deleted file mode 100644 index c413f55a..00000000 --- a/src/object-store/src/impl/collection_change_builder.cpp +++ /dev/null @@ -1,662 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "impl/collection_change_builder.hpp" - -#include -#include - -#include - -using namespace realm; -using namespace realm::_impl; - -CollectionChangeBuilder::CollectionChangeBuilder(IndexSet deletions, - IndexSet insertions, - IndexSet modifications, - std::vector moves) -: CollectionChangeSet({std::move(deletions), std::move(insertions), std::move(modifications), std::move(moves)}) -{ - for (auto&& move : this->moves) { - this->deletions.add(move.from); - this->insertions.add(move.to); - } -} - -void CollectionChangeBuilder::merge(CollectionChangeBuilder&& c) -{ - if (c.empty()) - return; - if (empty()) { - *this = std::move(c); - return; - } - - verify(); - c.verify(); - - // First update any old moves - if (!c.moves.empty() || !c.deletions.empty() || !c.insertions.empty()) { - auto it = std::remove_if(begin(moves), end(moves), [&](auto& old) { - // Check if the moved row was moved again, and if so just update the destination - auto it = find_if(begin(c.moves), end(c.moves), [&](auto const& m) { - return old.to == m.from; - }); - if (it != c.moves.end()) { - if (modifications.contains(it->from)) - c.modifications.add(it->to); - old.to = it->to; - *it = c.moves.back(); - c.moves.pop_back(); - ++it; - return false; - } - - // Check if the destination was deleted - // Removing the insert for this move will happen later - if (c.deletions.contains(old.to)) - return true; - - // Update the destination to adjust for any new insertions and deletions - old.to = c.insertions.shift(c.deletions.unshift(old.to)); - return false; - }); - moves.erase(it, end(moves)); - } - - // Ignore new moves of rows which were previously inserted (the implicit - // delete from the move will remove the insert) - if (!insertions.empty() && !c.moves.empty()) { - c.moves.erase(std::remove_if(begin(c.moves), end(c.moves), - [&](auto const& m) { return insertions.contains(m.from); }), - end(c.moves)); - } - - // Ensure that any previously modified rows which were moved are still modified - if (!modifications.empty() && !c.moves.empty()) { - for (auto const& move : c.moves) { - if (modifications.contains(move.from)) - c.modifications.add(move.to); - } - } - - // Update the source position of new moves to compensate for the changes made - // in the old changeset - if (!deletions.empty() || !insertions.empty()) { - for (auto& move : c.moves) - move.from = deletions.shift(insertions.unshift(move.from)); - } - - moves.insert(end(moves), begin(c.moves), end(c.moves)); - - // New deletion indices have been shifted by the insertions, so unshift them - // before adding - deletions.add_shifted_by(insertions, c.deletions); - - // Drop any inserted-then-deleted rows, then merge in new insertions - insertions.erase_at(c.deletions); - insertions.insert_at(c.insertions); - - clean_up_stale_moves(); - - modifications.erase_at(c.deletions); - modifications.shift_for_insert_at(c.insertions); - modifications.add(c.modifications); - - c = {}; - verify(); -} - -void CollectionChangeBuilder::clean_up_stale_moves() -{ - // Look for moves which are now no-ops, and remove them plus the associated - // insert+delete. Note that this isn't just checking for from == to due to - // that rows can also be shifted by other inserts and deletes - moves.erase(std::remove_if(begin(moves), end(moves), [&](auto const& move) { - if (move.from - deletions.count(0, move.from) != move.to - insertions.count(0, move.to)) - return false; - deletions.remove(move.from); - insertions.remove(move.to); - return true; - }), end(moves)); -} - -void CollectionChangeBuilder::parse_complete() -{ - moves.reserve(m_move_mapping.size()); - for (auto move : m_move_mapping) { - REALM_ASSERT_DEBUG(deletions.contains(move.second)); - REALM_ASSERT_DEBUG(insertions.contains(move.first)); - moves.push_back({move.second, move.first}); - } - m_move_mapping.clear(); - std::sort(begin(moves), end(moves), - [](auto const& a, auto const& b) { return a.from < b.from; }); -} - -void CollectionChangeBuilder::modify(size_t ndx) -{ - modifications.add(ndx); -} - -void CollectionChangeBuilder::insert(size_t index, size_t count, bool track_moves) -{ - modifications.shift_for_insert_at(index, count); - if (!track_moves) - return; - - insertions.insert_at(index, count); - - for (auto& move : moves) { - if (move.to >= index) - ++move.to; - } -} - -void CollectionChangeBuilder::erase(size_t index) -{ - modifications.erase_at(index); - size_t unshifted = insertions.erase_or_unshift(index); - if (unshifted != IndexSet::npos) - deletions.add_shifted(unshifted); - - for (size_t i = 0; i < moves.size(); ++i) { - auto& move = moves[i]; - if (move.to == index) { - moves.erase(moves.begin() + i); - --i; - } - else if (move.to > index) - --move.to; - } -} - -void CollectionChangeBuilder::clear(size_t old_size) -{ - if (old_size != std::numeric_limits::max()) { - for (auto range : deletions) - old_size += range.second - range.first; - for (auto range : insertions) - old_size -= range.second - range.first; - } - - modifications.clear(); - insertions.clear(); - moves.clear(); - m_move_mapping.clear(); - deletions.set(old_size); -} - -void CollectionChangeBuilder::move(size_t from, size_t to) -{ - REALM_ASSERT(from != to); - - bool updated_existing_move = false; - for (auto& move : moves) { - if (move.to != from) { - // Shift other moves if this row is moving from one side of them - // to the other - if (move.to >= to && move.to < from) - ++move.to; - else if (move.to <= to && move.to > from) - --move.to; - continue; - } - REALM_ASSERT(!updated_existing_move); - - // Collapse A -> B, B -> C into a single A -> C move - move.to = to; - updated_existing_move = true; - - insertions.erase_at(from); - insertions.insert_at(to); - } - - if (!updated_existing_move) { - auto shifted_from = insertions.erase_or_unshift(from); - insertions.insert_at(to); - - // Don't report deletions/moves for newly inserted rows - if (shifted_from != IndexSet::npos) { - shifted_from = deletions.add_shifted(shifted_from); - moves.push_back({shifted_from, to}); - } - } - - bool modified = modifications.contains(from); - modifications.erase_at(from); - - if (modified) - modifications.insert_at(to); - else - modifications.shift_for_insert_at(to); -} - -void CollectionChangeBuilder::move_over(size_t row_ndx, size_t last_row, bool track_moves) -{ - REALM_ASSERT(row_ndx <= last_row); - REALM_ASSERT(insertions.empty() || prev(insertions.end())->second - 1 <= last_row); - REALM_ASSERT(modifications.empty() || prev(modifications.end())->second - 1 <= last_row); - - if (row_ndx == last_row) { - if (track_moves) { - auto shifted_from = insertions.erase_or_unshift(row_ndx); - if (shifted_from != IndexSet::npos) - deletions.add_shifted(shifted_from); - m_move_mapping.erase(row_ndx); - } - modifications.remove(row_ndx); - return; - } - - bool modified = modifications.contains(last_row); - if (modified) { - modifications.remove(last_row); - modifications.add(row_ndx); - } - else - modifications.remove(row_ndx); - - if (!track_moves) - return; - - bool row_is_insertion = insertions.contains(row_ndx); - bool last_is_insertion = !insertions.empty() && prev(insertions.end())->second == last_row + 1; - REALM_ASSERT_DEBUG(insertions.empty() || prev(insertions.end())->second <= last_row + 1); - - // Collapse A -> B, B -> C into a single A -> C move - bool last_was_already_moved = false; - if (last_is_insertion) { - auto it = m_move_mapping.find(last_row); - if (it != m_move_mapping.end() && it->first == last_row) { - m_move_mapping[row_ndx] = it->second; - m_move_mapping.erase(it); - last_was_already_moved = true; - } - } - - // Remove moves to the row being deleted - if (row_is_insertion && !last_was_already_moved) { - auto it = m_move_mapping.find(row_ndx); - if (it != m_move_mapping.end() && it->first == row_ndx) - m_move_mapping.erase(it); - } - - // Don't report deletions/moves if last_row is newly inserted - if (last_is_insertion) { - insertions.remove(last_row); - } - // If it was previously moved, the unshifted source row has already been marked as deleted - else if (!last_was_already_moved) { - auto shifted_last_row = insertions.unshift(last_row); - shifted_last_row = deletions.add_shifted(shifted_last_row); - m_move_mapping[row_ndx] = shifted_last_row; - } - - // Don't mark the moved-over row as deleted if it was a new insertion - if (!row_is_insertion) { - deletions.add_shifted(insertions.unshift(row_ndx)); - insertions.add(row_ndx); - } - verify(); -} - -void CollectionChangeBuilder::verify() -{ -#ifdef REALM_DEBUG - for (auto&& move : moves) { - REALM_ASSERT(deletions.contains(move.from)); - REALM_ASSERT(insertions.contains(move.to)); - } -#endif -} - -namespace { -struct RowInfo { - size_t row_index; - size_t prev_tv_index; - size_t tv_index; - size_t shifted_tv_index; -}; - -void calculate_moves_unsorted(std::vector& new_rows, IndexSet& removed, CollectionChangeSet& changeset) -{ - size_t expected = 0; - for (auto& row : new_rows) { - // With unsorted queries rows only move due to move_last_over(), which - // inherently can only move a row to earlier in the table. - REALM_ASSERT(row.shifted_tv_index >= expected); - if (row.shifted_tv_index == expected) { - ++expected; - continue; - } - - // This row isn't just the row after the previous one, but it still may - // not be a move if there were rows deleted between the two, so next - // calcuate what row should be here taking those in to account - size_t calc_expected = row.tv_index - changeset.insertions.count(0, row.tv_index) + removed.count(0, row.prev_tv_index); - if (row.shifted_tv_index == calc_expected) { - expected = calc_expected + 1; - continue; - } - - // The row still isn't the expected one, so it's a move - changeset.moves.push_back({row.prev_tv_index, row.tv_index}); - changeset.insertions.add(row.tv_index); - removed.add(row.prev_tv_index); - } -} - -class LongestCommonSubsequenceCalculator { -public: - // A pair of an index in the table and an index in the table view - struct Row { - size_t row_index; - size_t tv_index; - }; - - struct Match { - // The index in `a` at which this match begins - size_t i; - // The index in `b` at which this match begins - size_t j; - // The length of this match - size_t size; - // The number of rows in this block which were modified - size_t modified; - }; - std::vector m_longest_matches; - - LongestCommonSubsequenceCalculator(std::vector& a, std::vector& b, - size_t start_index, - IndexSet const& modifications) - : m_modified(modifications) - , a(a), b(b) - { - find_longest_matches(start_index, a.size(), - start_index, b.size()); - m_longest_matches.push_back({a.size(), b.size(), 0}); - } - -private: - IndexSet const& m_modified; - - // The two arrays of rows being diffed - // a is sorted by tv_index, b is sorted by row_index - std::vector &a, &b; - - // Find the longest matching range in (a + begin1, a + end1) and (b + begin2, b + end2) - // "Matching" is defined as "has the same row index"; the TV index is just - // there to let us turn an index in a/b into an index which can be reported - // in the output changeset. - // - // This is done with the O(N) space variant of the dynamic programming - // algorithm for longest common subsequence, where N is the maximum number - // of the most common row index (which for everything but linkview-derived - // TVs will be 1). - Match find_longest_match(size_t begin1, size_t end1, size_t begin2, size_t end2) - { - struct Length { - size_t j, len; - }; - // The length of the matching block for each `j` for the previously checked row - std::vector prev; - // The length of the matching block for each `j` for the row currently being checked - std::vector cur; - - // Calculate the length of the matching block *ending* at b[j], which - // is 1 if b[j - 1] did not match, and b[j - 1] + 1 otherwise. - auto length = [&](size_t j) -> size_t { - for (auto const& pair : prev) { - if (pair.j + 1 == j) - return pair.len + 1; - } - return 1; - }; - - // Iterate over each `j` which has the same row index as a[i] and falls - // within the range begin2 <= j < end2 - auto for_each_b_match = [&](size_t i, auto&& f) { - size_t ai = a[i].row_index; - // Find the TV indicies at which this row appears in the new results - // There should always be at least one (or it would have been - // filtered out earlier), but there can be multiple if there are dupes - auto it = lower_bound(begin(b), end(b), ai, - [](auto lft, auto rgt) { return lft.row_index < rgt; }); - REALM_ASSERT(it != end(b) && it->row_index == ai); - for (; it != end(b) && it->row_index == ai; ++it) { - size_t j = it->tv_index; - if (j < begin2) - continue; - if (j >= end2) - break; // b is sorted by tv_index so this can't transition from false to true - f(j); - } - }; - - Match best = {begin1, begin2, 0, 0}; - for (size_t i = begin1; i < end1; ++i) { - // prev = std::move(cur), but avoids discarding prev's heap allocation - cur.swap(prev); - cur.clear(); - - for_each_b_match(i, [&](size_t j) { - size_t size = length(j); - - cur.push_back({j, size}); - - // If the matching block ending at a[i] and b[j] is longer than - // the previous one, select it as the best - if (size > best.size) - best = {i - size + 1, j - size + 1, size, IndexSet::npos}; - // Given two equal-length matches, prefer the one with fewer modified rows - else if (size == best.size) { - if (best.modified == IndexSet::npos) - best.modified = m_modified.count(best.j - size + 1, best.j + 1); - auto count = m_modified.count(j - size + 1, j + 1); - if (count < best.modified) - best = {i - size + 1, j - size + 1, size, count}; - } - - // The best block should always fall within the range being searched - REALM_ASSERT(best.i >= begin1 && best.i + best.size <= end1); - REALM_ASSERT(best.j >= begin2 && best.j + best.size <= end2); - }); - } - return best; - } - - void find_longest_matches(size_t begin1, size_t end1, size_t begin2, size_t end2) - { - // FIXME: recursion could get too deep here - // recursion depth worst case is currently O(N) and each recursion uses 320 bytes of stack - // could reduce worst case to O(sqrt(N)) (and typical case to O(log N)) - // biasing equal selections towards the middle, but that's still - // insufficient for Android's 8 KB stacks - auto m = find_longest_match(begin1, end1, begin2, end2); - if (!m.size) - return; - if (m.i > begin1 && m.j > begin2) - find_longest_matches(begin1, m.i, begin2, m.j); - m_longest_matches.push_back(m); - if (m.i + m.size < end2 && m.j + m.size < end2) - find_longest_matches(m.i + m.size, end1, m.j + m.size, end2); - } -}; - -void calculate_moves_sorted(std::vector& rows, CollectionChangeSet& changeset) -{ - // The RowInfo array contains information about the old and new TV indices of - // each row, which we need to turn into two sequences of rows, which we'll - // then find matches in - std::vector a, b; - - a.reserve(rows.size()); - for (auto& row : rows) { - a.push_back({row.row_index, row.prev_tv_index}); - } - std::sort(begin(a), end(a), [](auto lft, auto rgt) { - return std::tie(lft.tv_index, lft.row_index) < std::tie(rgt.tv_index, rgt.row_index); - }); - - // Before constructing `b`, first find the first index in `a` which will - // actually differ in `b`, and skip everything else if there aren't any - size_t first_difference = IndexSet::npos; - for (size_t i = 0; i < a.size(); ++i) { - if (a[i].row_index != rows[i].row_index) { - first_difference = i; - break; - } - } - if (first_difference == IndexSet::npos) - return; - - // Note that `b` is sorted by row_index, while `a` is sorted by tv_index - b.reserve(rows.size()); - for (size_t i = 0; i < rows.size(); ++i) - b.push_back({rows[i].row_index, i}); - std::sort(begin(b), end(b), [](auto lft, auto rgt) { - return std::tie(lft.row_index, lft.tv_index) < std::tie(rgt.row_index, rgt.tv_index); - }); - - // Calculate the LCS of the two sequences - auto matches = LongestCommonSubsequenceCalculator(a, b, first_difference, - changeset.modifications).m_longest_matches; - - // And then insert and delete rows as needed to align them - size_t i = first_difference, j = first_difference; - for (auto match : matches) { - for (; i < match.i; ++i) - changeset.deletions.add(a[i].tv_index); - for (; j < match.j; ++j) - changeset.insertions.add(rows[j].tv_index); - i += match.size; - j += match.size; - } -} - -} // Anonymous namespace - -CollectionChangeBuilder CollectionChangeBuilder::calculate(std::vector const& prev_rows, - std::vector const& next_rows, - std::function row_did_change, - bool rows_are_in_table_order) -{ - REALM_ASSERT_DEBUG(!rows_are_in_table_order || std::is_sorted(begin(next_rows), end(next_rows))); - - CollectionChangeBuilder ret; - - size_t deleted = 0; - std::vector old_rows; - old_rows.reserve(prev_rows.size()); - for (size_t i = 0; i < prev_rows.size(); ++i) { - if (prev_rows[i] == IndexSet::npos) { - ++deleted; - ret.deletions.add(i); - } - else - old_rows.push_back({prev_rows[i], IndexSet::npos, i, i - deleted}); - } - std::sort(begin(old_rows), end(old_rows), [](auto& lft, auto& rgt) { - return lft.row_index < rgt.row_index; - }); - - std::vector new_rows; - new_rows.reserve(next_rows.size()); - for (size_t i = 0; i < next_rows.size(); ++i) { - new_rows.push_back({next_rows[i], IndexSet::npos, i, 0}); - } - std::sort(begin(new_rows), end(new_rows), [](auto& lft, auto& rgt) { - return lft.row_index < rgt.row_index; - }); - - // Don't add rows which were modified to not match the query to `deletions` - // immediately because the unsorted move logic needs to be able to - // distinguish them from rows which were outright deleted - IndexSet removed; - - // Now that our old and new sets of rows are sorted by row index, we can - // iterate over them and either record old+new TV indices for rows present - // in both, or mark them as inserted/deleted if they appear only in one - size_t i = 0, j = 0; - while (i < old_rows.size() && j < new_rows.size()) { - auto old_index = old_rows[i]; - auto new_index = new_rows[j]; - if (old_index.row_index == new_index.row_index) { - new_rows[j].prev_tv_index = old_rows[i].tv_index; - new_rows[j].shifted_tv_index = old_rows[i].shifted_tv_index; - ++i; - ++j; - } - else if (old_index.row_index < new_index.row_index) { - removed.add(old_index.tv_index); - ++i; - } - else { - ret.insertions.add(new_index.tv_index); - ++j; - } - } - - for (; i < old_rows.size(); ++i) - removed.add(old_rows[i].tv_index); - for (; j < new_rows.size(); ++j) - ret.insertions.add(new_rows[j].tv_index); - - // Filter out the new insertions since we don't need them for any of the - // further calculations - new_rows.erase(std::remove_if(begin(new_rows), end(new_rows), - [](auto& row) { return row.prev_tv_index == IndexSet::npos; }), - end(new_rows)); - std::sort(begin(new_rows), end(new_rows), - [](auto& lft, auto& rgt) { return lft.tv_index < rgt.tv_index; }); - - for (auto& row : new_rows) { - if (row_did_change(row.row_index)) { - ret.modifications.add(row.tv_index); - } - } - - if (!rows_are_in_table_order) { - calculate_moves_sorted(new_rows, ret); - } - else { - calculate_moves_unsorted(new_rows, removed, ret); - } - ret.deletions.add(removed); - ret.verify(); - -#ifdef REALM_DEBUG - { // Verify that applying the calculated change to prev_rows actually produces next_rows - auto rows = prev_rows; - auto it = util::make_reverse_iterator(ret.deletions.end()); - auto end = util::make_reverse_iterator(ret.deletions.begin()); - for (; it != end; ++it) { - rows.erase(rows.begin() + it->first, rows.begin() + it->second); - } - - for (auto i : ret.insertions.as_indexes()) { - rows.insert(rows.begin() + i, next_rows[i]); - } - - REALM_ASSERT(rows == next_rows); - } -#endif - - return ret; -} diff --git a/src/object-store/src/impl/collection_change_builder.hpp b/src/object-store/src/impl/collection_change_builder.hpp deleted file mode 100644 index 6e9f78c1..00000000 --- a/src/object-store/src/impl/collection_change_builder.hpp +++ /dev/null @@ -1,67 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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_COLLECTION_CHANGE_BUILDER_HPP -#define REALM_COLLECTION_CHANGE_BUILDER_HPP - -#include "collection_notifications.hpp" - -#include - -namespace realm { -namespace _impl { -class CollectionChangeBuilder : public CollectionChangeSet { -public: - CollectionChangeBuilder(CollectionChangeBuilder const&) = default; - CollectionChangeBuilder(CollectionChangeBuilder&&) = default; - CollectionChangeBuilder& operator=(CollectionChangeBuilder const&) = default; - CollectionChangeBuilder& operator=(CollectionChangeBuilder&&) = default; - - CollectionChangeBuilder(IndexSet deletions = {}, - IndexSet insertions = {}, - IndexSet modification = {}, - std::vector moves = {}); - - // Calculate where rows need to be inserted or deleted from old_rows to turn - // it into new_rows, and check all matching rows for modifications - static CollectionChangeBuilder calculate(std::vector const& old_rows, - std::vector const& new_rows, - std::function row_did_change, - bool sort); - - void merge(CollectionChangeBuilder&&); - void clean_up_stale_moves(); - - void insert(size_t ndx, size_t count=1, bool track_moves=true); - void modify(size_t ndx); - void erase(size_t ndx); - void move_over(size_t ndx, size_t last_ndx, bool track_moves=true); - void clear(size_t old_size); - void move(size_t from, size_t to); - - void parse_complete(); - -private: - std::unordered_map m_move_mapping; - - void verify(); -}; -} // namespace _impl -} // namespace realm - -#endif // REALM_COLLECTION_CHANGE_BUILDER_HPP diff --git a/src/object-store/src/impl/collection_notifier.cpp b/src/object-store/src/impl/collection_notifier.cpp deleted file mode 100644 index 7751c3ac..00000000 --- a/src/object-store/src/impl/collection_notifier.cpp +++ /dev/null @@ -1,344 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "impl/collection_notifier.hpp" - -#include "impl/realm_coordinator.hpp" -#include "shared_realm.hpp" - -#include - -using namespace realm; -using namespace realm::_impl; - -std::function -CollectionNotifier::get_modification_checker(TransactionChangeInfo const& info, - Table const& root_table) -{ - // First check if any of the tables accessible from the root table were - // actually modified. This can be false if there were only insertions, or - // deletions which were not linked to by any row in the linking table - auto table_modified = [&](auto& tbl) { - return tbl.table_ndx < info.tables.size() - && !info.tables[tbl.table_ndx].modifications.empty(); - }; - if (!any_of(begin(m_related_tables), end(m_related_tables), table_modified)) { - return [](size_t) { return false; }; - } - - return DeepChangeChecker(info, root_table, m_related_tables); -} - -void DeepChangeChecker::find_related_tables(std::vector& out, Table const& table) -{ - auto table_ndx = table.get_index_in_group(); - if (any_of(begin(out), end(out), [=](auto& tbl) { return tbl.table_ndx == table_ndx; })) - return; - - // We need to add this table to `out` before recurring so that the check - // above works, but we can't store a pointer to the thing being populated - // because the recursive calls may resize `out`, so instead look it up by - // index every time - size_t out_index = out.size(); - out.push_back({table_ndx, {}}); - - for (size_t i = 0, count = table.get_column_count(); i != count; ++i) { - auto type = table.get_column_type(i); - if (type == type_Link || type == type_LinkList) { - out[out_index].links.push_back({i, type == type_LinkList}); - find_related_tables(out, *table.get_link_target(i)); - } - } -} - -DeepChangeChecker::DeepChangeChecker(TransactionChangeInfo const& info, - Table const& root_table, - std::vector const& related_tables) -: m_info(info) -, m_root_table(root_table) -, m_root_table_ndx(root_table.get_index_in_group()) -, m_root_modifications(m_root_table_ndx < info.tables.size() ? &info.tables[m_root_table_ndx].modifications : nullptr) -, m_related_tables(related_tables) -{ -} - -bool DeepChangeChecker::check_outgoing_links(size_t table_ndx, - Table const& table, - size_t row_ndx, size_t depth) -{ - auto it = find_if(begin(m_related_tables), end(m_related_tables), - [&](auto&& tbl) { return tbl.table_ndx == table_ndx; }); - if (it == m_related_tables.end()) - return false; - - // Check if we're already checking if the destination of the link is - // modified, and if not add it to the stack - auto already_checking = [&](size_t col) { - for (auto p = m_current_path.begin(); p < m_current_path.begin() + depth; ++p) { - if (p->table == table_ndx && p->row == row_ndx && p->col == col) - return true; - } - m_current_path[depth] = {table_ndx, row_ndx, col, false}; - return false; - }; - - for (auto const& link : it->links) { - if (already_checking(link.col_ndx)) - continue; - if (!link.is_list) { - if (table.is_null_link(link.col_ndx, row_ndx)) - continue; - auto dst = table.get_link(link.col_ndx, row_ndx); - return check_row(*table.get_link_target(link.col_ndx), dst, depth + 1); - } - - auto& target = *table.get_link_target(link.col_ndx); - auto lvr = table.get_linklist(link.col_ndx, row_ndx); - for (size_t j = 0, size = lvr->size(); j < size; ++j) { - size_t dst = lvr->get(j).get_index(); - if (check_row(target, dst, depth + 1)) - return true; - } - } - - return false; -} - -bool DeepChangeChecker::check_row(Table const& table, size_t idx, size_t depth) -{ - // Arbitrary upper limit on the maximum depth to search - if (depth >= m_current_path.size()) { - // Don't mark any of the intermediate rows checked along the path as - // not modified, as a search starting from them might hit a modification - for (size_t i = 1; i < m_current_path.size(); ++i) - m_current_path[i].depth_exceeded = true; - return false; - } - - size_t table_ndx = table.get_index_in_group(); - if (depth > 0 && table_ndx < m_info.tables.size() && m_info.tables[table_ndx].modifications.contains(idx)) - return true; - - if (m_not_modified.size() <= table_ndx) - m_not_modified.resize(table_ndx + 1); - if (m_not_modified[table_ndx].contains(idx)) - return false; - - bool ret = check_outgoing_links(table_ndx, table, idx, depth); - if (!ret && !m_current_path[depth].depth_exceeded) - m_not_modified[table_ndx].add(idx); - return ret; -} - -bool DeepChangeChecker::operator()(size_t ndx) -{ - if (m_root_modifications && m_root_modifications->contains(ndx)) - return true; - return check_row(m_root_table, ndx, 0); -} - -CollectionNotifier::CollectionNotifier(std::shared_ptr realm) -: m_realm(std::move(realm)) -, m_sg_version(Realm::Internal::get_shared_group(*m_realm).get_version_of_current_transaction()) -{ -} - -CollectionNotifier::~CollectionNotifier() -{ - // Need to do this explicitly to ensure m_realm is destroyed with the mutex - // held to avoid potential double-deletion - unregister(); -} - -size_t CollectionNotifier::add_callback(CollectionChangeCallback callback) -{ - m_realm->verify_thread(); - - auto next_token = [=] { - size_t token = 0; - for (auto& callback : m_callbacks) { - if (token <= callback.token) { - token = callback.token + 1; - } - } - return token; - }; - - std::lock_guard lock(m_callback_mutex); - auto token = next_token(); - m_callbacks.push_back({std::move(callback), token, false}); - if (m_callback_index == npos) { // Don't need to wake up if we're already sending notifications - Realm::Internal::get_coordinator(*m_realm).send_commit_notifications(); - m_have_callbacks = true; - } - return token; -} - -void CollectionNotifier::remove_callback(size_t token) -{ - Callback old; - { - std::lock_guard lock(m_callback_mutex); - REALM_ASSERT(m_error || m_callbacks.size() > 0); - - auto it = find_if(begin(m_callbacks), end(m_callbacks), - [=](const auto& c) { return c.token == token; }); - // We should only fail to find the callback if it was removed due to an error - REALM_ASSERT(m_error || it != end(m_callbacks)); - if (it == end(m_callbacks)) { - return; - } - - size_t idx = distance(begin(m_callbacks), it); - if (m_callback_index != npos && m_callback_index >= idx) { - --m_callback_index; - } - - old = std::move(*it); - m_callbacks.erase(it); - - m_have_callbacks = !m_callbacks.empty(); - } -} - -void CollectionNotifier::unregister() noexcept -{ - std::lock_guard lock(m_realm_mutex); - m_realm = nullptr; -} - -bool CollectionNotifier::is_alive() const noexcept -{ - std::lock_guard lock(m_realm_mutex); - return m_realm != nullptr; -} - -std::unique_lock CollectionNotifier::lock_target() -{ - return std::unique_lock{m_realm_mutex}; -} - -void CollectionNotifier::set_table(Table const& table) -{ - m_related_tables.clear(); - DeepChangeChecker::find_related_tables(m_related_tables, table); -} - -void CollectionNotifier::add_required_change_info(TransactionChangeInfo& info) -{ - if (!do_add_required_change_info(info)) { - return; - } - - auto max = max_element(begin(m_related_tables), end(m_related_tables), - [](auto&& a, auto&& b) { return a.table_ndx < b.table_ndx; }); - - if (max->table_ndx >= info.table_modifications_needed.size()) - info.table_modifications_needed.resize(max->table_ndx + 1, false); - for (auto& tbl : m_related_tables) { - info.table_modifications_needed[tbl.table_ndx] = true; - } -} - -void CollectionNotifier::prepare_handover() -{ - REALM_ASSERT(m_sg); - m_sg_version = m_sg->get_version_of_current_transaction(); - do_prepare_handover(*m_sg); -} - -bool CollectionNotifier::deliver(Realm& realm, SharedGroup& sg, std::exception_ptr err) -{ - { - std::lock_guard lock(m_realm_mutex); - if (m_realm.get() != &realm) { - return false; - } - } - - if (err) { - m_error = err; - return have_callbacks(); - } - - auto realm_sg_version = sg.get_version_of_current_transaction(); - if (version() != realm_sg_version) { - // Realm version can be newer if a commit was made on our thread or the - // user manually called refresh(), or older if a commit was made on a - // different thread and we ran *really* fast in between the check for - // if the shared group has changed and when we pick up async results - return false; - } - - bool should_call_callbacks = do_deliver(sg); - m_changes_to_deliver = std::move(m_accumulated_changes); - - // fixup modifications to be source rows rather than dest rows - // FIXME: the actual change calculations should be updated to just calculate - // the correct thing instead - m_changes_to_deliver.modifications.erase_at(m_changes_to_deliver.insertions); - m_changes_to_deliver.modifications.shift_for_insert_at(m_changes_to_deliver.deletions); - - return should_call_callbacks && have_callbacks(); -} - -void CollectionNotifier::call_callbacks() -{ - while (auto fn = next_callback()) { - fn(m_changes_to_deliver, m_error); - } - - if (m_error) { - // Remove all the callbacks as we never need to call anything ever again - // after delivering an error - std::lock_guard callback_lock(m_callback_mutex); - m_callbacks.clear(); - } -} - -CollectionChangeCallback CollectionNotifier::next_callback() -{ - std::lock_guard callback_lock(m_callback_mutex); - - for (++m_callback_index; m_callback_index < m_callbacks.size(); ++m_callback_index) { - auto& callback = m_callbacks[m_callback_index]; - if (!m_error && callback.initial_delivered && m_changes_to_deliver.empty()) { - continue; - } - callback.initial_delivered = true; - return callback.fn; - } - - m_callback_index = npos; - return nullptr; -} - -void CollectionNotifier::attach_to(SharedGroup& sg) -{ - REALM_ASSERT(!m_sg); - - m_sg = &sg; - do_attach_to(sg); -} - -void CollectionNotifier::detach() -{ - REALM_ASSERT(m_sg); - do_detach_from(*m_sg); - m_sg = nullptr; -} diff --git a/src/object-store/src/impl/collection_notifier.hpp b/src/object-store/src/impl/collection_notifier.hpp deleted file mode 100644 index 34a133fa..00000000 --- a/src/object-store/src/impl/collection_notifier.hpp +++ /dev/null @@ -1,247 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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_BACKGROUND_COLLECTION_HPP -#define REALM_BACKGROUND_COLLECTION_HPP - -#include "impl/collection_change_builder.hpp" - -#include - -#include -#include -#include -#include -#include -#include - -namespace realm { -class Realm; - -namespace _impl { -struct ListChangeInfo { - size_t table_ndx; - size_t row_ndx; - size_t col_ndx; - CollectionChangeBuilder* changes; -}; - -struct TransactionChangeInfo { - std::vector table_modifications_needed; - std::vector table_moves_needed; - std::vector lists; - std::vector tables; -}; - -class DeepChangeChecker { -public: - struct OutgoingLink { - size_t col_ndx; - bool is_list; - }; - struct RelatedTable { - size_t table_ndx; - std::vector links; - }; - - DeepChangeChecker(TransactionChangeInfo const& info, Table const& root_table, - std::vector const& related_tables); - - bool operator()(size_t row_ndx); - - // Recursively add `table` and all tables it links to to `out`, along with - // information about the links from them - static void find_related_tables(std::vector& out, Table const& table); - -private: - TransactionChangeInfo const& m_info; - Table const& m_root_table; - const size_t m_root_table_ndx; - IndexSet const* const m_root_modifications; - std::vector m_not_modified; - std::vector const& m_related_tables; - - struct Path { - size_t table; - size_t row; - size_t col; - bool depth_exceeded; - }; - std::array m_current_path; - - bool check_row(Table const& table, size_t row_ndx, size_t depth = 0); - bool check_outgoing_links(size_t table_ndx, Table const& table, - size_t row_ndx, size_t depth = 0); -}; - -// A base class for a notifier that keeps a collection up to date and/or -// generates detailed change notifications on a background thread. This manages -// most of the lifetime-management issues related to sharing an object between -// the worker thread and the collection on the target thread, along with the -// thread-safe callback collection. -class CollectionNotifier { -public: - CollectionNotifier(std::shared_ptr); - virtual ~CollectionNotifier(); - - // ------------------------------------------------------------------------ - // Public API for the collections using this to get notifications: - - // Stop receiving notifications from this background worker - // This must be called in the destructor of the collection - void unregister() noexcept; - - // Add a callback to be called each time the collection changes - // This can only be called from the target collection's thread - // Returns a token which can be passed to remove_callback() - size_t add_callback(CollectionChangeCallback callback); - // Remove a previously added token. The token is no longer valid after - // calling this function and must not be used again. This function can be - // called from any thread. - void remove_callback(size_t token); - - // ------------------------------------------------------------------------ - // API for RealmCoordinator to manage running things and calling callbacks - - Realm* get_realm() const noexcept { return m_realm.get(); } - - // Get the SharedGroup version which this collection can attach to (if it's - // in handover mode), or can deliver to (if it's been handed over to the BG worker alredad) - SharedGroup::VersionID version() const noexcept { return m_sg_version; } - - // Release references to all core types - // This is called on the worker thread to ensure that non-thread-safe things - // can be destroyed on the correct thread, even if the last reference to the - // CollectionNotifier is released on a different thread - virtual void release_data() noexcept = 0; - - // Call each of the currently registered callbacks, if there have been any - // changes since the last time each of those callbacks was called - void call_callbacks(); - - bool is_alive() const noexcept; - - // Attach the handed-over query to `sg`. Must not be already attached to a SharedGroup. - void attach_to(SharedGroup& sg); - // Create a new query handover object and stop using the previously attached - // SharedGroup - void detach(); - - // Set `info` as the new ChangeInfo that will be populated by the next - // transaction advance, and register all required information in it - void add_required_change_info(TransactionChangeInfo& info); - - virtual void run() = 0; - void prepare_handover(); - bool deliver(Realm&, SharedGroup&, std::exception_ptr); - - template - class Handle; - -protected: - bool have_callbacks() const noexcept { return m_have_callbacks; } - void add_changes(CollectionChangeBuilder change) { m_accumulated_changes.merge(std::move(change)); } - void set_table(Table const& table); - std::unique_lock lock_target(); - - std::function get_modification_checker(TransactionChangeInfo const&, Table const&); - -private: - virtual void do_attach_to(SharedGroup&) = 0; - virtual void do_detach_from(SharedGroup&) = 0; - virtual void do_prepare_handover(SharedGroup&) = 0; - virtual bool do_deliver(SharedGroup&) { return true; } - virtual bool do_add_required_change_info(TransactionChangeInfo&) = 0; - - mutable std::mutex m_realm_mutex; - std::shared_ptr m_realm; - - SharedGroup::VersionID m_sg_version; - SharedGroup* m_sg = nullptr; - - std::exception_ptr m_error; - CollectionChangeBuilder m_accumulated_changes; - CollectionChangeSet m_changes_to_deliver; - - std::vector m_related_tables; - - struct Callback { - CollectionChangeCallback fn; - size_t token; - bool initial_delivered; - }; - - // Currently registered callbacks and a mutex which must always be held - // while doing anything with them or m_callback_index - std::mutex m_callback_mutex; - std::vector m_callbacks; - - // Cached value for if m_callbacks is empty, needed to avoid deadlocks in - // run() due to lock-order inversion between m_callback_mutex and m_target_mutex - // It's okay if this value is stale as at worst it'll result in us doing - // some extra work. - std::atomic m_have_callbacks = {false}; - - // Iteration variable for looping over callbacks - // remove_callback() updates this when needed - size_t m_callback_index = npos; - - CollectionChangeCallback next_callback(); -}; - -// A smart pointer to a CollectionNotifier that unregisters the notifier when -// the pointer is destroyed. Movable. Copying will produce a null Handle. -template -class CollectionNotifier::Handle : public std::shared_ptr { -public: - using std::shared_ptr::shared_ptr; - - Handle() = default; - ~Handle() { reset(); } - - // Copying a Handle produces a null Handle. - Handle(const Handle&) : Handle() { } - Handle& operator=(const Handle& other) - { - if (this != &other) { - reset(); - } - return *this; - } - - Handle(Handle&&) = default; - Handle& operator=(Handle&& other) - { - reset(); - std::shared_ptr::shared_ptr::operator=(std::move(other)); - return *this; - } - - void reset() - { - if (*this) { - this->get()->unregister(); - std::shared_ptr::reset(); - } - } -}; - -} // namespace _impl -} // namespace realm - -#endif /* REALM_BACKGROUND_COLLECTION_HPP */ diff --git a/src/object-store/src/impl/external_commit_helper.hpp b/src/object-store/src/impl/external_commit_helper.hpp deleted file mode 100644 index 920f978f..00000000 --- a/src/object-store/src/impl/external_commit_helper.hpp +++ /dev/null @@ -1,32 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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_EXTERNAL_COMMIT_HELPER_HPP -#define REALM_EXTERNAL_COMMIT_HELPER_HPP - -#include - -#if REALM_PLATFORM_APPLE -#include "impl/apple/external_commit_helper.hpp" -#elif REALM_ANDROID || REALM_PLATFORM_NODE -#include "impl/android/external_commit_helper.hpp" -#else -#include "impl/generic/external_commit_helper.hpp" -#endif - -#endif // REALM_EXTERNAL_COMMIT_HELPER_HPP diff --git a/src/object-store/src/impl/generic/external_commit_helper.cpp b/src/object-store/src/impl/generic/external_commit_helper.cpp deleted file mode 100644 index 5071304b..00000000 --- a/src/object-store/src/impl/generic/external_commit_helper.cpp +++ /dev/null @@ -1,49 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "impl/external_commit_helper.hpp" - -#include "impl/realm_coordinator.hpp" - -#include -#include - -using namespace realm; -using namespace realm::_impl; - -ExternalCommitHelper::ExternalCommitHelper(RealmCoordinator& parent) -: m_parent(parent) -, m_history(realm::make_client_history(parent.get_path(), parent.get_encryption_key().data())) -, m_sg(*m_history, parent.is_in_memory() ? SharedGroup::durability_MemOnly : SharedGroup::durability_Full, - parent.get_encryption_key().data()) -, m_thread(std::async(std::launch::async, [=] { - m_sg.begin_read(); - while (m_sg.wait_for_change()) { - m_sg.end_read(); - m_sg.begin_read(); - m_parent.on_change(); - } -})) -{ -} - -ExternalCommitHelper::~ExternalCommitHelper() -{ - m_sg.wait_for_change_release(); - m_thread.wait(); // Wait for the thread to exit -} diff --git a/src/object-store/src/impl/generic/external_commit_helper.hpp b/src/object-store/src/impl/generic/external_commit_helper.hpp deleted file mode 100644 index 8bfe43f2..00000000 --- a/src/object-store/src/impl/generic/external_commit_helper.hpp +++ /dev/null @@ -1,50 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include - -#include - -namespace realm { -class Replication; - -namespace _impl { -class RealmCoordinator; - -class ExternalCommitHelper { -public: - ExternalCommitHelper(RealmCoordinator& parent); - ~ExternalCommitHelper(); - - // A no-op in this version, but needed for the Apple version - void notify_others() { } - -private: - RealmCoordinator& m_parent; - - // A shared group used to listen for changes - std::unique_ptr m_history; - SharedGroup m_sg; - - // The listener thread - std::future m_thread; -}; - -} // namespace _impl -} // namespace realm - diff --git a/src/object-store/src/impl/generic/weak_realm_notifier.hpp b/src/object-store/src/impl/generic/weak_realm_notifier.hpp deleted file mode 100644 index 219551eb..00000000 --- a/src/object-store/src/impl/generic/weak_realm_notifier.hpp +++ /dev/null @@ -1,36 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "impl/weak_realm_notifier_base.hpp" - -namespace realm { -class Realm; - -namespace _impl { - -class WeakRealmNotifier : public WeakRealmNotifierBase { -public: - using WeakRealmNotifierBase::WeakRealmNotifierBase; - - // Do nothing, as this can't be implemented portably - void notify() { } -}; - -} // namespace _impl -} // namespace realm - diff --git a/src/object-store/src/impl/list_notifier.cpp b/src/object-store/src/impl/list_notifier.cpp deleted file mode 100644 index 64bf6daf..00000000 --- a/src/object-store/src/impl/list_notifier.cpp +++ /dev/null @@ -1,122 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "impl/list_notifier.hpp" - -#include "shared_realm.hpp" - -#include - -using namespace realm; -using namespace realm::_impl; - -ListNotifier::ListNotifier(LinkViewRef lv, std::shared_ptr realm) -: CollectionNotifier(std::move(realm)) -, m_prev_size(lv->size()) -{ - // Find the lv's column, since that isn't tracked directly - size_t row_ndx = lv->get_origin_row_index(); - m_col_ndx = not_found; - auto& table = lv->get_origin_table(); - for (size_t i = 0, count = table.get_column_count(); i != count; ++i) { - if (table.get_column_type(i) == type_LinkList && table.get_linklist(i, row_ndx) == lv) { - m_col_ndx = i; - break; - } - } - REALM_ASSERT(m_col_ndx != not_found); - - set_table(lv->get_target_table()); - - auto& sg = Realm::Internal::get_shared_group(*get_realm()); - m_lv_handover = sg.export_linkview_for_handover(lv); -} - -void ListNotifier::release_data() noexcept -{ - m_lv.reset(); -} - -void ListNotifier::do_attach_to(SharedGroup& sg) -{ - REALM_ASSERT(m_lv_handover); - REALM_ASSERT(!m_lv); - m_lv = sg.import_linkview_from_handover(std::move(m_lv_handover)); -} - -void ListNotifier::do_detach_from(SharedGroup& sg) -{ - REALM_ASSERT(!m_lv_handover); - if (m_lv) { - m_lv_handover = sg.export_linkview_for_handover(m_lv); - m_lv = {}; - } -} - -bool ListNotifier::do_add_required_change_info(TransactionChangeInfo& info) -{ - REALM_ASSERT(!m_lv_handover); - if (!m_lv || !m_lv->is_attached()) { - return false; // origin row was deleted after the notification was added - } - - size_t row_ndx = m_lv->get_origin_row_index(); - auto& table = m_lv->get_origin_table(); - info.lists.push_back({table.get_index_in_group(), row_ndx, m_col_ndx, &m_change}); - - m_info = &info; - return true; -} - -void ListNotifier::run() -{ - if (!m_lv || !m_lv->is_attached()) { - // LV was deleted, so report all of the rows being removed if this is - // the first run after that - if (m_prev_size) { - m_change.deletions.set(m_prev_size); - m_prev_size = 0; - } - else { - m_change = {}; - } - return; - } - - auto row_did_change = get_modification_checker(*m_info, m_lv->get_target_table()); - for (size_t i = 0; i < m_lv->size(); ++i) { - if (m_change.modifications.contains(i)) - continue; - if (row_did_change(m_lv->get(i).get_index())) - m_change.modifications.add(i); - } - - for (auto const& move : m_change.moves) { - if (m_change.modifications.contains(move.to)) - continue; - if (row_did_change(m_lv->get(move.to).get_index())) - m_change.modifications.add(move.to); - } - - m_prev_size = m_lv->size(); -} - -void ListNotifier::do_prepare_handover(SharedGroup&) -{ - add_changes(std::move(m_change)); -} diff --git a/src/object-store/src/impl/list_notifier.hpp b/src/object-store/src/impl/list_notifier.hpp deleted file mode 100644 index 82b4e414..00000000 --- a/src/object-store/src/impl/list_notifier.hpp +++ /dev/null @@ -1,62 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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_LIST_NOTIFIER_HPP -#define REALM_LIST_NOTIFIER_HPP - -#include "impl/collection_notifier.hpp" - -#include - -namespace realm { -namespace _impl { -class ListNotifier : public CollectionNotifier { -public: - ListNotifier(LinkViewRef lv, std::shared_ptr realm); - -private: - // The linkview, in handover form if this has not been attached to the main - // SharedGroup yet - LinkViewRef m_lv; - std::unique_ptr> m_lv_handover; - - // The last-seen size of the LinkView so that we can report row deletions - // when the LinkView itself is deleted - size_t m_prev_size; - - // The column index of the LinkView - size_t m_col_ndx; - - // The actual change, calculated in run() and delivered in prepare_handover() - CollectionChangeBuilder m_change; - TransactionChangeInfo* m_info; - - void run() override; - - void do_prepare_handover(SharedGroup&) override; - - void do_attach_to(SharedGroup& sg) override; - void do_detach_from(SharedGroup& sg) override; - - void release_data() noexcept override; - bool do_add_required_change_info(TransactionChangeInfo& info) override; -}; -} -} - -#endif // REALM_LIST_NOTIFIER_HPP diff --git a/src/object-store/src/impl/node/weak_realm_notifier.cpp b/src/object-store/src/impl/node/weak_realm_notifier.cpp deleted file mode 100644 index d0d2d58a..00000000 --- a/src/object-store/src/impl/node/weak_realm_notifier.cpp +++ /dev/null @@ -1,79 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include -#include - -#include "impl/weak_realm_notifier.hpp" - -#include "shared_realm.hpp" - -using namespace realm; -using namespace realm::_impl; - -WeakRealmNotifier::WeakRealmNotifier(const std::shared_ptr& realm, bool cache) -: WeakRealmNotifierBase(realm, cache) -, m_handle(new uv_async_t) -{ - m_handle->data = new std::weak_ptr(realm); - - // This assumes that only one thread matters: the main thread (default loop). - uv_async_init(uv_default_loop(), m_handle, [](uv_async_t* handle) { - auto realm_weak_ptr = static_cast*>(handle->data); - auto realm = realm_weak_ptr->lock(); - - if (realm) { - // The v8::Local handles need a "scope" to be present or will crash. - Nan::HandleScope scope; - realm->notify(); - } - }); -} - -WeakRealmNotifier::WeakRealmNotifier(WeakRealmNotifier&& rgt) -: WeakRealmNotifierBase(std::move(rgt)) -, m_handle(rgt.m_handle) -{ - rgt.m_handle = nullptr; -} - -WeakRealmNotifier& WeakRealmNotifier::operator=(WeakRealmNotifier&& rgt) -{ - WeakRealmNotifierBase::operator=(std::move(rgt)); - std::swap(m_handle, rgt.m_handle); - - return *this; -} - -WeakRealmNotifier::~WeakRealmNotifier() -{ - if (m_handle) { - uv_close((uv_handle_t*)m_handle, [](uv_handle_t* handle) { - auto realm_weak_ptr = static_cast*>(handle->data); - delete realm_weak_ptr; - delete handle; - }); - } -} - -void WeakRealmNotifier::notify() -{ - if (m_handle) { - uv_async_send(m_handle); - } -} diff --git a/src/object-store/src/impl/node/weak_realm_notifier.hpp b/src/object-store/src/impl/node/weak_realm_notifier.hpp deleted file mode 100644 index 4b4f57df..00000000 --- a/src/object-store/src/impl/node/weak_realm_notifier.hpp +++ /dev/null @@ -1,47 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "impl/weak_realm_notifier_base.hpp" - -typedef struct uv_async_s uv_async_t; - -namespace realm { -class Realm; - -namespace _impl { - -class WeakRealmNotifier : public WeakRealmNotifierBase { -public: - WeakRealmNotifier(const std::shared_ptr& realm, bool cache); - ~WeakRealmNotifier(); - - WeakRealmNotifier(WeakRealmNotifier&&); - WeakRealmNotifier& operator=(WeakRealmNotifier&&); - - WeakRealmNotifier(const WeakRealmNotifier&) = delete; - WeakRealmNotifier& operator=(const WeakRealmNotifier&) = delete; - - // Asynchronously call notify() on the Realm on the main thread. - void notify(); - -private: - uv_async_t* m_handle; -}; - -} // namespace _impl -} // namespace realm diff --git a/src/object-store/src/impl/realm_coordinator.cpp b/src/object-store/src/impl/realm_coordinator.cpp deleted file mode 100644 index f9e2ae68..00000000 --- a/src/object-store/src/impl/realm_coordinator.cpp +++ /dev/null @@ -1,602 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "impl/realm_coordinator.hpp" - -#include "impl/collection_notifier.hpp" -#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" - -#include -#include -#include -#include - -#include -#include - -using namespace realm; -using namespace realm::_impl; - -static std::mutex s_coordinator_mutex; -static std::unordered_map> s_coordinators_per_path; - -std::shared_ptr RealmCoordinator::get_coordinator(StringData path) -{ - std::lock_guard lock(s_coordinator_mutex); - - auto& weak_coordinator = s_coordinators_per_path[path]; - if (auto coordinator = weak_coordinator.lock()) { - return coordinator; - } - - auto coordinator = std::make_shared(); - weak_coordinator = coordinator; - return coordinator; -} - -std::shared_ptr RealmCoordinator::get_existing_coordinator(StringData path) -{ - std::lock_guard lock(s_coordinator_mutex); - auto it = s_coordinators_per_path.find(path); - return it == s_coordinators_per_path.end() ? nullptr : it->second.lock(); -} - -std::shared_ptr RealmCoordinator::get_realm(Realm::Config config) -{ - std::lock_guard lock(m_realm_mutex); - if ((!m_config.read_only && !m_notifier) || (m_config.read_only && m_weak_realm_notifiers.empty())) { - m_config = config; - } - else { - if (m_config.read_only != config.read_only) { - throw MismatchedConfigException("Realm at path '%1' already opened with different read permissions.", config.path); - } - if (m_config.in_memory != config.in_memory) { - throw MismatchedConfigException("Realm at path '%1' already opened with different inMemory settings.", config.path); - } - if (m_config.encryption_key != config.encryption_key) { - throw MismatchedConfigException("Realm at path '%1' already opened with a different encryption key.", config.path); - } - if (m_config.schema_version != config.schema_version && config.schema_version != ObjectStore::NotVersioned) { - throw MismatchedConfigException("Realm at path '%1' already opened with different schema version.", config.path); - } - // FIXME: verify that schema is compatible - // Needs to verify that all tables present in both are identical, and - // then updated m_config with any tables present in config but not in - // it - // Public API currently doesn't make it possible to have non-matching - // schemata so it's not a huge issue - if ((false) && m_config.schema != config.schema) { - throw MismatchedConfigException("Realm at path '%1' already opened with different schema", config.path); - } - } - - if (config.cache) { - for (auto& cachedRealm : m_weak_realm_notifiers) { - if (cachedRealm.is_cached_for_current_thread()) { - // can be null if we jumped in between ref count hitting zero and - // unregister_realm() getting the lock - if (auto realm = cachedRealm.realm()) { - return realm; - } - } - } - } - - auto realm = std::make_shared(std::move(config)); - realm->init(shared_from_this()); - - if (!config.read_only && !m_notifier && config.automatic_change_notifications) { - try { - m_notifier = std::make_unique(*this); - } - catch (std::system_error const& ex) { - throw RealmFileException(RealmFileException::Kind::AccessError, config.path, ex.code().message(), ""); - } - } - - m_weak_realm_notifiers.emplace_back(realm, m_config.cache); - return realm; -} - -std::shared_ptr RealmCoordinator::get_realm() -{ - return get_realm(m_config); -} - -const Schema* RealmCoordinator::get_schema() const noexcept -{ - return m_weak_realm_notifiers.empty() ? nullptr : m_config.schema.get(); -} - -void RealmCoordinator::update_schema(Schema const& schema) -{ - // FIXME: this should probably be doing some sort of validation and - // notifying all Realm instances of the new schema in some way - m_config.schema = std::make_unique(schema); -} - -RealmCoordinator::RealmCoordinator() = default; - -RealmCoordinator::~RealmCoordinator() -{ - std::lock_guard coordinator_lock(s_coordinator_mutex); - for (auto it = s_coordinators_per_path.begin(); it != s_coordinators_per_path.end(); ) { - if (it->second.expired()) { - it = s_coordinators_per_path.erase(it); - } - else { - ++it; - } - } -} - -void RealmCoordinator::unregister_realm(Realm* realm) -{ - std::lock_guard lock(m_realm_mutex); - auto new_end = remove_if(begin(m_weak_realm_notifiers), end(m_weak_realm_notifiers), - [=](auto& notifier) { return notifier.expired() || notifier.is_for_realm(realm); }); - m_weak_realm_notifiers.erase(new_end, end(m_weak_realm_notifiers)); -} - -void RealmCoordinator::clear_cache() -{ - std::vector realms_to_close; - { - std::lock_guard lock(s_coordinator_mutex); - - for (auto& weak_coordinator : s_coordinators_per_path) { - auto coordinator = weak_coordinator.second.lock(); - if (!coordinator) { - continue; - } - - coordinator->m_notifier = nullptr; - - // Gather a list of all of the realms which will be removed - for (auto& weak_realm_notifier : coordinator->m_weak_realm_notifiers) { - if (auto realm = weak_realm_notifier.realm()) { - realms_to_close.push_back(realm); - } - } - } - - s_coordinators_per_path.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& weak_realm : realms_to_close) { - if (auto realm = weak_realm.lock()) { - realm->close(); - } - } -} - -void RealmCoordinator::clear_all_caches() -{ - std::vector> to_clear; - { - std::lock_guard lock(s_coordinator_mutex); - for (auto iter : s_coordinators_per_path) { - to_clear.push_back(iter.second); - } - } - for (auto weak_coordinator : to_clear) { - if (auto coordinator = weak_coordinator.lock()) { - coordinator->clear_cache(); - } - } -} - -void RealmCoordinator::send_commit_notifications() -{ - REALM_ASSERT(!m_config.read_only); - if (m_notifier) { - m_notifier->notify_others(); - } -} - -void RealmCoordinator::pin_version(uint_fast64_t version, uint_fast32_t index) -{ - if (m_async_error) { - return; - } - - SharedGroup::VersionID versionid(version, index); - if (!m_advancer_sg) { - try { - std::unique_ptr read_only_group; - Realm::open_with_config(m_config, m_advancer_history, m_advancer_sg, read_only_group); - REALM_ASSERT(!read_only_group); - m_advancer_sg->begin_read(versionid); - } - catch (...) { - m_async_error = std::current_exception(); - m_advancer_sg = nullptr; - m_advancer_history = nullptr; - } - } - else if (m_new_notifiers.empty()) { - // If this is the first notifier then we don't already have a read transaction - REALM_ASSERT_3(m_advancer_sg->get_transact_stage(), ==, SharedGroup::transact_Ready); - m_advancer_sg->begin_read(versionid); - } - else { - REALM_ASSERT_3(m_advancer_sg->get_transact_stage(), ==, SharedGroup::transact_Reading); - if (versionid < m_advancer_sg->get_version_of_current_transaction()) { - // Ensure we're holding a readlock on the oldest version we have a - // handover object for, as handover objects don't - m_advancer_sg->end_read(); - m_advancer_sg->begin_read(versionid); - } - } -} - -void RealmCoordinator::register_notifier(std::shared_ptr notifier) -{ - auto version = notifier->version(); - auto& self = Realm::Internal::get_coordinator(*notifier->get_realm()); - { - std::lock_guard lock(self.m_notifier_mutex); - self.pin_version(version.version, version.index); - self.m_new_notifiers.push_back(std::move(notifier)); - } -} - -void RealmCoordinator::clean_up_dead_notifiers() -{ - auto swap_remove = [&](auto& container) { - bool did_remove = false; - for (size_t i = 0; i < container.size(); ++i) { - if (container[i]->is_alive()) - continue; - - // Ensure the notifier is destroyed here even if there's lingering refs - // to the async notifier elsewhere - container[i]->release_data(); - - if (container.size() > i + 1) - container[i] = std::move(container.back()); - container.pop_back(); - --i; - did_remove = true; - } - return did_remove; - }; - - if (swap_remove(m_notifiers)) { - // Make sure we aren't holding on to read versions needlessly if there - // are no notifiers left, but don't close them entirely as opening shared - // groups is expensive - if (m_notifiers.empty() && m_notifier_sg) { - REALM_ASSERT_3(m_notifier_sg->get_transact_stage(), ==, SharedGroup::transact_Reading); - m_notifier_sg->end_read(); - } - } - if (swap_remove(m_new_notifiers)) { - REALM_ASSERT_3(m_advancer_sg->get_transact_stage(), ==, SharedGroup::transact_Reading); - if (m_new_notifiers.empty() && m_advancer_sg) { - m_advancer_sg->end_read(); - } - } -} - -void RealmCoordinator::on_change() -{ - run_async_notifiers(); - - std::lock_guard lock(m_realm_mutex); - for (auto& realm : m_weak_realm_notifiers) { - realm.notify(); - } -} - -namespace { -class IncrementalChangeInfo { -public: - IncrementalChangeInfo(SharedGroup& sg, - std::vector>& notifiers) - : m_sg(sg) - { - if (notifiers.empty()) - return; - - auto cmp = [&](auto&& lft, auto&& rgt) { - return lft->version() < rgt->version(); - }; - - // Sort the notifiers by their source version so that we can pull them - // all forward to the latest version in a single pass over the transaction log - std::sort(notifiers.begin(), notifiers.end(), cmp); - - // Preallocate the required amount of space in the vector so that we can - // safely give out pointers to within the vector - size_t count = 1; - for (auto it = notifiers.begin(), next = it + 1; next != notifiers.end(); ++it, ++next) { - if (cmp(*it, *next)) - ++count; - } - m_info.reserve(count); - m_info.resize(1); - m_current = &m_info[0]; - } - - TransactionChangeInfo& current() const { return *m_current; } - - bool advance_incremental(SharedGroup::VersionID version) - { - if (version != m_sg.get_version_of_current_transaction()) { - transaction::advance(m_sg, *m_current, version); - m_info.push_back({ - m_current->table_modifications_needed, - m_current->table_moves_needed, - std::move(m_current->lists)}); - m_current = &m_info.back(); - return true; - } - return false; - } - - void advance_to_final(SharedGroup::VersionID version) - { - if (!m_current) { - transaction::advance(m_sg, nullptr, version); - return; - } - - transaction::advance(m_sg, *m_current, version); - - // We now need to combine the transaction change info objects so that all of - // the notifiers see the complete set of changes from their first version to - // the most recent one - for (size_t i = m_info.size() - 1; i > 0; --i) { - auto& cur = m_info[i]; - if (cur.tables.empty()) - continue; - auto& prev = m_info[i - 1]; - if (prev.tables.empty()) { - prev.tables = cur.tables; - continue; - } - - for (size_t j = 0; j < prev.tables.size() && j < cur.tables.size(); ++j) { - prev.tables[j].merge(CollectionChangeBuilder{cur.tables[j]}); - } - prev.tables.reserve(cur.tables.size()); - while (prev.tables.size() < cur.tables.size()) { - prev.tables.push_back(cur.tables[prev.tables.size()]); - } - } - - // Copy the list change info if there are multiple LinkViews for the same LinkList - auto id = [](auto const& list) { return std::tie(list.table_ndx, list.col_ndx, list.row_ndx); }; - for (size_t i = 1; i < m_current->lists.size(); ++i) { - for (size_t j = i; j > 0; --j) { - if (id(m_current->lists[i]) == id(m_current->lists[j - 1])) { - m_current->lists[j - 1].changes->merge(CollectionChangeBuilder{*m_current->lists[i].changes}); - } - } - } - } - -private: - std::vector m_info; - TransactionChangeInfo* m_current = nullptr; - SharedGroup& m_sg; -}; -} // anonymous namespace - -void RealmCoordinator::run_async_notifiers() -{ - std::unique_lock lock(m_notifier_mutex); - - clean_up_dead_notifiers(); - - if (m_notifiers.empty() && m_new_notifiers.empty()) { - return; - } - - if (!m_async_error) { - open_helper_shared_group(); - } - - if (m_async_error) { - std::move(m_new_notifiers.begin(), m_new_notifiers.end(), std::back_inserter(m_notifiers)); - m_new_notifiers.clear(); - return; - } - - SharedGroup::VersionID version; - - // Advance all of the new notifiers to the most recent version, if any - auto new_notifiers = std::move(m_new_notifiers); - IncrementalChangeInfo new_notifier_change_info(*m_advancer_sg, new_notifiers); - - if (!new_notifiers.empty()) { - REALM_ASSERT_3(m_advancer_sg->get_transact_stage(), ==, SharedGroup::transact_Reading); - REALM_ASSERT_3(m_advancer_sg->get_version_of_current_transaction().version, - <=, new_notifiers.front()->version().version); - - // The advancer SG can be at an older version than the oldest new notifier - // if a notifier was added and then removed before it ever got the chance - // to run, as we don't move the pin forward when removing dead notifiers - transaction::advance(*m_advancer_sg, nullptr, new_notifiers.front()->version()); - - // Advance each of the new notifiers to the latest version, attaching them - // to the SG at their handover version. This requires a unique - // TransactionChangeInfo for each source version, so that things don't - // see changes from before the version they were handed over from. - // Each Info has all of the changes between that source version and the - // next source version, and they'll be merged together later after - // releasing the lock - for (auto& notifier : new_notifiers) { - new_notifier_change_info.advance_incremental(notifier->version()); - notifier->attach_to(*m_advancer_sg); - notifier->add_required_change_info(new_notifier_change_info.current()); - } - new_notifier_change_info.advance_to_final(SharedGroup::VersionID{}); - - for (auto& notifier : new_notifiers) { - notifier->detach(); - } - version = m_advancer_sg->get_version_of_current_transaction(); - m_advancer_sg->end_read(); - } - REALM_ASSERT_3(m_advancer_sg->get_transact_stage(), ==, SharedGroup::transact_Ready); - - // Make a copy of the notifiers vector and then release the lock to avoid - // blocking other threads trying to register or unregister notifiers while we run them - auto notifiers = m_notifiers; - lock.unlock(); - - // Advance the non-new notifiers to the same version as we advanced the new - // ones to (or the latest if there were no new ones) - IncrementalChangeInfo change_info(*m_notifier_sg, notifiers); - for (auto& notifier : notifiers) { - notifier->add_required_change_info(change_info.current()); - } - change_info.advance_to_final(version); - - // Attach the new notifiers to the main SG and move them to the main list - for (auto& notifier : new_notifiers) { - notifier->attach_to(*m_notifier_sg); - } - std::move(new_notifiers.begin(), new_notifiers.end(), std::back_inserter(notifiers)); - - // Change info is now all ready, so the notifiers can now perform their - // background work - for (auto& notifier : notifiers) { - notifier->run(); - } - - // Reacquire the lock while updating the fields that are actually read on - // other threads - lock.lock(); - for (auto& notifier : notifiers) { - notifier->prepare_handover(); - } - m_notifiers = std::move(notifiers); - clean_up_dead_notifiers(); -} - -void RealmCoordinator::open_helper_shared_group() -{ - if (!m_notifier_sg) { - try { - std::unique_ptr read_only_group; - Realm::open_with_config(m_config, m_notifier_history, m_notifier_sg, read_only_group); - REALM_ASSERT(!read_only_group); - m_notifier_sg->begin_read(); - } - catch (...) { - // Store the error to be passed to the async notifiers - m_async_error = std::current_exception(); - m_notifier_sg = nullptr; - m_notifier_history = nullptr; - } - } - else if (m_notifiers.empty()) { - m_notifier_sg->begin_read(); - } -} - -void RealmCoordinator::advance_to_ready(Realm& realm) -{ - decltype(m_notifiers) notifiers; - - auto& sg = Realm::Internal::get_shared_group(realm); - - auto get_notifier_version = [&] { - for (auto& notifier : m_notifiers) { - auto version = notifier->version(); - if (version != SharedGroup::VersionID{}) { - return version; - } - } - return SharedGroup::VersionID{}; - }; - - SharedGroup::VersionID version; - { - std::lock_guard lock(m_notifier_mutex); - version = get_notifier_version(); - } - - // no async notifiers; just advance to latest - if (version.version == std::numeric_limits::max()) { - transaction::advance(sg, realm.m_binding_context.get()); - return; - } - - // async results are out of date; ignore - if (version < sg.get_version_of_current_transaction()) { - return; - } - - while (true) { - // Advance to the ready version without holding any locks because it - // may end up calling user code (in did_change() notifications) - transaction::advance(sg, realm.m_binding_context.get(), version); - - // Reacquire the lock and recheck the notifier version, as the notifiers may - // have advanced to a later version while we didn't hold the lock. If - // so, we need to release the lock and re-advance - std::lock_guard lock(m_notifier_mutex); - version = get_notifier_version(); - if (version.version == std::numeric_limits::max()) - return; - if (version != sg.get_version_of_current_transaction()) - continue; - - // Query version now matches the SG version, so we can deliver them - for (auto& notifier : m_notifiers) { - if (notifier->deliver(realm, sg, m_async_error)) { - notifiers.push_back(notifier); - } - } - break; - } - - for (auto& notifier : notifiers) { - notifier->call_callbacks(); - } -} - -void RealmCoordinator::process_available_async(Realm& realm) -{ - auto& sg = Realm::Internal::get_shared_group(realm); - decltype(m_notifiers) notifiers; - { - std::lock_guard lock(m_notifier_mutex); - for (auto& notifier : m_notifiers) { - if (notifier->deliver(realm, sg, m_async_error)) { - notifiers.push_back(notifier); - } - } - } - - for (auto& notifier : notifiers) { - notifier->call_callbacks(); - } -} diff --git a/src/object-store/src/impl/realm_coordinator.hpp b/src/object-store/src/impl/realm_coordinator.hpp deleted file mode 100644 index 2a6f74b2..00000000 --- a/src/object-store/src/impl/realm_coordinator.hpp +++ /dev/null @@ -1,127 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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_COORDINATOR_HPP -#define REALM_COORDINATOR_HPP - -#include "shared_realm.hpp" - -#include - -namespace realm { -class Replication; -class Schema; -class SharedGroup; -class StringData; - -namespace _impl { -class CollectionNotifier; -class ExternalCommitHelper; -class WeakRealmNotifier; - -// RealmCoordinator manages the weak cache of Realm instances and communication -// between per-thread Realm instances for a given file -class RealmCoordinator : public std::enable_shared_from_this { -public: - // Get the coordinator for the given path, creating it if neccesary - static std::shared_ptr get_coordinator(StringData path); - // Get the coordinator for the given path, or null if there is none - static std::shared_ptr get_existing_coordinator(StringData path); - - // Get a thread-local shared Realm with the given configuration - // If the Realm is already open on another thread, validates that the given - // configuration is compatible with the existing one - std::shared_ptr get_realm(Realm::Config config); - std::shared_ptr get_realm(); - - const Schema* get_schema() const noexcept; - uint64_t get_schema_version() const noexcept { return m_config.schema_version; } - const std::string& get_path() const noexcept { return m_config.path; } - const std::vector& get_encryption_key() const noexcept { return m_config.encryption_key; } - bool is_in_memory() const noexcept { return m_config.in_memory; } - - // Asynchronously call notify() on every Realm instance for this coordinator's - // path, including those in other processes - void send_commit_notifications(); - - // Clear the weak Realm cache for all paths - // Should only be called in test code, as continuing to use the previously - // cached instances will have odd results - static void clear_cache(); - - // Clears all caches on existing coordinators - static void clear_all_caches(); - - // Explicit constructor/destructor needed for the unique_ptrs to forward-declared types - RealmCoordinator(); - ~RealmCoordinator(); - - // Called by Realm's destructor to ensure the cache is cleaned up promptly - // Do not call directly - void unregister_realm(Realm* realm); - - // Called by m_notifier when there's a new commit to send notifications for - void on_change(); - - // Update the schema in the cached config - void update_schema(Schema const& new_schema); - - static void register_notifier(std::shared_ptr notifier); - - // Advance the Realm to the most recent transaction version which all async - // work is complete for - void advance_to_ready(Realm& realm); - void process_available_async(Realm& realm); - -private: - Realm::Config m_config; - - std::mutex m_realm_mutex; - std::vector m_weak_realm_notifiers; - - std::mutex m_notifier_mutex; - std::vector> m_new_notifiers; - std::vector> m_notifiers; - - // SharedGroup used for actually running async notifiers - // Will have a read transaction iff m_notifiers is non-empty - std::unique_ptr m_notifier_history; - std::unique_ptr m_notifier_sg; - - // SharedGroup used to advance notifiers in m_new_notifiers to the main shared - // group's transaction version - // Will have a read transaction iff m_new_notifiers is non-empty - std::unique_ptr m_advancer_history; - std::unique_ptr m_advancer_sg; - std::exception_ptr m_async_error; - - std::unique_ptr<_impl::ExternalCommitHelper> m_notifier; - - // must be called with m_notifier_mutex locked - void pin_version(uint_fast64_t version, uint_fast32_t index); - - void run_async_notifiers(); - void open_helper_shared_group(); - void advance_helper_shared_group_to_latest(); - void clean_up_dead_notifiers(); -}; - -} // namespace _impl -} // namespace realm - -#endif /* REALM_COORDINATOR_HPP */ diff --git a/src/object-store/src/impl/results_notifier.cpp b/src/object-store/src/impl/results_notifier.cpp deleted file mode 100644 index eea290c2..00000000 --- a/src/object-store/src/impl/results_notifier.cpp +++ /dev/null @@ -1,217 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "impl/results_notifier.hpp" - -using namespace realm; -using namespace realm::_impl; - -ResultsNotifier::ResultsNotifier(Results& target) -: CollectionNotifier(target.get_realm()) -, m_target_results(&target) -, m_sort(target.get_sort()) -, m_target_is_in_table_order(target.is_in_table_order()) -{ - Query q = target.get_query(); - set_table(*q.get_table()); - m_query_handover = Realm::Internal::get_shared_group(*get_realm()).export_for_handover(q, MutableSourcePayload::Move); -} - -void ResultsNotifier::target_results_moved(Results& old_target, Results& new_target) -{ - auto lock = lock_target(); - - REALM_ASSERT(m_target_results == &old_target); - m_target_results = &new_target; -} - -void ResultsNotifier::release_data() noexcept -{ - m_query = nullptr; -} - -// Most of the inter-thread synchronization for run(), prepare_handover(), -// attach_to(), detach(), release_data() and deliver() is done by -// RealmCoordinator external to this code, which has some potentially -// non-obvious results on which members are and are not safe to use without -// holding a lock. -// -// add_required_change_info(), attach_to(), detach(), run(), -// prepare_handover(), and release_data() are all only ever called on a single -// background worker thread. call_callbacks() and deliver() are called on the -// target thread. Calls to prepare_handover() and deliver() are guarded by a -// lock. -// -// In total, this means that the safe data flow is as follows: -// - add_Required_change_info(), prepare_handover(), attach_to(), detach() and -// release_data() can read members written by each other -// - deliver() can read members written to in prepare_handover(), deliver(), -// and call_callbacks() -// - call_callbacks() and read members written to in deliver() -// -// Separately from the handover data flow, m_target_results is guarded by the target lock - -bool ResultsNotifier::do_add_required_change_info(TransactionChangeInfo& info) -{ - REALM_ASSERT(m_query); - m_info = &info; - - auto table_ndx = m_query->get_table()->get_index_in_group(); - if (info.table_moves_needed.size() <= table_ndx) - info.table_moves_needed.resize(table_ndx + 1); - info.table_moves_needed[table_ndx] = true; - - return m_initial_run_complete && have_callbacks(); -} - -bool ResultsNotifier::need_to_run() -{ - REALM_ASSERT(m_info); - REALM_ASSERT(!m_tv.is_attached()); - - { - auto lock = lock_target(); - // Don't run the query if the results aren't actually going to be used - if (!get_realm() || (!have_callbacks() && !m_target_results->wants_background_updates())) { - return false; - } - } - - // If we've run previously, check if we need to rerun - if (m_initial_run_complete && m_query->sync_view_if_needed() == m_last_seen_version) { - return false; - } - - return true; -} - -void ResultsNotifier::calculate_changes() -{ - size_t table_ndx = m_query->get_table()->get_index_in_group(); - if (m_initial_run_complete) { - auto changes = table_ndx < m_info->tables.size() ? &m_info->tables[table_ndx] : nullptr; - - std::vector next_rows; - next_rows.reserve(m_tv.size()); - for (size_t i = 0; i < m_tv.size(); ++i) - next_rows.push_back(m_tv[i].get_index()); - - if (changes) { - auto const& moves = changes->moves; - for (auto& idx : m_previous_rows) { - auto it = lower_bound(begin(moves), end(moves), idx, - [](auto const& a, auto b) { return a.from < b; }); - if (it != moves.end() && it->from == idx) - idx = it->to; - else if (changes->deletions.contains(idx)) - idx = npos; - else - REALM_ASSERT_DEBUG(!changes->insertions.contains(idx)); - } - } - - m_changes = CollectionChangeBuilder::calculate(m_previous_rows, next_rows, - get_modification_checker(*m_info, *m_query->get_table()), - m_target_is_in_table_order && !m_sort); - - m_previous_rows = std::move(next_rows); - } - else { - m_previous_rows.resize(m_tv.size()); - for (size_t i = 0; i < m_tv.size(); ++i) - m_previous_rows[i] = m_tv[i].get_index(); - } -} - -void ResultsNotifier::run() -{ - if (!need_to_run()) - return; - - m_query->sync_view_if_needed(); - m_tv = m_query->find_all(); - if (m_sort) { - m_tv.sort(m_sort.column_indices, m_sort.ascending); - } - m_last_seen_version = m_tv.sync_if_needed(); - - calculate_changes(); -} - -void ResultsNotifier::do_prepare_handover(SharedGroup& sg) -{ - if (!m_tv.is_attached()) { - return; - } - - REALM_ASSERT(m_tv.is_in_sync()); - - m_initial_run_complete = true; - m_tv_handover = sg.export_for_handover(m_tv, MutableSourcePayload::Move); - - add_changes(std::move(m_changes)); - REALM_ASSERT(m_changes.empty()); - - // detach the TableView as we won't need it again and keeping it around - // makes advance_read() much more expensive - m_tv = {}; -} - -bool ResultsNotifier::do_deliver(SharedGroup& sg) -{ - auto lock = lock_target(); - - // Target realm being null here indicates that we were unregistered while we - // were in the process of advancing the Realm version and preparing for - // delivery, i.e. the results was destroyed from the "wrong" thread - if (!get_realm()) { - return false; - } - - // We can get called before the query has actually had the chance to run if - // we're added immediately before a different set of async results are - // delivered - if (!m_initial_run_complete) { - return false; - } - - REALM_ASSERT(!m_query_handover); - - if (m_tv_handover) { - m_tv_handover->version = version(); - Results::Internal::set_table_view(*m_target_results, - std::move(*sg.import_from_handover(std::move(m_tv_handover)))); - } - REALM_ASSERT(!m_tv_handover); - return true; -} - -void ResultsNotifier::do_attach_to(SharedGroup& sg) -{ - REALM_ASSERT(m_query_handover); - m_query = sg.import_from_handover(std::move(m_query_handover)); -} - -void ResultsNotifier::do_detach_from(SharedGroup& sg) -{ - REALM_ASSERT(m_query); - REALM_ASSERT(!m_tv.is_attached()); - - m_query_handover = sg.export_for_handover(*m_query, MutableSourcePayload::Move); - m_query = nullptr; -} diff --git a/src/object-store/src/impl/results_notifier.hpp b/src/object-store/src/impl/results_notifier.hpp deleted file mode 100644 index 23212922..00000000 --- a/src/object-store/src/impl/results_notifier.hpp +++ /dev/null @@ -1,83 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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_RESULTS_NOTIFIER_HPP -#define REALM_RESULTS_NOTIFIER_HPP - -#include "collection_notifier.hpp" -#include "results.hpp" - -#include - -namespace realm { -namespace _impl { -class ResultsNotifier : public CollectionNotifier { -public: - ResultsNotifier(Results& target); - - void target_results_moved(Results& old_target, Results& new_target); - -private: - // Target Results to update - // Can only be used with lock_target() held - Results* m_target_results; - - const SortOrder m_sort; - bool m_target_is_in_table_order; - - // The source Query, in handover form iff m_sg is null - std::unique_ptr> m_query_handover; - std::unique_ptr m_query; - - // The TableView resulting from running the query. Will be detached unless - // the query was (re)run since the last time the handover object was created - TableView m_tv; - std::unique_ptr> m_tv_handover; - - // The table version from the last time the query was run. Used to avoid - // rerunning the query when there's no chance of it changing. - uint_fast64_t m_last_seen_version = -1; - - // The rows from the previous run of the query, for calculating diffs - std::vector m_previous_rows; - - // The changeset calculated during run() and delivered in do_prepare_handover() - CollectionChangeBuilder m_changes; - TransactionChangeInfo* m_info = nullptr; - - // Flag for whether or not the query has been run at all, as goofy timing - // can lead to deliver() being called before that - bool m_initial_run_complete = false; - - bool need_to_run(); - void calculate_changes(); - - void run() override; - void do_prepare_handover(SharedGroup&) override; - bool do_deliver(SharedGroup& sg) override; - bool do_add_required_change_info(TransactionChangeInfo& info) override; - - void release_data() noexcept override; - void do_attach_to(SharedGroup& sg) override; - void do_detach_from(SharedGroup& sg) override; -}; - -} // namespace _impl -} // namespace realm - -#endif /* REALM_RESULTS_NOTIFIER_HPP */ diff --git a/src/object-store/src/impl/transact_log_handler.cpp b/src/object-store/src/impl/transact_log_handler.cpp deleted file mode 100644 index c163fbb9..00000000 --- a/src/object-store/src/impl/transact_log_handler.cpp +++ /dev/null @@ -1,620 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "impl/transact_log_handler.hpp" - -#include "binding_context.hpp" -#include "impl/collection_notifier.hpp" -#include "index_set.hpp" - -#include -#include -#include - -using namespace realm; - -namespace { -template -struct MarkDirtyMixin { - bool mark_dirty(size_t row, size_t col) { static_cast(this)->mark_dirty(row, col); return true; } - - bool set_int(size_t col, size_t row, int_fast64_t) { return mark_dirty(row, col); } - bool set_bool(size_t col, size_t row, bool) { return mark_dirty(row, col); } - bool set_float(size_t col, size_t row, float) { return mark_dirty(row, col); } - bool set_double(size_t col, size_t row, double) { return mark_dirty(row, col); } - bool set_string(size_t col, size_t row, StringData) { return mark_dirty(row, col); } - bool set_binary(size_t col, size_t row, BinaryData) { return mark_dirty(row, col); } - bool set_olddatetime(size_t col, size_t row, OldDateTime) { return mark_dirty(row, col); } - bool set_timestamp(size_t col, size_t row, Timestamp) { return mark_dirty(row, col); } - bool set_table(size_t col, size_t row) { return mark_dirty(row, col); } - bool set_mixed(size_t col, size_t row, const Mixed&) { return mark_dirty(row, col); } - bool set_link(size_t col, size_t row, size_t, size_t) { return mark_dirty(row, col); } - bool set_null(size_t col, size_t row) { return mark_dirty(row, col); } - bool nullify_link(size_t col, size_t row, size_t) { return mark_dirty(row, col); } - bool set_int_unique(size_t col, size_t row, size_t, int_fast64_t) { return mark_dirty(row, col); } - bool set_string_unique(size_t col, size_t row, size_t, StringData) { return mark_dirty(row, col); } - bool insert_substring(size_t col, size_t row, size_t, StringData) { return mark_dirty(row, col); } - bool erase_substring(size_t col, size_t row, size_t, size_t) { return mark_dirty(row, col); } -}; - -class TransactLogValidationMixin { - // Index of currently selected table - size_t m_current_table = 0; - - // Tables which were created during the transaction being processed, which - // can have columns inserted without a schema version bump - std::vector m_new_tables; - - REALM_NORETURN - REALM_NOINLINE - void schema_error() - { - 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 - // the current set of modifications - bool schema_error_unless_new_table() - { - if (std::find(begin(m_new_tables), end(m_new_tables), m_current_table) == end(m_new_tables)) { - schema_error(); - } - return true; - } - -protected: - size_t current_table() const noexcept { return m_current_table; } - -public: - // Schema changes which don't involve a change in the schema version are - // allowed - bool add_search_index(size_t) { return true; } - bool remove_search_index(size_t) { return true; } - - // Creating entirely new tables without a schema version bump is allowed, so - // we need to track if new columns are being added to a new table or an - // existing one - bool insert_group_level_table(size_t table_ndx, size_t, StringData) - { - // Shift any previously added tables after the new one - for (auto& table : m_new_tables) { - if (table >= table_ndx) - ++table; - } - m_new_tables.push_back(table_ndx); - return true; - } - bool insert_column(size_t, DataType, StringData, bool) { return schema_error_unless_new_table(); } - bool insert_link_column(size_t, DataType, StringData, size_t, size_t) { return schema_error_unless_new_table(); } - bool set_link_type(size_t, LinkType) { return schema_error_unless_new_table(); } - - // Removing or renaming things while a Realm is open is never supported - bool erase_group_level_table(size_t, size_t) { schema_error(); } - bool rename_group_level_table(size_t, StringData) { schema_error(); } - bool erase_column(size_t) { schema_error(); } - bool erase_link_column(size_t, size_t, size_t) { schema_error(); } - bool rename_column(size_t, StringData) { schema_error(); } - bool move_column(size_t, size_t) { schema_error(); } - bool move_group_level_table(size_t, size_t) { schema_error(); } - - bool select_descriptor(int levels, const size_t*) - { - // subtables not supported - return levels == 0; - } - - bool select_table(size_t group_level_ndx, int, const size_t*) noexcept - { - m_current_table = group_level_ndx; - return true; - } - - bool select_link_list(size_t, size_t, size_t) { return true; } - - // Non-schema changes are all allowed - void parse_complete() { } - bool insert_empty_rows(size_t, size_t, size_t, bool) { return true; } - bool erase_rows(size_t, size_t, size_t, bool) { return true; } - bool swap_rows(size_t, size_t) { return true; } - bool clear_table() noexcept { return true; } - bool link_list_set(size_t, size_t) { return true; } - bool link_list_insert(size_t, size_t) { return true; } - bool link_list_erase(size_t) { return true; } - bool link_list_nullify(size_t) { return true; } - bool link_list_clear(size_t) { return true; } - bool link_list_move(size_t, size_t) { return true; } - bool link_list_swap(size_t, size_t) { return true; } - bool change_link_targets(size_t, size_t) { return true; } - bool optimize_table() { return true; } -}; - - -// A transaction log handler that just validates that all operations made are -// ones supported by the object store -struct TransactLogValidator : public TransactLogValidationMixin, public MarkDirtyMixin { - void mark_dirty(size_t, size_t) { } -}; - -// Extends TransactLogValidator to also track changes and report it to the -// binding context if any properties are being observed -class TransactLogObserver : public TransactLogValidationMixin, public MarkDirtyMixin { - using ColumnInfo = BindingContext::ColumnInfo; - using ObserverState = BindingContext::ObserverState; - - // Observed table rows which need change information - std::vector m_observers; - // Userdata pointers for rows which have been deleted - std::vector invalidated; - // Delegate to send change information to - BindingContext* m_context; - - // Change information for the currently selected LinkList, if any - ColumnInfo* m_active_linklist = nullptr; - - // Tables which were created during the transaction being processed, which - // can have columns inserted without a schema version bump - std::vector m_new_tables; - - // Get the change info for the given column, creating it if needed - static ColumnInfo& get_change(ObserverState& state, size_t i) - { - if (state.changes.size() <= i) { - state.changes.resize(std::max(state.changes.size() * 2, i + 1)); - } - return state.changes[i]; - } - - // Loop over the columns which were changed in an observer state - template - static void for_each(ObserverState& state, Func&& f) - { - for (size_t i = 0; i < state.changes.size(); ++i) { - auto const& change = state.changes[i]; - if (change.changed) { - f(i, change); - } - } - } - - // Remove the given observer from the list of observed objects and add it - // to the listed of invalidated objects - void invalidate(ObserverState *o) - { - invalidated.push_back(o->info); - m_observers.erase(m_observers.begin() + (o - &m_observers[0])); - } - -public: - template - TransactLogObserver(BindingContext* context, SharedGroup& sg, Func&& func, bool validate_schema_changes) - : m_context(context) - { - if (!context) { - if (validate_schema_changes) { - func(TransactLogValidator()); - } - else { - func(); - } - return; - } - - m_observers = context->get_observed_rows(); - if (m_observers.empty()) { - auto old_version = sg.get_version_of_current_transaction(); - if (validate_schema_changes) { - func(TransactLogValidator()); - } - else { - func(); - } - if (old_version != sg.get_version_of_current_transaction()) { - context->did_change({}, {}); - } - return; - } - - func(*this); - context->did_change(m_observers, invalidated); - } - - // Mark the given row/col as needing notifications sent - void mark_dirty(size_t row_ndx, size_t col_ndx) - { - auto it = lower_bound(begin(m_observers), end(m_observers), ObserverState{current_table(), row_ndx, nullptr}); - if (it != end(m_observers) && it->table_ndx == current_table() && it->row_ndx == row_ndx) { - get_change(*it, col_ndx).changed = true; - } - } - - // Called at the end of the transaction log immediately before the version - // is advanced - void parse_complete() - { - m_context->will_change(m_observers, invalidated); - } - - bool insert_group_level_table(size_t table_ndx, size_t prior_size, StringData name) - { - for (auto& observer : m_observers) { - if (observer.table_ndx >= table_ndx) - ++observer.table_ndx; - } - TransactLogValidationMixin::insert_group_level_table(table_ndx, prior_size, name); - return true; - } - - bool insert_empty_rows(size_t, size_t, size_t, bool) - { - // rows are only inserted at the end, so no need to do anything - return true; - } - - bool erase_rows(size_t row_ndx, size_t, size_t last_row_ndx, bool unordered) - { - for (size_t i = 0; i < m_observers.size(); ++i) { - auto& o = m_observers[i]; - if (o.table_ndx == current_table()) { - if (o.row_ndx == row_ndx) { - invalidate(&o); - --i; - } - else if (unordered && o.row_ndx == last_row_ndx) { - o.row_ndx = row_ndx; - } - else if (!unordered && o.row_ndx > row_ndx) { - o.row_ndx -= 1; - } - } - } - return true; - } - - bool clear_table() - { - for (size_t i = 0; i < m_observers.size(); ) { - auto& o = m_observers[i]; - if (o.table_ndx == current_table()) { - invalidate(&o); - } - else { - ++i; - } - } - return true; - } - - bool select_link_list(size_t col, size_t row, size_t) - { - m_active_linklist = nullptr; - for (auto& o : m_observers) { - if (o.table_ndx == current_table() && o.row_ndx == row) { - m_active_linklist = &get_change(o, col); - break; - } - } - return true; - } - - void append_link_list_change(ColumnInfo::Kind kind, size_t index) { - ColumnInfo *o = m_active_linklist; - if (!o || o->kind == ColumnInfo::Kind::SetAll) { - // Active LinkList isn't observed or already has multiple kinds of changes - return; - } - - if (o->kind == ColumnInfo::Kind::None) { - o->kind = kind; - o->changed = true; - o->indices.add(index); - } - else if (o->kind == kind) { - if (kind == ColumnInfo::Kind::Remove) { - o->indices.add_shifted(index); - } - else if (kind == ColumnInfo::Kind::Insert) { - o->indices.insert_at(index); - } - else { - o->indices.add(index); - } - } - else { - // Array KVO can only send a single kind of change at a time, so - // if there are multiple just give up and send "Set" - o->indices.set(0); - o->kind = ColumnInfo::Kind::SetAll; - } - } - - bool link_list_set(size_t index, size_t) - { - append_link_list_change(ColumnInfo::Kind::Set, index); - return true; - } - - bool link_list_insert(size_t index, size_t) - { - append_link_list_change(ColumnInfo::Kind::Insert, index); - return true; - } - - bool link_list_erase(size_t index) - { - append_link_list_change(ColumnInfo::Kind::Remove, index); - return true; - } - - bool link_list_nullify(size_t index) - { - append_link_list_change(ColumnInfo::Kind::Remove, index); - return true; - } - - bool link_list_swap(size_t index1, size_t index2) - { - append_link_list_change(ColumnInfo::Kind::Set, index1); - append_link_list_change(ColumnInfo::Kind::Set, index2); - return true; - } - - bool link_list_clear(size_t old_size) - { - ColumnInfo *o = m_active_linklist; - if (!o || o->kind == ColumnInfo::Kind::SetAll) { - return true; - } - - if (o->kind == ColumnInfo::Kind::Remove) - old_size += o->indices.count(); - else if (o->kind == ColumnInfo::Kind::Insert) - old_size -= o->indices.count(); - - o->indices.set(old_size); - - o->kind = ColumnInfo::Kind::Remove; - o->changed = true; - return true; - } - - bool link_list_move(size_t from, size_t to) - { - ColumnInfo *o = m_active_linklist; - if (!o || o->kind == ColumnInfo::Kind::SetAll) { - return true; - } - if (from > to) { - std::swap(from, to); - } - - if (o->kind == ColumnInfo::Kind::None) { - o->kind = ColumnInfo::Kind::Set; - o->changed = true; - } - if (o->kind == ColumnInfo::Kind::Set) { - for (size_t i = from; i <= to; ++i) - o->indices.add(i); - } - else { - o->indices.set(0); - o->kind = ColumnInfo::Kind::SetAll; - } - return true; - } -}; - -// Extends TransactLogValidator to track changes made to LinkViews -class LinkViewObserver : public TransactLogValidationMixin, public MarkDirtyMixin { - _impl::TransactionChangeInfo& m_info; - _impl::CollectionChangeBuilder* m_active = nullptr; - - _impl::CollectionChangeBuilder* get_change() - { - auto tbl_ndx = current_table(); - if (tbl_ndx >= m_info.table_modifications_needed.size() || !m_info.table_modifications_needed[tbl_ndx]) - return nullptr; - if (m_info.tables.size() <= tbl_ndx) { - m_info.tables.resize(std::max(m_info.tables.size() * 2, tbl_ndx + 1)); - } - return &m_info.tables[tbl_ndx]; - } - - bool need_move_info() const - { - auto tbl_ndx = current_table(); - return tbl_ndx < m_info.table_moves_needed.size() && m_info.table_moves_needed[tbl_ndx]; - } - -public: - LinkViewObserver(_impl::TransactionChangeInfo& info) - : m_info(info) { } - - void mark_dirty(size_t row, size_t) - { - if (auto change = get_change()) - change->modify(row); - } - - void parse_complete() - { - for (auto& table : m_info.tables) { - table.parse_complete(); - } - for (auto& list : m_info.lists) { - list.changes->clean_up_stale_moves(); - } - } - - bool select_link_list(size_t col, size_t row, size_t) - { - mark_dirty(row, col); - - m_active = nullptr; - // When there are multiple source versions there could be multiple - // change objects for a single LinkView, in which case we need to use - // the last one - for (auto it = m_info.lists.rbegin(), end = m_info.lists.rend(); it != end; ++it) { - if (it->table_ndx == current_table() && it->row_ndx == row && it->col_ndx == col) { - m_active = it->changes; - break; - } - } - return true; - } - - bool link_list_set(size_t index, size_t) - { - if (m_active) - m_active->modify(index); - return true; - } - - bool link_list_insert(size_t index, size_t) - { - if (m_active) - m_active->insert(index); - return true; - } - - bool link_list_erase(size_t index) - { - if (m_active) - m_active->erase(index); - return true; - } - - bool link_list_nullify(size_t index) - { - return link_list_erase(index); - } - - bool link_list_swap(size_t index1, size_t index2) - { - link_list_set(index1, 0); - link_list_set(index2, 0); - return true; - } - - bool link_list_clear(size_t old_size) - { - if (m_active) - m_active->clear(old_size); - return true; - } - - bool link_list_move(size_t from, size_t to) - { - if (m_active) - m_active->move(from, to); - return true; - } - - bool insert_empty_rows(size_t row_ndx, size_t num_rows_to_insert, size_t, bool unordered) - { - REALM_ASSERT(!unordered); - if (auto change = get_change()) - change->insert(row_ndx, num_rows_to_insert, need_move_info()); - - return true; - } - - bool erase_rows(size_t row_ndx, size_t, size_t prior_num_rows, bool unordered) - { - REALM_ASSERT(unordered); - size_t last_row = prior_num_rows - 1; - - for (auto it = begin(m_info.lists); it != end(m_info.lists); ) { - if (it->table_ndx == current_table()) { - if (it->row_ndx == row_ndx) { - *it = std::move(m_info.lists.back()); - m_info.lists.pop_back(); - continue; - } - if (it->row_ndx == last_row - 1) - it->row_ndx = row_ndx; - } - ++it; - } - - if (auto change = get_change()) - change->move_over(row_ndx, last_row, need_move_info()); - return true; - } - - bool clear_table() - { - auto tbl_ndx = current_table(); - auto it = remove_if(begin(m_info.lists), end(m_info.lists), - [&](auto const& lv) { return lv.table_ndx == tbl_ndx; }); - m_info.lists.erase(it, end(m_info.lists)); - if (auto change = get_change()) - change->clear(std::numeric_limits::max()); - return true; - } -}; -} // anonymous namespace - -namespace realm { -namespace _impl { -namespace transaction { -void advance(SharedGroup& sg, BindingContext* context, SharedGroup::VersionID version) -{ - TransactLogObserver(context, sg, [&](auto&&... args) { - LangBindHelper::advance_read(sg, std::move(args)..., version); - }, true); -} - -void begin(SharedGroup& sg, BindingContext* context, bool validate_schema_changes) -{ - TransactLogObserver(context, sg, [&](auto&&... args) { - LangBindHelper::promote_to_write(sg, std::move(args)...); - }, validate_schema_changes); -} - -void commit(SharedGroup& sg, BindingContext* context) -{ - LangBindHelper::commit_and_continue_as_read(sg); - - if (context) { - context->did_change({}, {}); - } -} - -void cancel(SharedGroup& sg, BindingContext* context) -{ - TransactLogObserver(context, sg, [&](auto&&... args) { - LangBindHelper::rollback_and_continue_as_read(sg, std::move(args)...); - }, false); -} - -void advance(SharedGroup& sg, - TransactionChangeInfo& info, - SharedGroup::VersionID version) -{ - if (info.table_modifications_needed.empty() && info.lists.empty()) { - LangBindHelper::advance_read(sg, version); - } - else { - LangBindHelper::advance_read(sg, LinkViewObserver(info), version); - } - -} - -} // namespace transaction -} // namespace _impl -} // namespace realm diff --git a/src/object-store/src/impl/transact_log_handler.hpp b/src/object-store/src/impl/transact_log_handler.hpp deleted file mode 100644 index 96dbbfda..00000000 --- a/src/object-store/src/impl/transact_log_handler.hpp +++ /dev/null @@ -1,57 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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_TRANSACT_LOG_HANDLER_HPP -#define REALM_TRANSACT_LOG_HANDLER_HPP - -#include - -namespace realm { -class BindingContext; - -namespace _impl { -struct TransactionChangeInfo; - -namespace transaction { -// Advance the read transaction version, with change notifications sent to delegate -// Must not be called from within a write transaction. -void advance(SharedGroup& sg, BindingContext* binding_context, - SharedGroup::VersionID version=SharedGroup::VersionID{}); - -// Begin a write transaction -// If the read transaction version is not up to date, will first advance to the -// most recent read transaction and sent notifications to delegate -void begin(SharedGroup& sg, BindingContext* binding_context, - bool validate_schema_changes=true); - -// Commit a write transaction -void commit(SharedGroup& sg, BindingContext* binding_context); - -// Cancel a write transaction and roll back all changes, with change notifications -// for reverting to the old values sent to delegate -void cancel(SharedGroup& sg, BindingContext* binding_context); - -// Advance the read transaction version, with change information gathered in info -void advance(SharedGroup& sg, - TransactionChangeInfo& info, - SharedGroup::VersionID version=SharedGroup::VersionID{}); -} // namespace transaction -} // namespace _impl -} // namespace realm - -#endif /* REALM_TRANSACT_LOG_HANDLER_HPP */ diff --git a/src/object-store/src/impl/weak_realm_notifier.hpp b/src/object-store/src/impl/weak_realm_notifier.hpp deleted file mode 100644 index fe575d54..00000000 --- a/src/object-store/src/impl/weak_realm_notifier.hpp +++ /dev/null @@ -1,34 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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_WEAK_REALM_NOTIFIER_HPP -#define REALM_WEAK_REALM_NOTIFIER_HPP - -#include - -#if REALM_PLATFORM_NODE -#include "impl/node/weak_realm_notifier.hpp" -#elif REALM_PLATFORM_APPLE -#include "impl/apple/weak_realm_notifier.hpp" -#elif REALM_ANDROID -#include "impl/android/weak_realm_notifier.hpp" -#else -#include "impl/generic/weak_realm_notifier.hpp" -#endif - -#endif // REALM_WEAK_REALM_NOTIFIER_HPP diff --git a/src/object-store/src/impl/weak_realm_notifier_base.hpp b/src/object-store/src/impl/weak_realm_notifier_base.hpp deleted file mode 100644 index ab19c6ae..00000000 --- a/src/object-store/src/impl/weak_realm_notifier_base.hpp +++ /dev/null @@ -1,71 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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_WEAK_REALM_NOTIFIER_BASE_HPP -#define REALM_WEAK_REALM_NOTIFIER_BASE_HPP - -#include "util/thread_id.hpp" - -#include - -namespace realm { -class Realm; - -namespace _impl { - -// WeakRealmNotifierBase stores a weak reference to a Realm instance, along with all of -// the information about a Realm that needs to be accessed from other threads. -// This is needed to avoid forming strong references to the Realm instances on -// other threads, which can produce deadlocks when the last strong reference to -// a Realm instance is released from within a function holding the cache lock. -class WeakRealmNotifierBase { -public: - WeakRealmNotifierBase(const std::shared_ptr& realm, bool cache); - - // Get a strong reference to the cached realm - std::shared_ptr realm() const { return m_realm.lock(); } - - // Does this WeakRealmNotifierBase store a Realm instance that should be used on the current thread? - bool is_cached_for_current_thread() const { return m_cache && is_for_current_thread(); } - - // Has the Realm instance been destroyed? - bool expired() const { return m_realm.expired(); } - - // Is this a WeakRealmNotifierBase for the given Realm instance? - bool is_for_realm(Realm* realm) const { return realm == m_realm_key; } - - bool is_for_current_thread() const { return m_thread_id == util::get_thread_id(); } - -private: - std::weak_ptr m_realm; - thread_id_t m_thread_id = util::get_thread_id(); - void* m_realm_key; - bool m_cache = false; -}; - -inline WeakRealmNotifierBase::WeakRealmNotifierBase(const std::shared_ptr& realm, bool cache) -: m_realm(realm) -, m_realm_key(realm.get()) -, m_cache(cache) -{ -} - -} // namespace _impl -} // namespace realm - -#endif // REALM_WEAK_REALM_NOTIFIER_BASE_HPP diff --git a/src/object-store/src/index_set.cpp b/src/object-store/src/index_set.cpp deleted file mode 100644 index a5c30c2b..00000000 --- a/src/object-store/src/index_set.cpp +++ /dev/null @@ -1,707 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "index_set.hpp" - -#include - -#include - -using namespace realm; -using namespace realm::_impl; - -const size_t IndexSet::npos; - -template -void MutableChunkedRangeVectorIterator::set(size_t front, size_t back) -{ - this->m_outer->count -= this->m_inner->second - this->m_inner->first; - if (this->offset() == 0) { - this->m_outer->begin = front; - } - if (this->m_inner == &this->m_outer->data.back()) { - this->m_outer->end = back; - } - this->m_outer->count += back - front; - this->m_inner->first = front; - this->m_inner->second = back; -} - -template -void MutableChunkedRangeVectorIterator::adjust(ptrdiff_t front, ptrdiff_t back) -{ - if (this->offset() == 0) { - this->m_outer->begin += front; - } - if (this->m_inner == &this->m_outer->data.back()) { - this->m_outer->end += back; - } - this->m_outer->count += -front + back; - this->m_inner->first += front; - this->m_inner->second += back; -} - -template -void MutableChunkedRangeVectorIterator::shift(ptrdiff_t distance) -{ - if (this->offset() == 0) { - this->m_outer->begin += distance; - } - if (this->m_inner == &this->m_outer->data.back()) { - this->m_outer->end += distance; - } - this->m_inner->first += distance; - this->m_inner->second += distance; -} - -void ChunkedRangeVector::push_back(value_type value) -{ - if (!empty() && m_data.back().data.size() < max_size) { - auto& range = m_data.back(); - REALM_ASSERT(range.end <= value.first); - - range.data.push_back(value); - range.count += value.second - value.first; - range.end = value.second; - } - else { - m_data.push_back({{std::move(value)}, value.first, value.second, value.second - value.first}); - } - verify(); -} - -ChunkedRangeVector::iterator ChunkedRangeVector::insert(iterator pos, value_type value) -{ - if (pos.m_outer == m_data.end()) { - push_back(std::move(value)); - return std::prev(end()); - } - - pos = ensure_space(pos); - auto& chunk = *pos.m_outer; - pos.m_inner = &*chunk.data.insert(pos.m_outer->data.begin() + pos.offset(), value); - chunk.count += value.second - value.first; - chunk.begin = std::min(chunk.begin, value.first); - chunk.end = std::max(chunk.end, value.second); - - verify(); - return pos; -} - -ChunkedRangeVector::iterator ChunkedRangeVector::ensure_space(iterator pos) -{ - if (pos.m_outer->data.size() + 1 <= max_size) - return pos; - - auto offset = pos.offset(); - - // Split the chunk in half to make space for the new insertion - auto new_pos = m_data.insert(pos.m_outer + 1, Chunk{}); - auto prev = new_pos - 1; - auto to_move = max_size / 2; - new_pos->data.reserve(to_move); - new_pos->data.assign(prev->data.end() - to_move, prev->data.end()); - prev->data.resize(prev->data.size() - to_move); - - size_t moved_count = 0; - for (auto range : new_pos->data) - moved_count += range.second - range.first; - - prev->end = prev->data.back().second; - prev->count -= moved_count; - new_pos->begin = new_pos->data.front().first; - new_pos->end = new_pos->data.back().second; - new_pos->count = moved_count; - - if (offset >= to_move) { - pos.m_outer = new_pos; - offset -= to_move; - } - else { - pos.m_outer = prev; - } - pos.m_end = m_data.end(); - pos.m_inner = &pos.m_outer->data[offset]; - verify(); - return pos; -} - -ChunkedRangeVector::iterator ChunkedRangeVector::erase(iterator pos) -{ - auto offset = pos.offset(); - auto& chunk = *pos.m_outer; - chunk.count -= pos->second - pos->first; - chunk.data.erase(chunk.data.begin() + offset); - - if (chunk.data.size() == 0) { - pos.m_outer = m_data.erase(pos.m_outer); - pos.m_end = m_data.end(); - pos.m_inner = pos.m_outer == m_data.end() ? nullptr : &pos.m_outer->data.front(); - verify(); - return pos; - } - - chunk.begin = chunk.data.front().first; - chunk.end = chunk.data.back().second; - if (offset < chunk.data.size()) - pos.m_inner = &chunk.data[offset]; - else { - ++pos.m_outer; - pos.m_inner = pos.m_outer == pos.m_end ? nullptr : &pos.m_outer->data.front(); - } - - verify(); - return pos; -} - -void ChunkedRangeVector::verify() const noexcept -{ -#ifdef REALM_DEBUG - size_t prev_end = -1; - for (auto range : *this) { - REALM_ASSERT(range.first < range.second); - REALM_ASSERT(prev_end == size_t(-1) || range.first > prev_end); - prev_end = range.second; - } - - for (auto& chunk : m_data) { - REALM_ASSERT(!chunk.data.empty()); - REALM_ASSERT(chunk.data.front().first == chunk.begin); - REALM_ASSERT(chunk.data.back().second == chunk.end); - REALM_ASSERT(chunk.count <= chunk.end - chunk.begin); - size_t count = 0; - for (auto range : chunk.data) - count += range.second - range.first; - REALM_ASSERT(count == chunk.count); - } -#endif -} - -namespace { -class ChunkedRangeVectorBuilder { -public: - using value_type = std::pair; - - ChunkedRangeVectorBuilder(ChunkedRangeVector const& expected); - void push_back(size_t index); - void push_back(std::pair range); - std::vector finalize(); -private: - std::vector m_data; - size_t m_outer_pos = 0; -}; - -ChunkedRangeVectorBuilder::ChunkedRangeVectorBuilder(ChunkedRangeVector const& expected) -{ - size_t size = 0; - for (auto const& chunk : expected.m_data) - size += chunk.data.size(); - m_data.resize(size / ChunkedRangeVector::max_size + 1); - for (size_t i = 0; i < m_data.size() - 1; ++i) - m_data[i].data.reserve(ChunkedRangeVector::max_size); -} - -void ChunkedRangeVectorBuilder::push_back(size_t index) -{ - push_back({index, index + 1}); -} - -void ChunkedRangeVectorBuilder::push_back(std::pair range) -{ - auto& chunk = m_data[m_outer_pos]; - if (chunk.data.empty()) { - chunk.data.push_back(range); - chunk.count = range.second - range.first; - chunk.begin = range.first; - } - else if (range.first == chunk.data.back().second) { - chunk.data.back().second = range.second; - chunk.count += range.second - range.first; - } - else if (chunk.data.size() < ChunkedRangeVector::max_size) { - chunk.data.push_back(range); - chunk.count += range.second - range.first; - } - else { - chunk.end = chunk.data.back().second; - ++m_outer_pos; - if (m_outer_pos >= m_data.size()) - m_data.push_back({{range}, range.first, 0, 1}); - else { - auto& chunk = m_data[m_outer_pos]; - chunk.data.push_back(range); - chunk.begin = range.first; - chunk.count = range.second - range.first; - } - } -} - -std::vector ChunkedRangeVectorBuilder::finalize() -{ - if (!m_data.empty()) { - m_data.resize(m_outer_pos + 1); - if (m_data.back().data.empty()) - m_data.pop_back(); - else - m_data.back().end = m_data.back().data.back().second; - } - return std::move(m_data); -} -} - -IndexSet::IndexSet(std::initializer_list values) -{ - for (size_t v : values) - add(v); -} - -bool IndexSet::contains(size_t index) const -{ - auto it = const_cast(this)->find(index); - return it != end() && it->first <= index; -} - -size_t IndexSet::count(size_t start_index, size_t end_index) const -{ - auto it = const_cast(this)->find(start_index); - const auto end = this->end(); - if (it == end || it->first >= end_index) { - return 0; - } - if (it->second >= end_index) - return std::min(it->second, end_index) - std::max(it->first, start_index); - - size_t ret = 0; - - if (start_index > it->first || it.offset() != 0) { - // Start index is in the middle of a chunk, so start by counting the - // rest of that chunk - ret = it->second - std::max(it->first, start_index); - for (++it; it != end && it->second < end_index && it.offset() != 0; ++it) { - ret += it->second - it->first; - } - if (it != end && it->first < end_index && it.offset() != 0) - ret += end_index - it->first; - if (it == end || it->second >= end_index) - return ret; - } - - // Now count all complete chunks that fall within the range - while (it != end && it.outer()->end <= end_index) { - REALM_ASSERT_DEBUG(it.offset() == 0); - ret += it.outer()->count; - it.next_chunk(); - } - - // Cound all complete ranges within the last chunk - while (it != end && it->second <= end_index) { - ret += it->second - it->first; - ++it; - } - - // And finally add in the partial last range - if (it != end && it->first < end_index) - ret += end_index - it->first; - return ret; -} - -IndexSet::iterator IndexSet::find(size_t index) -{ - return find(index, begin()); -} - -IndexSet::iterator IndexSet::find(size_t index, iterator begin) -{ - auto it = std::find_if(begin.outer(), m_data.end(), - [&](auto const& lft) { return lft.end > index; }); - if (it == m_data.end()) - return end(); - if (index < it->begin) - return iterator(it, m_data.end(), &it->data[0]); - auto inner_begin = it->data.begin(); - if (it == begin.outer()) - inner_begin += begin.offset(); - auto inner = std::lower_bound(inner_begin, it->data.end(), index, - [&](auto const& lft, auto) { return lft.second <= index; }); - REALM_ASSERT_DEBUG(inner != it->data.end()); - - return iterator(it, m_data.end(), &*inner); -} - -void IndexSet::add(size_t index) -{ - do_add(find(index), index); -} - -void IndexSet::add(IndexSet const& other) -{ - auto it = begin(); - for (size_t index : other.as_indexes()) { - it = do_add(find(index, it), index); - } -} - -size_t IndexSet::add_shifted(size_t index) -{ - iterator it = begin(), end = this->end(); - - // Shift for any complete chunks before the target - for (; it != end && it.outer()->end <= index; it.next_chunk()) - index += it.outer()->count; - - // And any ranges within the last partial chunk - for (; it != end && it->first <= index; ++it) - index += it->second - it->first; - - do_add(it, index); - return index; -} - -void IndexSet::add_shifted_by(IndexSet const& shifted_by, IndexSet const& values) -{ - if (values.empty()) - return; - -#ifdef REALM_DEBUG - size_t expected = std::distance(as_indexes().begin(), as_indexes().end()); - for (auto index : values.as_indexes()) { - if (!shifted_by.contains(index)) - ++expected; - } -#endif - - ChunkedRangeVectorBuilder builder(*this); - - auto old_it = cbegin(), old_end = cend(); - auto shift_it = shifted_by.cbegin(), shift_end = shifted_by.cend(); - - size_t skip_until = 0; - size_t old_shift = 0; - size_t new_shift = 0; - for (size_t index : values.as_indexes()) { - for (; shift_it != shift_end && shift_it->first <= index; ++shift_it) { - new_shift += shift_it->second - shift_it->first; - skip_until = shift_it->second; - } - if (index < skip_until) - continue; - - for (; old_it != old_end && old_it->first <= index - new_shift + old_shift; ++old_it) { - for (size_t i = old_it->first; i < old_it->second; ++i) - builder.push_back(i); - old_shift += old_it->second - old_it->first; - } - - REALM_ASSERT(index >= new_shift); - builder.push_back(index - new_shift + old_shift); - } - - copy(old_it, old_end, std::back_inserter(builder)); - m_data = builder.finalize(); - -#ifdef REALM_DEBUG - REALM_ASSERT((size_t)std::distance(as_indexes().begin(), as_indexes().end()) == expected); -#endif -} - -void IndexSet::set(size_t len) -{ - clear(); - if (len) { - push_back({0, len}); - } -} - -void IndexSet::insert_at(size_t index, size_t count) -{ - REALM_ASSERT(count > 0); - - auto pos = find(index); - auto end = this->end(); - bool in_existing = false; - if (pos != end) { - if (pos->first <= index) { - in_existing = true; - pos.adjust(0, count); - } - else { - pos.shift(count); - } - for (auto it = std::next(pos); it != end; ++it) - it.shift(count); - } - if (!in_existing) { - for (size_t i = 0; i < count; ++i) - pos = std::next(do_add(pos, index + i)); - } - - verify(); -} - -void IndexSet::insert_at(IndexSet const& positions) -{ - if (positions.empty()) - return; - if (empty()) { - *this = positions; - return; - } - - IndexIterator begin1 = cbegin(), begin2 = positions.cbegin(); - IndexIterator end1 = cend(), end2 = positions.cend(); - - ChunkedRangeVectorBuilder builder(*this); - size_t shift = 0; - while (begin1 != end1 && begin2 != end2) { - if (*begin1 + shift < *begin2) { - builder.push_back(*begin1++ + shift); - } - else { - ++shift; - builder.push_back(*begin2++); - } - } - for (; begin1 != end1; ++begin1) - builder.push_back(*begin1 + shift); - for (; begin2 != end2; ++begin2) - builder.push_back(*begin2); - - m_data = builder.finalize(); -} - -void IndexSet::shift_for_insert_at(size_t index, size_t count) -{ - REALM_ASSERT(count > 0); - - auto it = find(index); - if (it == end()) - return; - - for (auto pos = it, end = this->end(); pos != end; ++pos) - pos.shift(count); - - // If the range contained the insertion point, split the range and move - // the part of it before the insertion point back - if (it->first < index + count) { - auto old_second = it->second; - it.set(it->first - count, index); - insert(std::next(it), {index + count, old_second}); - } - verify(); -} - -void IndexSet::shift_for_insert_at(realm::IndexSet const& values) -{ - if (empty() || values.empty()) - return; - if (values.m_data.front().begin >= m_data.back().end) - return; - - IndexIterator begin1 = cbegin(), begin2 = values.cbegin(); - IndexIterator end1 = cend(), end2 = values.cend(); - - ChunkedRangeVectorBuilder builder(*this); - size_t shift = 0; - while (begin1 != end1 && begin2 != end2) { - if (*begin1 + shift < *begin2) { - builder.push_back(*begin1++ + shift); - } - else { - ++shift; - begin2++; - } - } - for (; begin1 != end1; ++begin1) - builder.push_back(*begin1 + shift); - - m_data = builder.finalize(); -} - -void IndexSet::erase_at(size_t index) -{ - auto it = find(index); - if (it != end()) - do_erase(it, index); -} - -void IndexSet::erase_at(IndexSet const& positions) -{ - if (empty() || positions.empty()) - return; - - ChunkedRangeVectorBuilder builder(*this); - - IndexIterator begin1 = cbegin(), begin2 = positions.cbegin(); - IndexIterator end1 = cend(), end2 = positions.cend(); - - size_t shift = 0; - while (begin1 != end1 && begin2 != end2) { - if (*begin1 < *begin2) { - builder.push_back(*begin1++ - shift); - } - else if (*begin1 == *begin2) { - ++shift; - ++begin1; - ++begin2; - } - else { - ++shift; - ++begin2; - } - } - for (; begin1 != end1; ++begin1) - builder.push_back(*begin1 - shift); - - m_data = builder.finalize(); -} - -size_t IndexSet::erase_or_unshift(size_t index) -{ - auto shifted = index; - iterator it = begin(), end = this->end(); - - // Shift for any complete chunks before the target - for (; it != end && it.outer()->end <= index; it.next_chunk()) - shifted -= it.outer()->count; - - // And any ranges within the last partial chunk - for (; it != end && it->second <= index; ++it) - shifted -= it->second - it->first; - - if (it == end) - return shifted; - - if (it->first <= index) - shifted = npos; - - do_erase(it, index); - - return shifted; -} - -void IndexSet::do_erase(iterator it, size_t index) -{ - if (it->first <= index) { - if (it->first + 1 == it->second) { - it = erase(it); - } - else { - it.adjust(0, -1); - ++it; - } - } - else if (it != begin() && std::prev(it)->second + 1 == it->first) { - std::prev(it).adjust(0, it->second - it->first); - it = erase(it); - } - - for (; it != end(); ++it) - it.shift(-1); -} - -IndexSet::iterator IndexSet::do_remove(iterator it, size_t begin, size_t end) -{ - for (it = find(begin, it); it != this->end() && it->first < end; it = find(begin, it)) { - // Trim off any part of the range to remove that's before the matching range - begin = std::max(it->first, begin); - - // If the matching range extends to both sides of the range to remove, - // split it on the range to remove - if (it->first < begin && it->second > end) { - auto old_second = it->second; - it.set(it->first, begin); - it = std::prev(insert(std::next(it), {end, old_second})); - } - // Range to delete now coverages (at least) one end of the matching range - else if (begin == it->first && end >= it->second) - it = erase(it); - else if (begin == it->first) - it.set(end, it->second); - else - it.set(it->first, begin); - } - return it; -} - -void IndexSet::remove(size_t index, size_t count) -{ - do_remove(find(index), index, index + count); -} - -void IndexSet::remove(realm::IndexSet const& values) -{ - auto it = begin(); - for (auto range : values) { - it = do_remove(it, range.first, range.second); - if (it == end()) - return; - } -} - -size_t IndexSet::shift(size_t index) const -{ - // FIXME: optimize - for (auto range : *this) { - if (range.first > index) - break; - index += range.second - range.first; - } - return index; -} - -size_t IndexSet::unshift(size_t index) const -{ - REALM_ASSERT_DEBUG(!contains(index)); - return index - count(0, index); -} - -void IndexSet::clear() -{ - m_data.clear(); -} - -IndexSet::iterator IndexSet::do_add(iterator it, size_t index) -{ - verify(); - bool more_before = it != begin(), valid = it != end(); - REALM_ASSERT(!more_before || index >= std::prev(it)->second); - if (valid && it->first <= index && it->second > index) { - // index is already in set - return it; - } - if (more_before && std::prev(it)->second == index) { - auto prev = std::prev(it); - // index is immediately after an existing range - prev.adjust(0, 1); - - if (valid && prev->second == it->first) { - // index joins two existing ranges - prev.adjust(0, it->second - it->first); - return std::prev(erase(it)); - } - return prev; - } - if (valid && it->first == index + 1) { - // index is immediately before an existing range - it.adjust(-1, 0); - return it; - } - - // index is not next to an existing range - return insert(it, {index, index + 1}); -} diff --git a/src/object-store/src/index_set.hpp b/src/object-store/src/index_set.hpp deleted file mode 100644 index 0cf00fd9..00000000 --- a/src/object-store/src/index_set.hpp +++ /dev/null @@ -1,326 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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_INDEX_SET_HPP -#define REALM_INDEX_SET_HPP - -#include -#include -#include -#include -#include -#include -#include - -namespace realm { -namespace _impl { -template -class MutableChunkedRangeVectorIterator; - -// An iterator for ChunkedRangeVector, templated on the vector iterator/const_iterator -template -class ChunkedRangeVectorIterator { -public: - using iterator_category = std::bidirectional_iterator_tag; - using value_type = typename std::remove_referencedata.begin())>::type; - using difference_type = ptrdiff_t; - using pointer = const value_type*; - using reference = const value_type&; - - ChunkedRangeVectorIterator(OuterIterator outer, OuterIterator end, value_type* inner) - : m_outer(outer), m_end(end), m_inner(inner) { } - - reference operator*() const { return *m_inner; } - pointer operator->() const { return m_inner; } - - template bool operator==(Other const& it) const; - template bool operator!=(Other const& it) const; - - ChunkedRangeVectorIterator& operator++(); - ChunkedRangeVectorIterator operator++(int); - - ChunkedRangeVectorIterator& operator--(); - ChunkedRangeVectorIterator operator--(int); - - // Advance directly to the next outer block - void next_chunk(); - - OuterIterator outer() const { return m_outer; } - size_t offset() const { return m_inner - &m_outer->data[0]; } - -private: - OuterIterator m_outer; - OuterIterator m_end; - value_type* m_inner; - friend struct ChunkedRangeVector; - friend class MutableChunkedRangeVectorIterator; -}; - -// A mutable iterator that adds some invariant-preserving mutation methods -template -class MutableChunkedRangeVectorIterator : public ChunkedRangeVectorIterator { -public: - using ChunkedRangeVectorIterator::ChunkedRangeVectorIterator; - - // Set this iterator to the given range and update the parent if needed - void set(size_t begin, size_t end); - // Adjust the begin and end of this iterator by the given amounts and - // update the parent if needed - void adjust(ptrdiff_t front, ptrdiff_t back); - // Shift this iterator by the given amount and update the parent if needed - void shift(ptrdiff_t distance); -}; - -// A vector which stores ranges in chunks with a maximum size -struct ChunkedRangeVector { - struct Chunk { - std::vector> data; - size_t begin; - size_t end; - size_t count; - }; - std::vector m_data; - - using value_type = std::pair; - using iterator = MutableChunkedRangeVectorIterator; - using const_iterator = ChunkedRangeVectorIterator; - -#ifdef REALM_DEBUG - static const size_t max_size = 4; -#else - static const size_t max_size = 4096 / sizeof(std::pair); -#endif - - iterator begin() { return empty() ? end() : iterator(m_data.begin(), m_data.end(), &m_data[0].data[0]); } - iterator end() { return iterator(m_data.end(), m_data.end(), nullptr); } - const_iterator begin() const { return cbegin(); } - const_iterator end() const { return cend(); } - const_iterator cbegin() const { return empty() ? cend() : const_iterator(m_data.cbegin(), m_data.end(), &m_data[0].data[0]); } - const_iterator cend() const { return const_iterator(m_data.end(), m_data.end(), nullptr); } - - bool empty() const noexcept { return m_data.empty(); } - - iterator insert(iterator pos, value_type value); - iterator erase(iterator pos); - void push_back(value_type value); - iterator ensure_space(iterator pos); - - void verify() const noexcept; -}; -} // namespace _impl - -class IndexSet : private _impl::ChunkedRangeVector { -public: - static const size_t npos = -1; - - using ChunkedRangeVector::value_type; - using ChunkedRangeVector::iterator; - using ChunkedRangeVector::const_iterator; - using ChunkedRangeVector::begin; - using ChunkedRangeVector::end; - using ChunkedRangeVector::empty; - using ChunkedRangeVector::verify; - - IndexSet() = default; - IndexSet(std::initializer_list); - - // Check if the index set contains the given index - bool contains(size_t index) const; - - // Counts the number of indices in the set in the given range - size_t count(size_t start_index=0, size_t end_index=-1) const; - - // Add an index to the set, doing nothing if it's already present - void add(size_t index); - void add(IndexSet const& is); - - // Add an index which has had all of the ranges in the set before it removed - // Returns the unshifted index - size_t add_shifted(size_t index); - // Add indexes which have had the ranges in `shifted_by` added and the ranges - // in the current set removed - void add_shifted_by(IndexSet const& shifted_by, IndexSet const& values); - - // Remove all indexes from the set and then add a single range starting from - // zero with the given length - void set(size_t len); - - // Insert an index at the given position, shifting existing indexes at or - // after that point back by one - void insert_at(size_t index, size_t count=1); - void insert_at(IndexSet const&); - - // Shift indexes at or after the given point back by one - void shift_for_insert_at(size_t index, size_t count=1); - void shift_for_insert_at(IndexSet const&); - - // Delete an index at the given position, shifting indexes after that point - // forward by one - void erase_at(size_t index); - void erase_at(IndexSet const&); - - // If the given index is in the set remove it and return npos; otherwise unshift() it - size_t erase_or_unshift(size_t index); - - // Remove the indexes at the given index from the set, without shifting - void remove(size_t index, size_t count=1); - void remove(IndexSet const&); - - // Shift an index by inserting each of the indexes in this set - size_t shift(size_t index) const; - // Shift an index by deleting each of the indexes in this set - size_t unshift(size_t index) const; - - // Remove all indexes from the set - void clear(); - - // An iterator over the individual indices in the set rather than the ranges - class IndexIterator : public std::iterator { - public: - IndexIterator(IndexSet::const_iterator it) : m_iterator(it) { } - size_t operator*() const { return m_iterator->first + m_offset; } - bool operator==(IndexIterator const& it) const { return m_iterator == it.m_iterator; } - bool operator!=(IndexIterator const& it) const { return m_iterator != it.m_iterator; } - - IndexIterator& operator++() - { - ++m_offset; - if (m_iterator->first + m_offset == m_iterator->second) { - ++m_iterator; - m_offset = 0; - } - return *this; - } - - IndexIterator operator++(int) - { - auto value = *this; - ++*this; - return value; - } - - private: - IndexSet::const_iterator m_iterator; - size_t m_offset = 0; - }; - - class IndexIteratableAdaptor { - public: - using value_type = size_t; - using iterator = IndexIterator; - using const_iterator = iterator; - - const_iterator begin() const { return m_index_set.begin(); } - const_iterator end() const { return m_index_set.end(); } - - IndexIteratableAdaptor(IndexSet const& is) : m_index_set(is) { } - private: - IndexSet const& m_index_set; - }; - - IndexIteratableAdaptor as_indexes() const { return *this; } - -private: - // Find the range which contains the index, or the first one after it if - // none do - iterator find(size_t index); - iterator find(size_t index, iterator it); - // Insert the index before the given position, combining existing ranges as - // applicable - // returns inserted position - iterator do_add(iterator pos, size_t index); - void do_erase(iterator it, size_t index); - iterator do_remove(iterator it, size_t index, size_t count); - - void shift_until_end_by(iterator begin, ptrdiff_t shift); -}; - -namespace util { -// This was added in C++14 but is missing from libstdc++ 4.9 -template -std::reverse_iterator make_reverse_iterator(Iterator it) -{ - return std::reverse_iterator(it); -} -} // namespace util - - -namespace _impl { -template -template -inline bool ChunkedRangeVectorIterator::operator==(OtherIterator const& it) const -{ - return m_outer == it.outer() && m_inner == it.operator->(); -} - -template -template -inline bool ChunkedRangeVectorIterator::operator!=(OtherIterator const& it) const -{ - return !(*this == it); -} - -template -inline ChunkedRangeVectorIterator& ChunkedRangeVectorIterator::operator++() -{ - ++m_inner; - if (offset() == m_outer->data.size()) - next_chunk(); - return *this; -} - -template -inline ChunkedRangeVectorIterator ChunkedRangeVectorIterator::operator++(int) -{ - auto value = *this; - ++*this; - return value; -} - -template -inline ChunkedRangeVectorIterator& ChunkedRangeVectorIterator::operator--() -{ - if (!m_inner || m_inner == &m_outer->data.front()) { - --m_outer; - m_inner = &m_outer->data.back(); - } - else { - --m_inner; - } - return *this; -} - -template -inline ChunkedRangeVectorIterator ChunkedRangeVectorIterator::operator--(int) -{ - auto value = *this; - --*this; - return value; -} - -template -inline void ChunkedRangeVectorIterator::next_chunk() -{ - ++m_outer; - m_inner = m_outer != m_end ? &m_outer->data[0] : nullptr; -} -} // namespace _impl - -} // namespace realm - -#endif // REALM_INDEX_SET_HPP diff --git a/src/object-store/src/list.cpp b/src/object-store/src/list.cpp deleted file mode 100644 index 8ed098cc..00000000 --- a/src/object-store/src/list.cpp +++ /dev/null @@ -1,229 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "list.hpp" - -#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" - -#include - -using namespace realm; -using namespace realm::_impl; - -List::List() noexcept = default; -List::~List() = default; - -List::List(const List&) = default; -List& List::operator=(const List&) = default; -List::List(List&&) = default; -List& List::operator=(List&&) = default; - -List::List(std::shared_ptr r, LinkViewRef l) noexcept -: m_realm(std::move(r)) -, 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(); - return m_link_view->get_target_table().where(m_link_view); -} - -size_t List::get_origin_row_index() const -{ - verify_attached(); - return m_link_view->get_origin_row_index(); -} - -void List::verify_valid_row(size_t row_ndx, bool insertion) const -{ - size_t size = m_link_view->size(); - if (row_ndx > size || (!insertion && row_ndx == size)) { - throw OutOfBoundsIndexException{row_ndx, size + insertion}; - } -} - -bool List::is_valid() const -{ - m_realm->verify_thread(); - return m_link_view && m_link_view->is_attached(); -} - -void List::verify_attached() const -{ - if (!is_valid()) { - throw InvalidatedException(); - } -} - -void List::verify_in_transaction() const -{ - verify_attached(); - if (!m_realm->is_in_transaction()) { - throw InvalidTransactionException("Must be in a write transaction"); - } -} - -size_t List::size() const -{ - verify_attached(); - return m_link_view->size(); -} - -RowExpr List::get(size_t row_ndx) const -{ - verify_attached(); - verify_valid_row(row_ndx); - return m_link_view->get(row_ndx); -} - -size_t List::get_unchecked(size_t row_ndx) const noexcept -{ - return m_link_view->get(row_ndx).get_index(); -} - -size_t List::find(ConstRow const& row) const -{ - verify_attached(); - - if (!row.is_attached() || row.get_table() != &m_link_view->get_target_table()) { - return not_found; - } - - return m_link_view->find(row.get_index()); -} - -void List::add(size_t target_row_ndx) -{ - verify_in_transaction(); - m_link_view->add(target_row_ndx); -} - -void List::insert(size_t row_ndx, size_t target_row_ndx) -{ - verify_in_transaction(); - verify_valid_row(row_ndx, true); - m_link_view->insert(row_ndx, target_row_ndx); -} - -void List::move(size_t source_ndx, size_t dest_ndx) -{ - verify_in_transaction(); - verify_valid_row(source_ndx); - verify_valid_row(dest_ndx); // Can't be one past end due to removing one earlier - m_link_view->move(source_ndx, dest_ndx); -} - -void List::remove(size_t row_ndx) -{ - verify_in_transaction(); - verify_valid_row(row_ndx); - m_link_view->remove(row_ndx); -} - -void List::remove_all() -{ - verify_in_transaction(); - m_link_view->clear(); -} - -void List::set(size_t row_ndx, size_t target_row_ndx) -{ - verify_in_transaction(); - verify_valid_row(row_ndx); - m_link_view->set(row_ndx, target_row_ndx); -} - -void List::swap(size_t ndx1, size_t ndx2) -{ - verify_in_transaction(); - verify_valid_row(ndx1); - verify_valid_row(ndx2); - m_link_view->swap(ndx1, ndx2); -} - -void List::delete_all() -{ - verify_in_transaction(); - m_link_view->remove_all_target_rows(); -} - -Results List::sort(SortOrder order) -{ - verify_attached(); - return Results(m_realm, m_link_view, util::none, std::move(order)); -} - -Results List::filter(Query q) -{ - verify_attached(); - 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 -bool List::operator==(List const& rgt) const noexcept -{ - return m_link_view.get() == rgt.m_link_view.get(); -} - -namespace std { -size_t hash::operator()(realm::List const& list) const -{ - return std::hash()(list.m_link_view.get()); -} -} - -NotificationToken List::add_notification_callback(CollectionChangeCallback cb) -{ - verify_attached(); - if (!m_notifier) { - m_notifier = std::make_shared(m_link_view, m_realm); - RealmCoordinator::register_notifier(m_notifier); - } - return {m_notifier, m_notifier->add_callback(std::move(cb))}; -} - -List::OutOfBoundsIndexException::OutOfBoundsIndexException(size_t r, size_t c) -: std::out_of_range(util::format("Requested index %1 greater than max %2", r, c)) -, requested(r), valid_count(c) {} diff --git a/src/object-store/src/list.hpp b/src/object-store/src/list.hpp deleted file mode 100644 index ca75fd54..00000000 --- a/src/object-store/src/list.hpp +++ /dev/null @@ -1,127 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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_LIST_HPP -#define REALM_LIST_HPP - -#include "collection_notifications.hpp" -#include "impl/collection_notifier.hpp" - -#include -#include - -#include -#include - -namespace realm { -using RowExpr = BasicRowExpr; - -class ObjectSchema; -class Query; -class Realm; -class Results; -struct SortOrder; - -class List { -public: - List() noexcept; - List(std::shared_ptr r, LinkViewRef l) noexcept; - ~List(); - - List(const List&); - List& operator=(const List&); - List(List&&); - List& operator=(List&&); - - const std::shared_ptr& get_realm() const { return m_realm; } - Query get_query() const; - const ObjectSchema& get_object_schema() const; - size_t get_origin_row_index() const; - - bool is_valid() const; - void verify_attached() const; - void verify_in_transaction() const; - - size_t size() const; - RowExpr get(size_t row_ndx) const; - size_t get_unchecked(size_t row_ndx) const noexcept; - size_t find(ConstRow const& row) const; - - void add(size_t target_row_ndx); - void insert(size_t list_ndx, size_t target_row_ndx); - void move(size_t source_ndx, size_t dest_ndx); - void remove(size_t list_ndx); - void remove_all(); - void set(size_t row_ndx, size_t target_row_ndx); - void swap(size_t ndx1, size_t ndx2); - - void delete_all(); - - 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); - - // These are implemented in object_accessor.hpp - template - void add(ContextType ctx, ValueType value); - - template - void insert(ContextType ctx, ValueType value, size_t list_ndx); - - template - void set(ContextType ctx, ValueType value, size_t list_ndx); - - // 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::logic_error { - InvalidatedException() : std::logic_error("Access to invalidated List object") {} - }; - - // The input index parameter was out of bounds - struct OutOfBoundsIndexException : public std::out_of_range { - OutOfBoundsIndexException(size_t r, size_t c); - size_t requested; - size_t valid_count; - }; - -private: - std::shared_ptr m_realm; - mutable const ObjectSchema* m_object_schema = nullptr; - LinkViewRef m_link_view; - _impl::CollectionNotifier::Handle<_impl::CollectionNotifier> m_notifier; - - void verify_valid_row(size_t row_ndx, bool insertion = false) const; - - friend struct std::hash; -}; -} // namespace realm - -namespace std { -template<> struct hash { - size_t operator()(realm::List const&) const; -}; -} - -#endif /* REALM_LIST_HPP */ diff --git a/src/object-store/src/object_accessor.hpp b/src/object-store/src/object_accessor.hpp deleted file mode 100644 index a19608a6..00000000 --- a/src/object-store/src/object_accessor.hpp +++ /dev/null @@ -1,434 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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_OBJECT_ACCESSOR_HPP -#define REALM_OBJECT_ACCESSOR_HPP - -#include "list.hpp" -#include "object_schema.hpp" -#include "object_store.hpp" -#include "results.hpp" -#include "schema.hpp" -#include "shared_realm.hpp" -#include "util/format.hpp" - -#include -#include -#include - -namespace realm { - - class Object { - public: - Object(SharedRealm r, const ObjectSchema &s, Row o) : m_realm(r), m_object_schema(&s), m_row(o) {} - - // property getter/setter - template - inline void set_property_value(ContextType ctx, std::string prop_name, ValueType value, bool try_update); - - template - inline ValueType get_property_value(ContextType ctx, std::string prop_name); - - // create an Object from a native representation - template - static inline Object create(ContextType ctx, SharedRealm realm, const ObjectSchema &object_schema, ValueType value, bool try_update); - - template - static Object get_for_primary_key(ContextType ctx, SharedRealm realm, const ObjectSchema &object_schema, ValueType primary_value); - - SharedRealm realm() { return m_realm; } - const ObjectSchema &get_object_schema() { return *m_object_schema; } - Row row() { return m_row; } - - bool is_valid() const { return m_row.is_attached(); } - - private: - SharedRealm m_realm; - const ObjectSchema *m_object_schema; - Row m_row; - - template - inline void set_property_value_impl(ContextType ctx, const Property &property, ValueType value, bool try_update); - template - inline ValueType get_property_value_impl(ContextType ctx, const Property &property); - - template - static size_t get_for_primary_key_impl(ContextType ctx, const ConstTableRef &table, const Property &primary_prop, ValueType primary_value); - - inline void verify_attached(); - }; - - // - // Value converters - template specializations must be implemented for each platform in order to call templated methods on Object - // - template - class NativeAccessor { - public: - static bool dict_has_value_for_key(ContextType ctx, ValueType dict, const std::string &prop_name); - static ValueType dict_value_for_key(ContextType ctx, ValueType dict, const std::string &prop_name); - - static bool has_default_value_for_property(ContextType ctx, Realm *realm, const ObjectSchema &object_schema, const std::string &prop_name); - static ValueType default_value_for_property(ContextType ctx, Realm *realm, const ObjectSchema &object_schema, const std::string &prop_name); - - static bool to_bool(ContextType, ValueType &); - static ValueType from_bool(ContextType, bool); - static long long to_long(ContextType, ValueType &); - static ValueType from_long(ContextType, long long); - static float to_float(ContextType, ValueType &); - static ValueType from_float(ContextType, float); - static double to_double(ContextType, ValueType &); - static ValueType from_double(ContextType, double); - static std::string to_string(ContextType, ValueType &); - static ValueType from_string(ContextType, StringData); - static std::string to_binary(ContextType, ValueType &); - static ValueType from_binary(ContextType, BinaryData); - static Timestamp to_timestamp(ContextType, ValueType &); - static ValueType from_timestamp(ContextType, Timestamp); - - static bool is_null(ContextType, ValueType &); - static ValueType null_value(ContextType); - - // convert value to persisted object - // for existing objects return the existing row index - // for new/updated objects return the row index - static size_t to_object_index(ContextType ctx, SharedRealm realm, ValueType &val, const std::string &type, bool try_update); - static ValueType from_object(ContextType ctx, Object); - - // object index for an existing object - static size_t to_existing_object_index(ContextType ctx, SharedRealm realm, ValueType &val); - - // list value accessors - static size_t list_size(ContextType ctx, ValueType &val); - static ValueType list_value_at_index(ContextType ctx, ValueType &val, size_t index); - static ValueType from_list(ContextType ctx, List); - - // results value accessors - static ValueType from_results(ContextType ctx, Results); - - // - // Deprecated - // - static Mixed to_mixed(ContextType, ValueType&) { throw std::logic_error("'Any' type is unsupported"); } - }; - - 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; - }; - - 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; - }; - - 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; - }; - - 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; - }; - - 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; - }; - - struct MutationOutsideTransactionException : public std::logic_error { - MutationOutsideTransactionException(const std::string& message) : std::logic_error(message) {} - }; - - // - // template method implementations - // - template - inline void Object::set_property_value(ContextType ctx, std::string prop_name, ValueType value, bool try_update) - { - const Property *prop = m_object_schema->property_for_name(prop_name); - if (!prop) { - throw InvalidPropertyException(m_object_schema->name, prop_name, - "Setting invalid property '" + prop_name + "' on object '" + m_object_schema->name + "'."); - } - set_property_value_impl(ctx, *prop, value, try_update); - } - - template - inline ValueType Object::get_property_value(ContextType ctx, std::string prop_name) - { - const Property *prop = m_object_schema->property_for_name(prop_name); - if (!prop) { - throw InvalidPropertyException(m_object_schema->name, prop_name, - "Getting invalid property '" + prop_name + "' on object '" + m_object_schema->name + "'."); - } - return get_property_value_impl(ctx, *prop); - } - - template - inline void Object::set_property_value_impl(ContextType ctx, const Property &property, ValueType value, bool try_update) - { - using Accessor = NativeAccessor; - - verify_attached(); - - if (!m_realm->is_in_transaction()) { - throw MutationOutsideTransactionException("Can only set property values within a transaction."); - } - - size_t column = property.table_column; - if (property.is_nullable && Accessor::is_null(ctx, value)) { - if (property.type == PropertyType::Object) { - m_row.nullify_link(column); - } - else { - m_row.set_null(column); - } - return; - } - - switch (property.type) { - case PropertyType::Bool: - m_row.set_bool(column, Accessor::to_bool(ctx, value)); - break; - case PropertyType::Int: - m_row.set_int(column, Accessor::to_long(ctx, value)); - break; - case PropertyType::Float: - m_row.set_float(column, Accessor::to_float(ctx, value)); - break; - case PropertyType::Double: - m_row.set_double(column, Accessor::to_double(ctx, value)); - break; - case PropertyType::String: { - auto string_value = Accessor::to_string(ctx, value); - m_row.set_string(column, string_value); - break; - } - case PropertyType::Data: - m_row.set_binary(column, BinaryData(Accessor::to_binary(ctx, value))); - break; - case PropertyType::Any: - m_row.set_mixed(column, Accessor::to_mixed(ctx, value)); - break; - case PropertyType::Date: - m_row.set_timestamp(column, Accessor::to_timestamp(ctx, value)); - break; - case PropertyType::Object: { - if (Accessor::is_null(ctx, value)) { - m_row.nullify_link(column); - } - else { - m_row.set_link(column, Accessor::to_object_index(ctx, m_realm, value, property.object_type, try_update)); - } - break; - } - case PropertyType::Array: { - realm::LinkViewRef link_view = m_row.get_linklist(column); - link_view->clear(); - if (!Accessor::is_null(ctx, value)) { - size_t count = Accessor::list_size(ctx, value); - for (size_t i = 0; i < count; i++) { - ValueType element = Accessor::list_value_at_index(ctx, value, i); - link_view->add(Accessor::to_object_index(ctx, m_realm, element, property.object_type, try_update)); - } - } - break; - } - case PropertyType::LinkingObjects: - throw ReadOnlyPropertyException(m_object_schema->name, property.name, - util::format("Cannot modify read-only property '%1.%2'", - m_object_schema->name, property.name)); - } - } - - template - inline ValueType Object::get_property_value_impl(ContextType ctx, const Property &property) - { - using Accessor = NativeAccessor; - - verify_attached(); - - size_t column = property.table_column; - if (property.is_nullable && m_row.is_null(column)) { - return Accessor::null_value(ctx); - } - - switch (property.type) { - case PropertyType::Bool: - return Accessor::from_bool(ctx, m_row.get_bool(column)); - case PropertyType::Int: - return Accessor::from_long(ctx, m_row.get_int(column)); - case PropertyType::Float: - return Accessor::from_float(ctx, m_row.get_float(column)); - case PropertyType::Double: - return Accessor::from_double(ctx, m_row.get_double(column)); - case PropertyType::String: - return Accessor::from_string(ctx, m_row.get_string(column)); - case PropertyType::Data: - return Accessor::from_binary(ctx, m_row.get_binary(column)); - case PropertyType::Any: - throw "Any not supported"; - case PropertyType::Date: - return Accessor::from_timestamp(ctx, m_row.get_timestamp(column)); - case PropertyType::Object: { - auto linkObjectSchema = m_realm->config().schema->find(property.object_type); - TableRef table = ObjectStore::table_for_object_type(m_realm->read_group(), linkObjectSchema->name); - if (m_row.is_null_link(property.table_column)) { - return Accessor::null_value(ctx); - } - return Accessor::from_object(ctx, std::move(Object(m_realm, *linkObjectSchema, table->get(m_row.get_link(column))))); - } - case PropertyType::Array: { - 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, std::move(tv), {}); - return Accessor::from_results(ctx, std::move(results)); - } - } - } - - template - inline Object Object::create(ContextType ctx, SharedRealm realm, const ObjectSchema &object_schema, ValueType value, bool try_update) - { - using Accessor = NativeAccessor; - - if (!realm->is_in_transaction()) { - throw MutationOutsideTransactionException("Can only create objects within a transaction."); - } - - // get or create our accessor - bool created; - - // try to get existing row if updating - size_t row_index = realm::not_found; - realm::TableRef table = ObjectStore::table_for_object_type(realm->read_group(), object_schema.name); - const Property *primary_prop = object_schema.primary_key_property(); - - if (primary_prop) { - // search for existing object based on primary key type - ValueType primary_value = Accessor::dict_value_for_key(ctx, value, object_schema.primary_key); - row_index = get_for_primary_key_impl(ctx, table, *primary_prop, primary_value); - - if (!try_update && row_index != realm::not_found) { - throw DuplicatePrimaryKeyValueException(object_schema.name, *primary_prop, - "Attempting to create an object of type '" + object_schema.name + "' with an existing primary key value."); - } - } - - // if no existing, create row - created = false; - if (row_index == realm::not_found) { - row_index = table->add_empty_row(); - created = true; - } - - // populate - Object object(realm, object_schema, table->get(row_index)); - for (const Property& prop : object_schema.persisted_properties) { - if (created || !prop.is_primary) { - if (Accessor::dict_has_value_for_key(ctx, value, prop.name)) { - object.set_property_value_impl(ctx, prop, Accessor::dict_value_for_key(ctx, value, prop.name), try_update); - } - else if (created) { - if (Accessor::has_default_value_for_property(ctx, realm.get(), object_schema, prop.name)) { - object.set_property_value_impl(ctx, prop, Accessor::default_value_for_property(ctx, realm.get(), object_schema, prop.name), try_update); - } - else if (prop.is_nullable || prop.type == PropertyType::Array) { - object.set_property_value_impl(ctx, prop, Accessor::null_value(ctx), try_update); - } - else { - throw MissingPropertyValueException(object_schema.name, prop.name, - "Missing property value for property " + prop.name); - } - } - } - } - return object; - } - - template - inline Object Object::get_for_primary_key(ContextType ctx, SharedRealm realm, const ObjectSchema &object_schema, ValueType primary_value) - { - auto primary_prop = object_schema.primary_key_property(); - if (!primary_prop) { - throw MissingPrimaryKeyException(object_schema.name, object_schema.name + " does not have a primary key"); - } - - auto table = ObjectStore::table_for_object_type(realm->read_group(), object_schema.name); - auto row_index = get_for_primary_key_impl(ctx, table, *primary_prop, primary_value); - - return Object(realm, object_schema, row_index == realm::not_found ? Row() : table->get(row_index)); - } - - template - inline size_t Object::get_for_primary_key_impl(ContextType ctx, const ConstTableRef &table, const Property &primary_prop, ValueType primary_value) { - using Accessor = NativeAccessor; - - if (primary_prop.type == PropertyType::String) { - auto primary_string = Accessor::to_string(ctx, primary_value); - return table->find_first_string(primary_prop.table_column, primary_string); - } - else { - return table->find_first_int(primary_prop.table_column, Accessor::to_long(ctx, primary_value)); - } - } - - inline void Object::verify_attached() { - if (!m_row.is_attached()) { - throw InvalidatedObjectException(m_object_schema->name, - "Accessing object of type " + m_object_schema->name + " which has been deleted" - ); - } - } - - // - // List implementation - // - template - void List::add(ContextType ctx, ValueType value) - { - add(NativeAccessor::to_object_index(ctx, m_realm, value, get_object_schema().name, false)); - } - - template - void List::insert(ContextType ctx, ValueType value, size_t list_ndx) - { - insert(list_ndx, NativeAccessor::to_object_index(ctx, m_realm, value, get_object_schema().name, false)); - } - - template - void List::set(ContextType ctx, ValueType value, size_t list_ndx) - { - set(list_ndx, NativeAccessor::to_object_index(ctx, m_realm, value, get_object_schema().name, false)); - } -} - -#endif /* defined(REALM_OBJECT_ACCESSOR_HPP) */ diff --git a/src/object-store/src/object_schema.cpp b/src/object-store/src/object_schema.cpp deleted file mode 100644 index 6eb819d4..00000000 --- a/src/object-store/src/object_schema.cpp +++ /dev/null @@ -1,106 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "object_schema.hpp" - -#include "object_store.hpp" -#include "property.hpp" - -#include -#include - -using namespace realm; - -#define ASSERT_PROPERTY_TYPE_VALUE(property, type) \ - static_assert(static_cast(PropertyType::property) == type_##type, \ - "PropertyType and DataType must have the same values") - -ASSERT_PROPERTY_TYPE_VALUE(Int, Int); -ASSERT_PROPERTY_TYPE_VALUE(Bool, Bool); -ASSERT_PROPERTY_TYPE_VALUE(Float, Float); -ASSERT_PROPERTY_TYPE_VALUE(Double, Double); -ASSERT_PROPERTY_TYPE_VALUE(Data, Binary); -ASSERT_PROPERTY_TYPE_VALUE(Date, Timestamp); -ASSERT_PROPERTY_TYPE_VALUE(Any, Mixed); -ASSERT_PROPERTY_TYPE_VALUE(Object, Link); -ASSERT_PROPERTY_TYPE_VALUE(Array, LinkList); - -ObjectSchema::ObjectSchema() = default; -ObjectSchema::~ObjectSchema() = default; - -ObjectSchema::ObjectSchema(std::string name, std::string primary_key, std::initializer_list persisted_properties) -: name(std::move(name)) -, persisted_properties(persisted_properties) -, primary_key(std::move(primary_key)) -{ - set_primary_key_property(); -} - -ObjectSchema::ObjectSchema(const Group *group, const std::string &name) : name(name) { - ConstTableRef table = ObjectStore::table_for_object_type(group, name); - - size_t count = table->get_column_count(); - persisted_properties.reserve(count); - for (size_t col = 0; col < count; col++) { - Property property; - property.name = table->get_column_name(col).data(); - property.type = (PropertyType)table->get_column_type(col); - property.is_indexed = table->has_search_index(col); - property.is_primary = false; - property.is_nullable = table->is_nullable(col) || property.type == PropertyType::Object; - property.table_column = col; - if (property.type == PropertyType::Object || property.type == PropertyType::Array) { - // set link type for objects and arrays - ConstTableRef linkTable = table->get_link_target(col); - property.object_type = ObjectStore::object_type_for_table_name(linkTable->get_name().data()); - } - persisted_properties.push_back(std::move(property)); - } - - primary_key = realm::ObjectStore::get_primary_key_for_object(group, name); - set_primary_key_property(); -} - -Property *ObjectSchema::property_for_name(StringData name) { - for (auto& prop : persisted_properties) { - if (StringData(prop.name) == name) { - return ∝ - } - } - for (auto& prop : computed_properties) { - if (StringData(prop.name) == name) { - return ∝ - } - } - return nullptr; -} - -const Property *ObjectSchema::property_for_name(StringData name) const { - return const_cast(this)->property_for_name(name); -} - -void ObjectSchema::set_primary_key_property() -{ - if (primary_key.length()) { - auto primary_key_prop = primary_key_property(); - if (!primary_key_prop) { - throw InvalidPrimaryKeyException(name, primary_key); - } - primary_key_prop->is_primary = true; - } -} diff --git a/src/object-store/src/object_schema.hpp b/src/object-store/src/object_schema.hpp deleted file mode 100644 index 71d56d69..00000000 --- a/src/object-store/src/object_schema.hpp +++ /dev/null @@ -1,60 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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_OBJECT_SCHEMA_HPP -#define REALM_OBJECT_SCHEMA_HPP - -#include - -#include -#include - -namespace realm { -class Group; -struct Property; - -class ObjectSchema { -public: - ObjectSchema(); - ObjectSchema(std::string name, std::string primary_key, std::initializer_list persisted_properties); - ~ObjectSchema(); - - // create object schema from existing table - // if no table is provided it is looked up in the group - ObjectSchema(const Group *group, const std::string &name); - - std::string name; - std::vector persisted_properties; - std::vector computed_properties; - std::string primary_key; - - Property *property_for_name(StringData name); - const Property *property_for_name(StringData name) const; - Property *primary_key_property() { - return property_for_name(primary_key); - } - const Property *primary_key_property() const { - return property_for_name(primary_key); - } - -private: - void set_primary_key_property(); -}; -} - -#endif /* defined(REALM_OBJECT_SCHEMA_HPP) */ diff --git a/src/object-store/src/object_store.cpp b/src/object-store/src/object_store.cpp deleted file mode 100644 index 3d80e765..00000000 --- a/src/object-store/src/object_store.cpp +++ /dev/null @@ -1,664 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "object_store.hpp" - -#include "object_schema.hpp" -#include "schema.hpp" -#include "util/format.hpp" - -#include -#include -#include -#include - -#include - -using namespace realm; - -namespace { -const char * const c_metadataTableName = "metadata"; -const char * const c_versionColumnName = "version"; -const size_t c_versionColumnIndex = 0; - -const char * const c_primaryKeyTableName = "pk"; -const char * const c_primaryKeyObjectClassColumnName = "pk_table"; -const size_t c_primaryKeyObjectClassColumnIndex = 0; -const char * const c_primaryKeyPropertyNameColumnName = "pk_property"; -const size_t c_primaryKeyPropertyNameColumnIndex = 1; - -const size_t c_zeroRowIndex = 0; - -const char c_object_table_prefix[] = "class_"; -} - -const uint64_t ObjectStore::NotVersioned = std::numeric_limits::max(); - -bool ObjectStore::has_metadata_tables(const Group *group) { - return group->get_table(c_primaryKeyTableName) && group->get_table(c_metadataTableName); -} - -void ObjectStore::create_metadata_tables(Group *group) { - TableRef table = group->get_or_add_table(c_primaryKeyTableName); - if (table->get_column_count() == 0) { - table->add_column(type_String, c_primaryKeyObjectClassColumnName); - table->add_column(type_String, c_primaryKeyPropertyNameColumnName); - } - - table = group->get_or_add_table(c_metadataTableName); - if (table->get_column_count() == 0) { - table->add_column(type_Int, c_versionColumnName); - - // set initial version - table->add_empty_row(); - table->set_int(c_versionColumnIndex, c_zeroRowIndex, ObjectStore::NotVersioned); - } -} - -uint64_t ObjectStore::get_schema_version(const Group *group) { - ConstTableRef table = group->get_table(c_metadataTableName); - if (!table || table->get_column_count() == 0) { - return ObjectStore::NotVersioned; - } - return table->get_int(c_versionColumnIndex, c_zeroRowIndex); -} - -void ObjectStore::set_schema_version(Group *group, uint64_t version) { - TableRef table = group->get_or_add_table(c_metadataTableName); - table->set_int(c_versionColumnIndex, c_zeroRowIndex, version); -} - -StringData ObjectStore::get_primary_key_for_object(const Group *group, StringData object_type) { - ConstTableRef table = group->get_table(c_primaryKeyTableName); - if (!table) { - return ""; - } - size_t row = table->find_first_string(c_primaryKeyObjectClassColumnIndex, object_type); - if (row == not_found) { - return ""; - } - return table->get_string(c_primaryKeyPropertyNameColumnIndex, row); -} - -void ObjectStore::set_primary_key_for_object(Group *group, StringData object_type, StringData primary_key) { - TableRef table = group->get_table(c_primaryKeyTableName); - - // get row or create if new object and populate - size_t row = table->find_first_string(c_primaryKeyObjectClassColumnIndex, object_type); - if (row == not_found && primary_key.size()) { - row = table->add_empty_row(); - table->set_string(c_primaryKeyObjectClassColumnIndex, row, object_type); - } - - // set if changing, or remove if setting to nil - if (primary_key.size() == 0) { - if (row != not_found) { - table->remove(row); - } - } - else { - table->set_string(c_primaryKeyPropertyNameColumnIndex, row, primary_key); - } -} - -StringData ObjectStore::object_type_for_table_name(StringData table_name) { - if (table_name.begins_with(c_object_table_prefix)) { - return table_name.substr(sizeof(c_object_table_prefix) - 1); - } - return StringData(); -} - -std::string ObjectStore::table_name_for_object_type(StringData object_type) { - return std::string(c_object_table_prefix) + static_cast(object_type); -} - -TableRef ObjectStore::table_for_object_type(Group *group, StringData object_type) { - auto name = table_name_for_object_type(object_type); - return group->get_table(name); -} - -ConstTableRef ObjectStore::table_for_object_type(const Group *group, StringData object_type) { - auto name = table_name_for_object_type(object_type); - return group->get_table(name); -} - -TableRef ObjectStore::table_for_object_type_create_if_needed(Group *group, StringData object_type, bool &created) { - auto name = table_name_for_object_type(object_type); - return group->get_or_add_table(name, &created); -} - -static inline bool property_has_changed(Property const& p1, Property const& p2) { - return p1.type != p2.type - || p1.name != p2.name - || p1.object_type != p2.object_type - || p1.is_nullable != p2.is_nullable; -} - -static inline bool property_can_be_migrated_to_nullable(const Property& old_property, const Property& new_property) { - return old_property.type == new_property.type - && !old_property.is_nullable - && new_property.is_nullable - && new_property.name == old_property.name; -} - -void ObjectStore::verify_schema(Schema const& actual_schema, Schema& target_schema, bool allow_missing_tables) { - std::vector errors; - for (auto &object_schema : target_schema) { - auto matching_schema = actual_schema.find(object_schema); - if (matching_schema == actual_schema.end()) { - if (!allow_missing_tables) { - errors.emplace_back(object_schema.name, - util::format("Missing table for object type '%1'.", object_schema.name)); - } - continue; - } - - auto more_errors = verify_object_schema(*matching_schema, object_schema); - errors.insert(errors.end(), more_errors.begin(), more_errors.end()); - } - if (errors.size()) { - throw SchemaMismatchException(errors); - } -} - -std::vector ObjectStore::verify_object_schema(ObjectSchema const& table_schema, - ObjectSchema& target_schema) { - std::vector exceptions; - - // check to see if properties are the same - for (auto& current_prop : table_schema.persisted_properties) { - auto target_prop = target_schema.property_for_name(current_prop.name); - - if (!target_prop) { - exceptions.emplace_back(MissingPropertyException(table_schema.name, current_prop)); - continue; - } - if (property_has_changed(current_prop, *target_prop)) { - exceptions.emplace_back(MismatchedPropertiesException(table_schema.name, current_prop, *target_prop)); - continue; - } - - // create new property with aligned column - target_prop->table_column = current_prop.table_column; - } - - // check for change to primary key - if (table_schema.primary_key != target_schema.primary_key) { - exceptions.emplace_back(ChangedPrimaryKeyException(table_schema.name, table_schema.primary_key, target_schema.primary_key)); - } - - // check for new missing properties - for (auto& target_prop : target_schema.persisted_properties) { - if (!table_schema.property_for_name(target_prop.name)) { - exceptions.emplace_back(ExtraPropertyException(table_schema.name, target_prop)); - } - } - - return exceptions; -} - -template -static void copy_property_values(const Property& old_property, const Property& new_property, Table& table, - T (Table::*getter)(std::size_t, std::size_t) const noexcept, - void (Table::*setter)(std::size_t, std::size_t, T)) { - size_t old_column = old_property.table_column, new_column = new_property.table_column; - size_t count = table.size(); - for (size_t i = 0; i < count; i++) { - (table.*setter)(new_column, i, (table.*getter)(old_column, i)); - } -} - -static void copy_property_values(const Property& source, const Property& destination, Table& table) { - switch (destination.type) { - case PropertyType::Int: - copy_property_values(source, destination, table, &Table::get_int, &Table::set_int); - break; - case PropertyType::Bool: - copy_property_values(source, destination, table, &Table::get_bool, &Table::set_bool); - break; - case PropertyType::Float: - copy_property_values(source, destination, table, &Table::get_float, &Table::set_float); - break; - case PropertyType::Double: - copy_property_values(source, destination, table, &Table::get_double, &Table::set_double); - break; - case PropertyType::String: - copy_property_values(source, destination, table, &Table::get_string, &Table::set_string); - break; - case PropertyType::Data: - copy_property_values(source, destination, table, &Table::get_binary, &Table::set_binary); - break; - case PropertyType::Date: - copy_property_values(source, destination, table, &Table::get_timestamp, &Table::set_timestamp); - break; - default: - break; - } -} - -// set references to tables on targetSchema and create/update any missing or out-of-date tables -// if update existing is true, updates existing tables, otherwise validates existing tables -// NOTE: must be called from within write transaction -void ObjectStore::create_tables(Group *group, Schema &target_schema, bool update_existing) { - // first pass to create missing tables - std::vector to_update; - for (auto& object_schema : target_schema) { - bool created = false; - ObjectStore::table_for_object_type_create_if_needed(group, object_schema.name, created); - - // we will modify tables for any new objectSchema (table was created) or for all if update_existing is true - if (update_existing || created) { - to_update.push_back(&object_schema); - } - } - - // second pass adds/removes columns for out of date tables - for (auto& target_object_schema : to_update) { - TableRef table = table_for_object_type(group, target_object_schema->name); - ObjectSchema current_schema(group, target_object_schema->name); - std::vector &target_props = target_object_schema->persisted_properties; - - // handle columns changing from required to optional - for (auto& current_prop : current_schema.persisted_properties) { - auto target_prop = target_object_schema->property_for_name(current_prop.name); - if (!target_prop || !property_can_be_migrated_to_nullable(current_prop, *target_prop)) - continue; - - target_prop->table_column = current_prop.table_column; - current_prop.table_column = current_prop.table_column + 1; - - table->insert_column(target_prop->table_column, DataType(target_prop->type), target_prop->name, target_prop->is_nullable); - copy_property_values(current_prop, *target_prop, *table); - table->remove_column(current_prop.table_column); - - current_prop.table_column = target_prop->table_column; - } - - bool inserted_placeholder_column = false; - - // remove extra columns - size_t deleted = 0; - for (auto& current_prop : current_schema.persisted_properties) { - current_prop.table_column -= deleted; - - auto target_prop = target_object_schema->property_for_name(current_prop.name); - if (!target_prop || (property_has_changed(current_prop, *target_prop) - && !property_can_be_migrated_to_nullable(current_prop, *target_prop))) { - if (deleted == current_schema.persisted_properties.size() - 1) { - // We're about to remove the last column from the table. Insert a placeholder column to preserve - // the number of rows in the table for the addition of new columns below. - table->add_column(type_Bool, "placeholder"); - inserted_placeholder_column = true; - } - - table->remove_column(current_prop.table_column); - ++deleted; - current_prop.table_column = npos; - } - } - - // add missing columns - for (auto& target_prop : target_props) { - auto current_prop = current_schema.property_for_name(target_prop.name); - - // add any new properties (no old column or old column was removed due to not matching) - if (!current_prop || current_prop->table_column == npos) { - switch (target_prop.type) { - // for objects and arrays, we have to specify target table - case PropertyType::Object: - case PropertyType::Array: { - TableRef link_table = ObjectStore::table_for_object_type(group, target_prop.object_type); - REALM_ASSERT(link_table); - target_prop.table_column = table->add_column_link(DataType(target_prop.type), target_prop.name, *link_table); - break; - } - default: - target_prop.table_column = table->add_column(DataType(target_prop.type), - target_prop.name, - target_prop.is_nullable); - break; - } - } - else { - target_prop.table_column = current_prop->table_column; - } - } - - if (inserted_placeholder_column) { - // We inserted a placeholder due to removing all columns from the table. Remove it, and update the indices - // of any columns that we inserted after it. - table->remove_column(0); - for (auto& target_prop : target_props) { - target_prop.table_column--; - } - } - - // update table metadata - if (target_object_schema->primary_key.length()) { - // if there is a primary key set, check if it is the same as the old key - if (current_schema.primary_key != target_object_schema->primary_key) { - set_primary_key_for_object(group, target_object_schema->name, target_object_schema->primary_key); - } - } - else if (current_schema.primary_key.length()) { - // there is no primary key, so if there was one nil out - set_primary_key_for_object(group, target_object_schema->name, ""); - } - } -} - -bool ObjectStore::is_schema_at_version(const Group *group, uint64_t version) { - uint64_t old_version = get_schema_version(group); - if (old_version > version && old_version != NotVersioned) { - throw InvalidSchemaVersionException(old_version, version); - } - return old_version == version; -} - -bool ObjectStore::needs_update(Schema const& old_schema, Schema const& schema) { - for (auto const& target_schema : schema) { - auto matching_schema = old_schema.find(target_schema); - if (matching_schema == end(old_schema)) { - // Table doesn't exist - return true; - } - - if (matching_schema->persisted_properties.size() != target_schema.persisted_properties.size()) { - // If the number of properties don't match then a migration is required - return false; - } - - // Check that all of the property indexes are up to date - for (size_t i = 0, count = target_schema.persisted_properties.size(); i < count; ++i) { - if (target_schema.persisted_properties[i].is_indexed != matching_schema->persisted_properties[i].is_indexed) { - return true; - } - } - } - - return false; -} - -void ObjectStore::update_realm_with_schema(Group *group, Schema const& old_schema, - uint64_t version, Schema &schema, - MigrationFunction migration) { - // Recheck the schema version after beginning the write transaction as - // another process may have done the migration after we opened the read - // transaction - bool migrating = !is_schema_at_version(group, version); - - // create tables - create_metadata_tables(group); - create_tables(group, schema, migrating); - - if (!migrating) { - // If we aren't migrating, then verify that all of the tables which - // were already present are valid (newly created ones always are) - verify_schema(old_schema, schema, true); - } - - update_indexes(group, schema); - - if (!migrating) { - return; - } - - // apply the migration block if provided and there's any old data - if (get_schema_version(group) != ObjectStore::NotVersioned) { - migration(group, schema); - - validate_primary_column_uniqueness(group, schema); - } - - set_schema_version(group, version); -} - -Schema ObjectStore::schema_from_group(const Group *group) { - std::vector schema; - for (size_t i = 0; i < group->size(); i++) { - std::string object_type = object_type_for_table_name(group->get_table_name(i)); - if (object_type.length()) { - schema.emplace_back(group, object_type); - } - } - return schema; -} - -bool ObjectStore::update_indexes(Group *group, Schema &schema) { - bool changed = false; - for (auto& object_schema : schema) { - TableRef table = table_for_object_type(group, object_schema.name); - if (!table) { - continue; - } - - for (auto& property : object_schema.persisted_properties) { - if (property.requires_index() == table->has_search_index(property.table_column)) { - continue; - } - - changed = true; - if (property.requires_index()) { - try { - table->add_search_index(property.table_column); - } - catch (LogicError const&) { - throw PropertyTypeNotIndexableException(object_schema.name, property); - } - } - else { - table->remove_search_index(property.table_column); - } - } - } - return changed; -} - -void ObjectStore::validate_primary_column_uniqueness(const Group *group, Schema const& schema) { - for (auto& object_schema : schema) { - auto primary_prop = object_schema.primary_key_property(); - if (!primary_prop) { - continue; - } - - ConstTableRef table = table_for_object_type(group, object_schema.name); - if (table->get_distinct_view(primary_prop->table_column).size() != table->size()) { - throw DuplicatePrimaryKeyValueException(object_schema.name, *primary_prop); - } - } -} - -void ObjectStore::delete_data_for_object(Group *group, StringData object_type) { - TableRef table = table_for_object_type(group, object_type); - if (table) { - group->remove_table(table->get_index_in_group()); - set_primary_key_for_object(group, object_type, ""); - } -} - -bool ObjectStore::is_empty(const Group *group) { - for (size_t i = 0; i < group->size(); i++) { - ConstTableRef table = group->get_table(i); - std::string object_type = object_type_for_table_name(table->get_name()); - if (!object_type.length()) { - continue; - } - if (!table->is_empty()) { - return false; - } - } - return true; -} - -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.", new_version, old_version); -} - -DuplicatePrimaryKeyValueException::DuplicatePrimaryKeyValueException(std::string const& object_type, Property const& property) : - m_object_type(object_type), m_property(property) -{ - m_what = util::format("Primary key property '%1' has duplicate values after migration.", property.name); -} - -DuplicatePrimaryKeyValueException::DuplicatePrimaryKeyValueException(std::string const& object_type, - Property const& property, - const std::string message) -: m_object_type(object_type), m_property(property) -{ - m_what = message; -} - -SchemaValidationException::SchemaValidationException(std::vector const& errors) -: m_validation_errors(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_what = "Migration is required due to the following errors:"; - for (auto const& error : errors) { - m_what += std::string("\n- ") + error.what(); - } -} - -PropertyTypeNotIndexableException::PropertyTypeNotIndexableException(std::string const& object_type, - 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.", - object_type, property.name, string_for_property_type(property.type)); -} - -ExtraPropertyException::ExtraPropertyException(std::string const& object_type, Property const& property) : - ObjectSchemaPropertyException(object_type, property) -{ - m_what = util::format("Property '%1' has been added to latest object model.", property.name); -} - -MissingPropertyException::MissingPropertyException(std::string const& object_type, Property const& property) : - ObjectSchemaPropertyException(object_type, property) -{ - m_what = util::format("Property '%1' is missing from latest object model.", property.name); -} - -InvalidNullabilityException::InvalidNullabilityException(std::string const& object_type, Property const& property) : - ObjectSchemaPropertyException(object_type, property) -{ - switch (property.type) { - case PropertyType::Object: - m_what = util::format("'Object' property '%1' must be nullable.", property.name); - break; - case PropertyType::Any: - case PropertyType::Array: - case PropertyType::LinkingObjects: - m_what = util::format("Property '%1' of type '%2' cannot be nullable.", - property.name, string_for_property_type(property.type)); - break; - case PropertyType::Int: - case PropertyType::Bool: - case PropertyType::Data: - case PropertyType::Date: - case PropertyType::Float: - case PropertyType::Double: - case PropertyType::String: - REALM_ASSERT(false); - } -} - -MissingObjectTypeException::MissingObjectTypeException(std::string const& object_type, Property const& property) -: ObjectSchemaPropertyException(object_type, property) -{ - m_what = util::format("Target type '%1' doesn't exist for property '%2'.", - property.object_type, property.name); -} - -MismatchedPropertiesException::MismatchedPropertiesException(std::string const& object_type, - Property const& old_property, - Property const& new_property) : - 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 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'.", - 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.", - old_property.name, - old_property.is_nullable, new_property.is_nullable); - } -} - -ChangedPrimaryKeyException::ChangedPrimaryKeyException(std::string const& object_type, - std::string const& old_primary, - std::string const& new_primary) -: ObjectSchemaValidationException(object_type), m_old_primary(old_primary), m_new_primary(new_primary) -{ - if (old_primary.size()) { - m_what = util::format("Property '%1' is no longer a primary key.", old_primary); - } - else { - m_what = util::format("Property '%1' has been made a primary key.", new_primary); - } -} - -InvalidPrimaryKeyException::InvalidPrimaryKeyException(std::string const& object_type, std::string const& primary) : - ObjectSchemaValidationException(object_type), m_primary_key(primary) -{ - m_what = util::format("Specified primary key property '%1' does not exist.", primary); -} - -DuplicatePrimaryKeysException::DuplicatePrimaryKeysException(std::string const& object_type) -: ObjectSchemaValidationException(object_type) -{ - m_what = util::format("Duplicate primary keys for object '%1'.", object_type); -} - -InvalidLinkingObjectsPropertyException::InvalidLinkingObjectsPropertyException(Type error_type, std::string const& object_type, Property const& property) -: ObjectSchemaPropertyException(object_type, property) -{ - 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.", - 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.", - 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.", - 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 deleted file mode 100644 index 37450c0b..00000000 --- a/src/object-store/src/object_store.hpp +++ /dev/null @@ -1,258 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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_OBJECT_STORE_HPP -#define REALM_OBJECT_STORE_HPP - -#include "property.hpp" - -#include - -#include -#include -#include - -namespace realm { - class Group; - class ObjectSchema; - class ObjectSchemaValidationException; - class Schema; - class StringData; - - class ObjectStore { - public: - // Schema version used for uninitialized Realms - static const uint64_t NotVersioned; - - // get the last set schema version - static uint64_t get_schema_version(const Group *group); - - // checks if the schema in the group is at the given version - static bool is_schema_at_version(const Group *group, uint64_t version); - - // verify that schema from a group and a target schema are compatible - // updates the column mapping on all ObjectSchema properties of the target schema - // throws if the schema is invalid or does not match - static void verify_schema(Schema const& actual_schema, Schema& target_schema, bool allow_missing_tables = false); - - // determines if a realm with the given old schema needs non-migration - // changes to make it compatible with the given target schema - static bool needs_update(Schema const& old_schema, Schema const& schema); - - // updates a Realm from old_schema to the given target schema, creating and updating tables as needed - // passed in target schema is updated with the correct column mapping - // optionally runs migration function if schema is out of date - // NOTE: must be performed within a write transaction - typedef std::function MigrationFunction; - static void update_realm_with_schema(Group *group, Schema const& old_schema, uint64_t version, - Schema &schema, MigrationFunction migration); - - // get a table for an object type - static realm::TableRef table_for_object_type(Group *group, StringData object_type); - static realm::ConstTableRef table_for_object_type(const Group *group, StringData object_type); - - // get existing Schema from a group - static Schema schema_from_group(const Group *group); - - // deletes the table for the given type - static void delete_data_for_object(Group *group, StringData object_type); - - // indicates if this group contains any objects - static bool is_empty(const Group *group); - - static std::string table_name_for_object_type(StringData class_name); - static StringData object_type_for_table_name(StringData table_name); - - private: - // set a new schema version - static void set_schema_version(Group *group, uint64_t version); - - // check if the realm already has all metadata tables - static bool has_metadata_tables(const Group *group); - - // create any metadata tables that don't already exist - // must be in write transaction to set - // returns true if it actually did anything - static void create_metadata_tables(Group *group); - - // set references to tables on targetSchema and create/update any missing or out-of-date tables - // if update existing is true, updates existing tables, otherwise only adds and initializes new tables - static void create_tables(realm::Group *group, Schema &target_schema, bool update_existing); - - // verify a target schema against an expected schema, setting the table_column property on each schema object - // updates the column mapping on the target_schema - // returns array of validation errors - static std::vector verify_object_schema(ObjectSchema const& expected, - ObjectSchema &target_schema); - - // get primary key property name for object type - static StringData get_primary_key_for_object(const Group *group, StringData object_type); - - // sets primary key property for object type - // must be in write transaction to set - static void set_primary_key_for_object(Group *group, StringData object_type, StringData primary_key); - - static TableRef table_for_object_type_create_if_needed(Group *group, StringData object_type, bool &created); - - // returns if any indexes were changed - static bool update_indexes(Group *group, Schema &schema); - - // validates that all primary key properties have unique values - static void validate_primary_column_uniqueness(const Group *group, Schema const& schema); - - friend ObjectSchema; - }; - - // Base exception - class ObjectStoreException : public std::exception { - public: - ObjectStoreException() = default; - ObjectStoreException(const std::string &what) : m_what(what) {} - const char* what() const noexcept override { return m_what.c_str(); } - protected: - std::string m_what; - }; - - // Migration exceptions - class MigrationException : public ObjectStoreException {}; - - class InvalidSchemaVersionException : public MigrationException { - public: - InvalidSchemaVersionException(uint64_t old_version, uint64_t new_version); - uint64_t old_version() const { return m_old_version; } - uint64_t new_version() const { return m_new_version; } - private: - uint64_t m_old_version, m_new_version; - }; - - class DuplicatePrimaryKeyValueException : public MigrationException { - public: - DuplicatePrimaryKeyValueException(std::string const& object_type, Property const& property); - DuplicatePrimaryKeyValueException(std::string const& object_type, Property const& property, const std::string message); - - std::string object_type() const { return m_object_type; } - Property const& property() const { return m_property; } - private: - std::string m_object_type; - Property m_property; - }; - - // Schema validation exceptions - class SchemaValidationException : public ObjectStoreException { - public: - SchemaValidationException(std::vector const& errors); - std::vector const& validation_errors() const { return m_validation_errors; } - private: - std::vector m_validation_errors; - }; - - class SchemaMismatchException : public ObjectStoreException { - public: - SchemaMismatchException(std::vector const& errors); - std::vector const& validation_errors() const { return m_validation_errors; } - private: - std::vector m_validation_errors; - }; - - class ObjectSchemaValidationException : public ObjectStoreException { - public: - ObjectSchemaValidationException(std::string const& object_type) : m_object_type(object_type) {} - ObjectSchemaValidationException(std::string const& object_type, std::string const& message) : - m_object_type(object_type) { m_what = message; } - std::string object_type() const { return m_object_type; } - protected: - std::string m_object_type; - }; - - class ObjectSchemaPropertyException : public ObjectSchemaValidationException { - public: - ObjectSchemaPropertyException(std::string const& object_type, Property const& property) : - ObjectSchemaValidationException(object_type), m_property(property) {} - Property const& property() const { return m_property; } - private: - Property m_property; - }; - - class PropertyTypeNotIndexableException : public ObjectSchemaPropertyException { - public: - PropertyTypeNotIndexableException(std::string const& object_type, Property const& property); - }; - - class ExtraPropertyException : public ObjectSchemaPropertyException { - public: - ExtraPropertyException(std::string const& object_type, Property const& property); - }; - - class MissingPropertyException : public ObjectSchemaPropertyException { - public: - MissingPropertyException(std::string const& object_type, Property const& property); - }; - - class InvalidNullabilityException : public ObjectSchemaPropertyException { - public: - InvalidNullabilityException(std::string const& object_type, Property const& property); - }; - - class MissingObjectTypeException : public ObjectSchemaPropertyException { - public: - MissingObjectTypeException(std::string const& object_type, Property const& property); - }; - - class DuplicatePrimaryKeysException : public ObjectSchemaValidationException { - public: - DuplicatePrimaryKeysException(std::string const& object_type); - }; - - class MismatchedPropertiesException : public ObjectSchemaValidationException { - public: - MismatchedPropertiesException(std::string const& object_type, Property const& old_property, Property const& new_property); - Property const& old_property() const { return m_old_property; } - Property const& new_property() const { return m_new_property; } - private: - Property m_old_property, m_new_property; - }; - - class ChangedPrimaryKeyException : public ObjectSchemaValidationException { - public: - ChangedPrimaryKeyException(std::string const& object_type, std::string const& old_primary, std::string const& new_primary); - std::string old_primary() const { return m_old_primary; } - std::string new_primary() const { return m_new_primary; } - private: - std::string m_old_primary, m_new_primary; - }; - - class InvalidPrimaryKeyException : public ObjectSchemaValidationException { - public: - InvalidPrimaryKeyException(std::string const& object_type, std::string const& primary_key); - std::string primary_key() const { return m_primary_key; } - private: - std::string m_primary_key; - }; - - class InvalidLinkingObjectsPropertyException : public ObjectSchemaPropertyException { - public: - enum class Type { - OriginPropertyDoesNotExist, - OriginPropertyIsNotALink, - OriginPropertyInvalidLinkTarget, - }; - InvalidLinkingObjectsPropertyException(Type error_type, std::string const& object_type, Property const& property); - }; -} - -#endif /* defined(REALM_OBJECT_STORE_HPP) */ diff --git a/src/object-store/src/parser/parser.cpp b/src/object-store/src/parser/parser.cpp deleted file mode 100644 index 6b7c3396..00000000 --- a/src/object-store/src/parser/parser.cpp +++ /dev/null @@ -1,353 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "parser.hpp" - -#include - -#include -#include -#include - -// String tokens can't be followed by [A-z0-9_]. -#define string_token_t(s) seq< pegtl_istring_t(s), not_at< identifier_other > > - -using namespace pegtl; - -namespace realm { -namespace parser { - -// strings -struct unicode : list< seq< one< 'u' >, rep< 4, must< xdigit > > >, one< '\\' > > {}; -struct escaped_char : one< '"', '\'', '\\', '/', 'b', 'f', 'n', 'r', 't', '0' > {}; -struct escaped : sor< escaped_char, unicode > {}; -struct unescaped : utf8::range< 0x20, 0x10FFFF > {}; -struct chars : if_then_else< one< '\\' >, must< escaped >, unescaped > {}; -struct dq_string_content : until< at< one< '"' > >, must< chars > > {}; -struct dq_string : seq< one< '"' >, must< dq_string_content >, any > {}; - -struct sq_string_content : until< at< one< '\'' > >, must< chars > > {}; -struct sq_string : seq< one< '\'' >, must< sq_string_content >, any > {}; - -// numbers -struct minus : opt< one< '-' > > {}; -struct dot : one< '.' > {}; - -struct float_num : sor< - seq< plus< digit >, dot, star< digit > >, - seq< star< digit >, dot, plus< digit > > -> {}; -struct hex_num : seq< one< '0' >, one< 'x', 'X' >, plus< xdigit > > {}; -struct int_num : plus< digit > {}; - -struct number : seq< minus, sor< float_num, hex_num, int_num > > {}; - -struct true_value : string_token_t("true") {}; -struct false_value : string_token_t("false") {}; -struct null_value : string_token_t("null") {}; - -// key paths -struct key_path : list< seq< sor< alpha, one< '_' > >, star< sor< alnum, one< '_', '-' > > > >, one< '.' > > {}; - -// argument -struct argument_index : plus< digit > {}; -struct argument : seq< one< '$' >, must< argument_index > > {}; - -// expressions and operators -struct expr : sor< dq_string, sq_string, number, argument, true_value, false_value, null_value, key_path > {}; -struct case_insensitive : pegtl_istring_t("[c]") {}; - -struct eq : seq< sor< two< '=' >, one< '=' > >, star< blank >, opt< case_insensitive > >{}; -struct noteq : pegtl::string< '!', '=' > {}; -struct lteq : pegtl::string< '<', '=' > {}; -struct lt : one< '<' > {}; -struct gteq : pegtl::string< '>', '=' > {}; -struct gt : one< '>' > {}; -struct contains : string_token_t("contains") {}; -struct begins : string_token_t("beginswith") {}; -struct ends : string_token_t("endswith") {}; - -struct string_oper : seq< sor< contains, begins, ends>, star< blank >, opt< case_insensitive > > {}; -struct symbolic_oper : sor< eq, noteq, lteq, lt, gteq, gt > {}; - -// predicates -struct comparison_pred : seq< expr, pad< sor< string_oper, symbolic_oper >, blank >, expr > {}; - -struct pred; -struct group_pred : if_must< one< '(' >, pad< pred, blank >, one< ')' > > {}; -struct true_pred : string_token_t("truepredicate") {}; -struct false_pred : string_token_t("falsepredicate") {}; - -struct not_pre : seq< sor< one< '!' >, string_token_t("not") > > {}; -struct atom_pred : seq< opt< not_pre >, pad< sor< group_pred, true_pred, false_pred, comparison_pred >, blank > > {}; - -struct and_op : pad< sor< two< '&' >, string_token_t("and") >, blank > {}; -struct or_op : pad< sor< two< '|' >, string_token_t("or") >, blank > {}; - -struct or_ext : if_must< or_op, pred > {}; -struct and_ext : if_must< and_op, pred > {}; -struct and_pred : seq< atom_pred, star< and_ext > > {}; - -struct pred : seq< and_pred, star< or_ext > > {}; - -// state -struct ParserState -{ - std::vector group_stack; - - Predicate *current_group() - { - return group_stack.back(); - } - - Predicate *last_predicate() - { - Predicate *pred = current_group(); - while (pred->type != Predicate::Type::Comparison && pred->cpnd.sub_predicates.size()) { - pred = &pred->cpnd.sub_predicates.back(); - } - return pred; - } - - void add_predicate_to_current_group(Predicate::Type type) - { - current_group()->cpnd.sub_predicates.emplace_back(type, negate_next); - negate_next = false; - - if (current_group()->cpnd.sub_predicates.size() > 1) { - if (next_type == Predicate::Type::Or) { - apply_or(); - } - else { - apply_and(); - } - } - } - - bool negate_next = false; - Predicate::Type next_type = Predicate::Type::And; - - void add_expression(Expression && exp) - { - Predicate *current = last_predicate(); - if (current->type == Predicate::Type::Comparison && current->cmpr.expr[1].type == parser::Expression::Type::None) { - current->cmpr.expr[1] = std::move(exp); - } - else { - add_predicate_to_current_group(Predicate::Type::Comparison); - last_predicate()->cmpr.expr[0] = std::move(exp); - } - } - - void apply_or() - { - Predicate *group = current_group(); - if (group->type == Predicate::Type::Or) { - return; - } - - // convert to OR - group->type = Predicate::Type::Or; - if (group->cpnd.sub_predicates.size() > 2) { - // split the current group into an AND group ORed with the last subpredicate - Predicate new_sub(Predicate::Type::And); - new_sub.cpnd.sub_predicates = std::move(group->cpnd.sub_predicates); - - group->cpnd.sub_predicates = { new_sub, std::move(new_sub.cpnd.sub_predicates.back()) }; - group->cpnd.sub_predicates[0].cpnd.sub_predicates.pop_back(); - } - } - - void apply_and() - { - if (current_group()->type == Predicate::Type::And) { - return; - } - - auto &sub_preds = current_group()->cpnd.sub_predicates; - auto second_last = sub_preds.end() - 2; - if (second_last->type == Predicate::Type::And && !second_last->negate) { - // make a new and group populated with the last two predicates - second_last->cpnd.sub_predicates.push_back(std::move(sub_preds.back())); - sub_preds.pop_back(); - } - else { - // otherwise combine last two into a new AND group - Predicate pred(Predicate::Type::And); - pred.cpnd.sub_predicates.insert(pred.cpnd.sub_predicates.begin(), second_last, sub_preds.end()); - sub_preds.erase(second_last, sub_preds.end()); - sub_preds.emplace_back(std::move(pred)); - } - } -}; - -// rules -template< typename Rule > -struct action : nothing< Rule > {}; - -#ifdef REALM_PARSER_PRINT_TOKENS - #define DEBUG_PRINT_TOKEN(string) do { std::cout << string << std::endl; while (0) -#else - #define DEBUG_PRINT_TOKEN(string) do { static_cast(string); } while (0) -#endif - -template<> struct action< and_op > -{ - static void apply(const input&, ParserState& state) - { - DEBUG_PRINT_TOKEN(""); - state.next_type = Predicate::Type::And; - } -}; - -template<> struct action< or_op > -{ - static void apply(const input&, ParserState & state) - { - DEBUG_PRINT_TOKEN(""); - state.next_type = Predicate::Type::Or; - } -}; - - -#define EXPRESSION_ACTION(rule, type) \ -template<> struct action< rule > { \ - static void apply(const input& in, ParserState& state) { \ - DEBUG_PRINT_TOKEN(in.string()); \ - state.add_expression(Expression(type, in.string())); }}; - -EXPRESSION_ACTION(dq_string_content, Expression::Type::String) -EXPRESSION_ACTION(sq_string_content, Expression::Type::String) -EXPRESSION_ACTION(key_path, Expression::Type::KeyPath) -EXPRESSION_ACTION(number, Expression::Type::Number) -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) - { - DEBUG_PRINT_TOKEN(in.string()); - state.current_group()->cpnd.sub_predicates.emplace_back(Predicate::Type::True); - } -}; - -template<> struct action< false_pred > -{ - static void apply(const input& in, ParserState & state) - { - DEBUG_PRINT_TOKEN(in.string()); - state.current_group()->cpnd.sub_predicates.emplace_back(Predicate::Type::False); - } -}; - -#define OPERATOR_ACTION(rule, oper) \ -template<> struct action< rule > { \ - static void apply(const input& in, ParserState& state) { \ - DEBUG_PRINT_TOKEN(in.string()); \ - state.last_predicate()->cmpr.op = oper; }}; - -OPERATOR_ACTION(eq, Predicate::Operator::Equal) -OPERATOR_ACTION(noteq, Predicate::Operator::NotEqual) -OPERATOR_ACTION(gteq, Predicate::Operator::GreaterThanOrEqual) -OPERATOR_ACTION(gt, Predicate::Operator::GreaterThan) -OPERATOR_ACTION(lteq, Predicate::Operator::LessThanOrEqual) -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) - { - DEBUG_PRINT_TOKEN(in.string()); - state.last_predicate()->cmpr.option = Predicate::OperatorOption::CaseInsensitive; - } -}; - -template<> struct action< one< '(' > > -{ - static void apply(const input&, ParserState & state) - { - DEBUG_PRINT_TOKEN(""); - state.add_predicate_to_current_group(Predicate::Type::And); - state.group_stack.push_back(state.last_predicate()); - } -}; - -template<> struct action< group_pred > -{ - static void apply(const input&, ParserState & state) - { - DEBUG_PRINT_TOKEN(""); - state.group_stack.pop_back(); - } -}; - -template<> struct action< not_pre > -{ - static void apply(const input&, ParserState & state) - { - DEBUG_PRINT_TOKEN(""); - state.negate_next = true; - } -}; - -template< typename Rule > -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&&...) - { - throw pegtl::parse_error(error_message, in); - } -}; - -template<> -const std::string error_message_control< chars >::error_message = "Invalid characters in string constant."; - -template< typename Rule> -const std::string error_message_control< Rule >::error_message = "Invalid predicate."; - -Predicate parse(const std::string &query) -{ - DEBUG_PRINT_TOKEN(query); - - Predicate out_predicate(Predicate::Type::And); - - ParserState state; - state.group_stack.push_back(&out_predicate); - - pegtl::parse< must< pred, eof >, action, error_message_control >(query, query, state); - if (out_predicate.type == Predicate::Type::And && out_predicate.cpnd.sub_predicates.size() == 1) { - return std::move(out_predicate.cpnd.sub_predicates.back()); - } - return out_predicate; -} - -void analyze_grammar() -{ - analyze(); -} - -}} diff --git a/src/object-store/src/parser/parser.hpp b/src/object-store/src/parser/parser.hpp deleted file mode 100644 index 1e8afef8..00000000 --- a/src/object-store/src/parser/parser.hpp +++ /dev/null @@ -1,94 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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_PARSER_HPP -#define REALM_PARSER_HPP - -#include -#include - -namespace realm { -class Schema; - -namespace parser { -struct Expression -{ - enum class Type { None, Number, String, KeyPath, Argument, True, False, Null } type; - std::string s; - Expression(Type t = Type::None, std::string s = "") : type(t), s(s) {} -}; - -struct Predicate -{ - enum class Type - { - Comparison, - Or, - And, - True, - False - } type = Type::And; - - enum class Operator - { - None, - Equal, - NotEqual, - LessThan, - LessThanOrEqual, - GreaterThan, - GreaterThanOrEqual, - BeginsWith, - EndsWith, - Contains - }; - - enum class OperatorOption - { - None, - CaseInsensitive, - }; - - struct Comparison - { - Operator op = Operator::None; - OperatorOption option = OperatorOption::None; - Expression expr[2] = {{Expression::Type::None, ""}, {Expression::Type::None, ""}}; - }; - - struct Compound - { - std::vector sub_predicates; - }; - - Comparison cmpr; - Compound cpnd; - - bool negate = false; - - Predicate(Type t, bool n = false) : type(t), negate(n) {} -}; - -Predicate parse(const std::string &query); - -void analyze_grammar(); -bool test_grammar(); -} -} - -#endif // REALM_PARSER_HPP diff --git a/src/object-store/src/parser/query_builder.cpp b/src/object-store/src/parser/query_builder.cpp deleted file mode 100644 index 5618c6f4..00000000 --- a/src/object-store/src/parser/query_builder.cpp +++ /dev/null @@ -1,613 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "query_builder.hpp" -#include "parser.hpp" - -#include "object_store.hpp" -#include "schema.hpp" -#include "util/compiler.hpp" -#include "util/format.hpp" - -#include -#include -#include - -using namespace realm; -using namespace parser; -using namespace query_builder; - -namespace { -template -T stot(std::string const& s) { - std::istringstream iss(s); - T value; - iss >> value; - if (iss.fail()) { - throw std::invalid_argument(util::format("Cannot convert string '%1'", s)); - } - return value; -} - -// 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::logic_error(message); } - -// FIXME: TrueExpression and FalseExpression should be supported by core in some way -struct TrueExpression : realm::Expression { - size_t find_first(size_t start, size_t end) const override - { - if (start != end) - return start; - - return realm::not_found; - } - void set_base_table(const Table*) override {} - const Table* get_base_table() const override { return nullptr; } - std::unique_ptr clone(QueryNodeHandoverPatches*) const override - { - return std::unique_ptr(new TrueExpression(*this)); - } -}; - -struct FalseExpression : realm::Expression { - size_t find_first(size_t, size_t) const override { return realm::not_found; } - void set_base_table(const Table*) override {} - const Table* get_base_table() const override { return nullptr; } - std::unique_ptr clone(QueryNodeHandoverPatches*) const override - { - return std::unique_ptr(new FalseExpression(*this)); - } -}; - -using KeyPath = std::vector; -KeyPath key_path_from_string(const std::string &s) { - std::stringstream ss(s); - std::string item; - KeyPath key_path; - while (std::getline(ss, item, '.')) { - key_path.push_back(item); - } - return key_path; -} - -struct PropertyExpression -{ - const Property *prop = nullptr; - std::vector indexes; - std::function
table_getter; - - PropertyExpression(Query &query, const Schema &schema, Schema::const_iterator desc, const std::string &key_path_string) - { - KeyPath key_path = key_path_from_string(key_path_string); - for (size_t index = 0; index < key_path.size(); index++) { - if (prop) { - precondition(prop->type == PropertyType::Object || prop->type == PropertyType::Array, - util::format("Property '%1' is not a link in object of type '%2'", key_path[index], desc->name)); - indexes.push_back(prop->table_column); - - } - prop = desc->property_for_name(key_path[index]); - precondition(prop != nullptr, - util::format("No property '%1' on object of type '%2'", key_path[index], desc->name)); - - if (prop->object_type.size()) { - desc = schema.find(prop->object_type); - } - } - - table_getter = [&] { - TableRef& tbl = query.get_table(); - for (size_t col : indexes) { - tbl->link(col); // mutates m_link_chain on table - } - return tbl.get(); - }; - } -}; - - -// add a clause for numeric constraints based on operator type -template -void add_numeric_constraint_to_query(Query& query, - Predicate::Operator operatorType, - A lhs, - B rhs) -{ - switch (operatorType) { - case Predicate::Operator::LessThan: - query.and_query(lhs < rhs); - break; - case Predicate::Operator::LessThanOrEqual: - query.and_query(lhs <= rhs); - break; - case Predicate::Operator::GreaterThan: - query.and_query(lhs > rhs); - break; - case Predicate::Operator::GreaterThanOrEqual: - query.and_query(lhs >= rhs); - break; - case Predicate::Operator::Equal: - query.and_query(lhs == rhs); - break; - case Predicate::Operator::NotEqual: - query.and_query(lhs != rhs); - break; - default: - throw std::logic_error("Unsupported operator for numeric queries."); - } -} - -template -void add_bool_constraint_to_query(Query &query, Predicate::Operator operatorType, A lhs, B rhs) { - switch (operatorType) { - case Predicate::Operator::Equal: - query.and_query(lhs == rhs); - break; - case Predicate::Operator::NotEqual: - query.and_query(lhs != rhs); - break; - default: - throw std::logic_error("Unsupported operator for numeric queries."); - } -} - -void add_string_constraint_to_query(Query &query, - Predicate::Comparison cmp, - Columns &&column, - std::string &&value) { - bool case_sensitive = (cmp.option != Predicate::OperatorOption::CaseInsensitive); - switch (cmp.op) { - case Predicate::Operator::BeginsWith: - query.and_query(column.begins_with(value, case_sensitive)); - break; - case Predicate::Operator::EndsWith: - query.and_query(column.ends_with(value, case_sensitive)); - break; - case Predicate::Operator::Contains: - query.and_query(column.contains(value, case_sensitive)); - break; - case Predicate::Operator::Equal: - query.and_query(column.equal(value, case_sensitive)); - break; - case Predicate::Operator::NotEqual: - query.and_query(column.not_equal(value, case_sensitive)); - break; - default: - throw std::logic_error("Unsupported operator for string queries."); - } -} - -void add_string_constraint_to_query(realm::Query &query, - Predicate::Comparison cmp, - std::string &&value, - Columns &&column) { - bool case_sensitive = (cmp.option != Predicate::OperatorOption::CaseInsensitive); - switch (cmp.op) { - case Predicate::Operator::Equal: - query.and_query(column.equal(value, case_sensitive)); - break; - case Predicate::Operator::NotEqual: - query.and_query(column.not_equal(value, case_sensitive)); - break; - default: - throw std::logic_error("Substring comparison not supported for keypath substrings."); - } -} - -void add_binary_constraint_to_query(Query &query, - Predicate::Operator op, - Columns &&column, - std::string &&value) { - switch (op) { - case Predicate::Operator::BeginsWith: - query.begins_with(column.m_column, BinaryData(value)); - break; - case Predicate::Operator::EndsWith: - query.ends_with(column.m_column, BinaryData(value)); - break; - case Predicate::Operator::Contains: - query.contains(column.m_column, BinaryData(value)); - break; - case Predicate::Operator::Equal: - query.equal(column.m_column, BinaryData(value)); - break; - case Predicate::Operator::NotEqual: - query.not_equal(column.m_column, BinaryData(value)); - break; - default: - throw std::logic_error("Unsupported operator for binary queries."); - } -} - -void add_binary_constraint_to_query(realm::Query &query, - Predicate::Operator op, - std::string value, - Columns &&column) { - switch (op) { - case Predicate::Operator::Equal: - query.equal(column.m_column, BinaryData(value)); - break; - case Predicate::Operator::NotEqual: - query.not_equal(column.m_column, BinaryData(value)); - break; - default: - throw std::logic_error("Substring comparison not supported for keypath substrings."); - } -} - -void add_link_constraint_to_query(realm::Query &query, - Predicate::Operator op, - const PropertyExpression &prop_expr, - size_t row_index) { - precondition(prop_expr.indexes.empty(), "KeyPath queries not supported for object comparisons."); - 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::logic_error("Only 'equal' and 'not equal' operators supported for object comparison."); - } -} - -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&, Arguments &args) -{ - return args.object_index_for_argument(stot(argExpr.s)); -} - - -template -struct ColumnGetter { - static Columns convert(TableGetter&& table, const PropertyExpression& expr, Arguments&) - { - return table()->template column(expr.prop->table_column); - } -}; - -template -struct ValueGetter; - -template -struct ValueGetter { - static Timestamp convert(TableGetter&&, const parser::Expression & value, Arguments &args) - { - if (value.type != parser::Expression::Type::Argument) { - throw std::logic_error("You must pass in a date argument to compare"); - } - return args.timestamp_for_argument(stot(value.s)); - } -}; - -template -struct ValueGetter { - static bool convert(TableGetter&&, const parser::Expression & value, Arguments &args) - { - if (value.type == parser::Expression::Type::Argument) { - return args.bool_for_argument(stot(value.s)); - } - if (value.type != parser::Expression::Type::True && value.type != parser::Expression::Type::False) { - throw std::logic_error("Attempting to compare bool property to a non-bool value"); - } - return value.type == parser::Expression::Type::True; - } -}; - -template -struct ValueGetter { - static Double convert(TableGetter&&, const parser::Expression & value, Arguments &args) - { - if (value.type == parser::Expression::Type::Argument) { - return args.double_for_argument(stot(value.s)); - } - return stot(value.s); - } -}; - -template -struct ValueGetter { - static Float convert(TableGetter&&, const parser::Expression & value, Arguments &args) - { - if (value.type == parser::Expression::Type::Argument) { - return args.float_for_argument(stot(value.s)); - } - return stot(value.s); - } -}; - -template -struct ValueGetter { - static Int convert(TableGetter&&, const parser::Expression & value, Arguments &args) - { - if (value.type == parser::Expression::Type::Argument) { - return args.long_for_argument(stot(value.s)); - } - return stot(value.s); - } -}; - -template -struct ValueGetter { - static std::string convert(TableGetter&&, const parser::Expression & value, Arguments &args) - { - if (value.type == parser::Expression::Type::Argument) { - return args.string_for_argument(stot(value.s)); - } - if (value.type != parser::Expression::Type::String) { - throw std::logic_error("Attempting to compare String property to a non-String value"); - } - return value.s; - } -}; - -template -struct ValueGetter { - static std::string convert(TableGetter&&, const parser::Expression & value, Arguments &args) - { - if (value.type == parser::Expression::Type::Argument) { - return args.binary_for_argument(stot(value.s)); - } - throw std::logic_error("Binary properties must be compared against a binary argument."); - } -}; - -template -auto value_of_type_for_query(TableGetter&& tables, Value&& value, Arguments &args) -{ - const bool isColumn = std::is_same::type>::value; - using helper = std::conditional_t, ValueGetter>; - return helper::convert(tables, value, args); -} - -template -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; - switch (type) { - case PropertyType::Bool: - add_bool_constraint_to_query(query, cmp.op, value_of_type_for_query(expr.table_getter, lhs, args), - value_of_type_for_query(expr.table_getter, rhs, args)); - break; - case PropertyType::Date: - add_numeric_constraint_to_query(query, cmp.op, value_of_type_for_query(expr.table_getter, lhs, args), - value_of_type_for_query(expr.table_getter, rhs, args)); - break; - case PropertyType::Double: - add_numeric_constraint_to_query(query, cmp.op, value_of_type_for_query(expr.table_getter, lhs, args), - value_of_type_for_query(expr.table_getter, rhs, args)); - break; - case PropertyType::Float: - add_numeric_constraint_to_query(query, cmp.op, value_of_type_for_query(expr.table_getter, lhs, args), - value_of_type_for_query(expr.table_getter, rhs, args)); - break; - case PropertyType::Int: - add_numeric_constraint_to_query(query, cmp.op, value_of_type_for_query(expr.table_getter, lhs, args), - value_of_type_for_query(expr.table_getter, rhs, args)); - break; - case PropertyType::String: - add_string_constraint_to_query(query, cmp, value_of_type_for_query(expr.table_getter, lhs, args), - value_of_type_for_query(expr.table_getter, rhs, args)); - break; - case PropertyType::Data: - add_binary_constraint_to_query(query, cmp.op, value_of_type_for_query(expr.table_getter, lhs, args), - value_of_type_for_query(expr.table_getter, rhs, args)); - break; - case PropertyType::Object: - case PropertyType::Array: - add_link_constraint_to_query(query, cmp.op, expr, link_argument(lhs, rhs, args)); - break; - default: - 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) -{ - Columns column = expr.table_getter()->template column(expr.prop->table_column); - switch (op) { - case Predicate::Operator::NotEqual: - query.and_query(column != realm::null()); - break; - case Predicate::Operator::Equal: - query.and_query(column == realm::null()); - break; - default: - 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) -{ - precondition(expr.indexes.empty(), "KeyPath queries not supported for data comparisons."); - Columns column = expr.table_getter()->template column(expr.prop->table_column); - switch (op) { - case Predicate::Operator::NotEqual: - query.not_equal(expr.prop->table_column, realm::null()); - break; - case Predicate::Operator::Equal: - query.equal(expr.prop->table_column, realm::null()); - break; - default: - 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) -{ - precondition(expr.indexes.empty(), "KeyPath queries not supported for object comparisons."); - switch (op) { - case Predicate::Operator::NotEqual: - 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::logic_error("Only 'equal' and 'not equal' operators supported for object comparison."); - } -} - -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); - break; - case realm::PropertyType::Date: - 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); - break; - case realm::PropertyType::Float: - 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); - break; - case realm::PropertyType::String: - 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); - break; - case realm::PropertyType::Object: - do_add_null_comparison_to_query(query, cmp.op, expr); - break; - case realm::PropertyType::Array: - 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))); - } -} - -bool expression_is_null(const parser::Expression &expr, Arguments &args) { - if (expr.type == parser::Expression::Type::Null) { - return true; - } - else if (expr.type == parser::Expression::Type::Argument) { - return args.is_argument_null(stot(expr.s)); - } - return false; -} - -void add_comparison_to_query(Query &query, const Predicate &pred, Arguments &args, const Schema &schema, const std::string &type) -{ - const Predicate::Comparison &cmpr = pred.cmpr; - auto t0 = cmpr.expr[0].type, t1 = cmpr.expr[1].type; - auto object_schema = schema.find(type); - 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, cmpr, expr); - } - else { - 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, cmpr, expr); - } - else { - do_add_comparison_to_query(query, cmpr, expr, cmpr.expr[0], expr, args); - } - } - else { - throw std::logic_error("Predicate expressions must compare a keypath and another keypath or a constant value"); - } -} - -void update_query_with_predicate(Query &query, const Predicate &pred, Arguments &arguments, const Schema &schema, const std::string &type) -{ - if (pred.negate) { - query.Not(); - } - - switch (pred.type) { - case Predicate::Type::And: - query.group(); - for (auto &sub : pred.cpnd.sub_predicates) { - update_query_with_predicate(query, sub, arguments, schema, type); - } - if (!pred.cpnd.sub_predicates.size()) { - query.and_query(std::unique_ptr(new TrueExpression)); - } - query.end_group(); - break; - - case Predicate::Type::Or: - query.group(); - for (auto &sub : pred.cpnd.sub_predicates) { - query.Or(); - update_query_with_predicate(query, sub, arguments, schema, type); - } - if (!pred.cpnd.sub_predicates.size()) { - query.and_query(std::unique_ptr(new FalseExpression)); - } - query.end_group(); - break; - - case Predicate::Type::Comparison: { - add_comparison_to_query(query, pred, arguments, schema, type); - break; - } - case Predicate::Type::True: - query.and_query(std::unique_ptr(new TrueExpression)); - break; - - case Predicate::Type::False: - query.and_query(std::unique_ptr(new FalseExpression)); - break; - - default: - 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) -{ - update_query_with_predicate(query, predicate, arguments, schema, objectType); - - // Test the constructed query in core - std::string validateMessage = query.validate(); - precondition(validateMessage.empty(), validateMessage.c_str()); -} - -} -} diff --git a/src/object-store/src/parser/query_builder.hpp b/src/object-store/src/parser/query_builder.hpp deleted file mode 100644 index 5f773d77..00000000 --- a/src/object-store/src/parser/query_builder.hpp +++ /dev/null @@ -1,77 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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_QUERY_BUILDER_HPP -#define REALM_QUERY_BUILDER_HPP - -#include "parser.hpp" -#include "object_accessor.hpp" - -namespace realm { -class Query; -class Schema; - -namespace query_builder { -class Arguments; - -void apply_predicate(Query &query, const parser::Predicate &predicate, Arguments &arguments, - const Schema &schema, const std::string &objectType); - -class Arguments { - public: - virtual bool bool_for_argument(size_t argument_index) = 0; - virtual long long long_for_argument(size_t argument_index) = 0; - virtual float float_for_argument(size_t argument_index) = 0; - virtual double double_for_argument(size_t argument_index) = 0; - virtual std::string string_for_argument(size_t argument_index) = 0; - virtual std::string binary_for_argument(size_t argument_index) = 0; - virtual Timestamp timestamp_for_argument(size_t argument_index) = 0; - virtual size_t object_index_for_argument(size_t argument_index) = 0; - virtual bool is_argument_null(size_t argument_index) = 0; -}; - -template -class ArgumentConverter : public Arguments { - public: - ArgumentConverter(ContextType context, SharedRealm realm, std::vector arguments) - : m_arguments(arguments), m_ctx(context), m_realm(std::move(realm)) {} - - using Accessor = realm::NativeAccessor; - virtual bool bool_for_argument(size_t argument_index) { return Accessor::to_bool(m_ctx, argument_at(argument_index)); } - virtual long long long_for_argument(size_t argument_index) { return Accessor::to_long(m_ctx, argument_at(argument_index)); } - virtual float float_for_argument(size_t argument_index) { return Accessor::to_float(m_ctx, argument_at(argument_index)); } - virtual double double_for_argument(size_t argument_index) { return Accessor::to_double(m_ctx, argument_at(argument_index)); } - virtual std::string string_for_argument(size_t argument_index) { return Accessor::to_string(m_ctx, argument_at(argument_index)); } - virtual std::string binary_for_argument(size_t argument_index) { return Accessor::to_binary(m_ctx, argument_at(argument_index)); } - virtual Timestamp timestamp_for_argument(size_t argument_index) { return Accessor::to_timestamp(m_ctx, argument_at(argument_index)); } - virtual size_t object_index_for_argument(size_t argument_index) { return Accessor::to_existing_object_index(m_ctx, m_realm, argument_at(argument_index)); } - virtual bool is_argument_null(size_t argument_index) { return Accessor::is_null(m_ctx, argument_at(argument_index)); } - - private: - std::vector m_arguments; - ContextType m_ctx; - SharedRealm m_realm; - - ValueType &argument_at(size_t index) { - return m_arguments.at(index); - } -}; -} -} - -#endif // REALM_QUERY_BUILDER_HPP diff --git a/src/object-store/src/parser/test.sh b/src/object-store/src/parser/test.sh deleted file mode 100755 index aad1d031..00000000 --- a/src/object-store/src/parser/test.sh +++ /dev/null @@ -1,3 +0,0 @@ -# /bin/sh -llvm-g++ -std=c++11 -I ../../../vendor/PEGTL/ -o test test.cpp parser.cpp -./test diff --git a/src/object-store/src/placeholder.cpp b/src/object-store/src/placeholder.cpp deleted file mode 100644 index 89365342..00000000 --- a/src/object-store/src/placeholder.cpp +++ /dev/null @@ -1 +0,0 @@ -// This file is intentionally left blank. diff --git a/src/object-store/src/property.hpp b/src/object-store/src/property.hpp deleted file mode 100644 index f66adff2..00000000 --- a/src/object-store/src/property.hpp +++ /dev/null @@ -1,107 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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_PROPERTY_HPP -#define REALM_PROPERTY_HPP - -#include - -namespace realm { - enum class PropertyType : unsigned char { - Int = 0, - Bool = 1, - Float = 9, - Double = 10, - String = 2, - Data = 4, - Any = 6, // deprecated and will be removed in the future - Date = 8, - Object = 12, - Array = 13, - LinkingObjects = 14, - }; - - struct Property { - std::string name; - PropertyType type; - std::string object_type; - std::string link_origin_property_name; - bool is_primary = false; - bool is_indexed = false; - bool is_nullable = false; - - size_t table_column = -1; - bool requires_index() const { return is_primary || is_indexed; } - bool is_indexable() const - { - return type == PropertyType::Int - || type == PropertyType::Bool - || type == PropertyType::Date - || type == PropertyType::String; - } - -#if __GNUC__ < 5 - // GCC 4.9 does not support C++14 braced-init with NSDMIs - Property(std::string name="", PropertyType type=PropertyType::Int, - std::string object_type="", std::string link_origin_property_name="", - bool is_primary=false, bool is_indexed=false, bool is_nullable=false) - : name(std::move(name)) - , type(type) - , object_type(std::move(object_type)) - , link_origin_property_name(std::move(link_origin_property_name)) - , is_primary(is_primary) - , is_indexed(is_indexed) - , is_nullable(is_nullable) - { - } -#endif - }; - - static inline const char *string_for_property_type(PropertyType type) { - switch (type) { - case PropertyType::String: - return "string"; - case PropertyType::Int: - return "int"; - case PropertyType::Bool: - return "bool"; - case PropertyType::Date: - return "date"; - case PropertyType::Data: - return "data"; - case PropertyType::Double: - return "double"; - case PropertyType::Float: - return "float"; - case PropertyType::Any: - return "any"; - case PropertyType::Object: - return "object"; - case PropertyType::Array: - return "array"; - case PropertyType::LinkingObjects: - return "linking objects"; -#if __GNUC__ - default: - __builtin_unreachable(); -#endif - } - } -} - -#endif /* REALM_PROPERTY_HPP */ diff --git a/src/object-store/src/results.cpp b/src/object-store/src/results.cpp deleted file mode 100644 index 35fb07f4..00000000 --- a/src/object-store/src/results.cpp +++ /dev/null @@ -1,614 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "results.hpp" - -#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; - -Results::Results() = default; -Results::~Results() = default; - -Results::Results(SharedRealm r, Query q, SortOrder s) -: m_realm(std::move(r)) -, m_query(std::move(q)) -, m_table(m_query.get_table().get()) -, m_sort(std::move(s)) -, m_mode(Mode::Query) -{ - REALM_ASSERT(m_sort.column_indices.size() == m_sort.ascending.size()); -} - -Results::Results(SharedRealm r, Table& table) -: m_realm(std::move(r)) -, m_table(&table) -, m_mode(Mode::Table) -{ -} - -Results::Results(SharedRealm r, LinkViewRef lv, util::Optional q, SortOrder s) -: m_realm(std::move(r)) -, m_link_view(lv) -, m_table(&lv->get_target_table()) -, m_sort(std::move(s)) -, m_mode(Mode::LinkView) -{ - REALM_ASSERT(m_sort.column_indices.size() == m_sort.ascending.size()); - if (q) { - m_query = std::move(*q); - m_mode = Mode::Query; - } -} - -Results::Results(SharedRealm r, TableView tv, SortOrder s) -: m_realm(std::move(r)) -, m_table_view(std::move(tv)) -, m_table(&m_table_view.get_parent()) -, m_sort(std::move(s)) -, m_mode(Mode::TableView) -{ - REALM_ASSERT(m_sort.column_indices.size() == m_sort.ascending.size()); -} - -Results::Results(const Results&) = default; - -// Cannot be defaulted as TableViewBase::operator= is missing from the core static library. -// Delegate to the copy constructor and move-assignment operators instead. -Results& Results::operator=(const Results& other) -{ - if (this != &other) { - *this = Results(other); - } - - return *this; -} - -Results::Results(Results&& other) -: m_realm(std::move(other.m_realm)) -, m_object_schema(std::move(other.m_object_schema)) -, m_query(std::move(other.m_query)) -, m_table_view(std::move(other.m_table_view)) -, m_link_view(std::move(other.m_link_view)) -, m_table(other.m_table) -, m_sort(std::move(other.m_sort)) -, 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) -{ - if (m_notifier) { - m_notifier->target_results_moved(other, *this); - } -} - -Results& Results::operator=(Results&& other) -{ - this->~Results(); - new (this) Results(std::move(other)); - return *this; -} - -bool Results::is_valid() const -{ - if (m_realm) - m_realm->verify_thread(); - - if (m_table && !m_table->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(); -} - -void Results::validate_write() const -{ - validate_read(); - if (!m_realm || !m_realm->is_in_transaction()) - throw InvalidTransactionException("Must be in a write transaction"); -} - -size_t Results::size() -{ - validate_read(); - switch (m_mode) { - case Mode::Empty: return 0; - case Mode::Table: return m_table->size(); - case Mode::LinkView: return m_link_view->size(); - case Mode::Query: - m_query.sync_view_if_needed(); - return m_query.count(); - case Mode::TableView: - update_tableview(); - return m_table_view.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 -{ - if (!m_table) { - return StringData(); - } - - return ObjectStore::object_type_for_table_name(m_table->get_name()); -} - -RowExpr Results::get(size_t row_ndx) -{ - validate_read(); - switch (m_mode) { - case Mode::Empty: break; - case Mode::Table: - if (row_ndx < m_table->size()) - return m_table->get(row_ndx); - break; - case Mode::LinkView: - if (update_linkview()) { - if (row_ndx < m_link_view->size()) - return m_link_view->get(row_ndx); - break; - } - REALM_FALLTHROUGH; - case Mode::Query: - case Mode::TableView: - update_tableview(); - if (row_ndx >= m_table_view.size()) - break; - if (m_update_policy == UpdatePolicy::Never && !m_table_view.is_row_attached(row_ndx)) - return {}; - return m_table_view.get(row_ndx); - } - - throw OutOfBoundsIndexException{row_ndx, size()}; -} - -util::Optional Results::first() -{ - validate_read(); - switch (m_mode) { - case Mode::Empty: - return none; - case Mode::Table: - return m_table->size() == 0 ? util::none : util::make_optional(m_table->front()); - case Mode::LinkView: - if (update_linkview()) - return m_link_view->size() == 0 ? util::none : util::make_optional(m_link_view->get(0)); - REALM_FALLTHROUGH; - case Mode::Query: - case Mode::TableView: - update_tableview(); - return m_table_view.size() == 0 ? util::none : util::make_optional(m_table_view.front()); - } - REALM_UNREACHABLE(); -} - -util::Optional Results::last() -{ - validate_read(); - switch (m_mode) { - case Mode::Empty: - return none; - case Mode::Table: - return m_table->size() == 0 ? util::none : util::make_optional(m_table->back()); - case Mode::LinkView: - if (update_linkview()) - return m_link_view->size() == 0 ? util::none : util::make_optional(m_link_view->get(m_link_view->size() - 1)); - REALM_FALLTHROUGH; - case Mode::Query: - case Mode::TableView: - update_tableview(); - return m_table_view.size() == 0 ? util::none : util::make_optional(m_table_view.back()); - } - REALM_UNREACHABLE(); -} - -bool Results::update_linkview() -{ - REALM_ASSERT(m_update_policy == UpdatePolicy::Auto); - - if (m_sort) { - m_query = get_query(); - m_mode = Mode::Query; - update_tableview(); - return false; - } - return true; -} - -void Results::update_tableview(bool wants_notifications) -{ - if (m_update_policy == UpdatePolicy::Never) { - REALM_ASSERT(m_mode == Mode::TableView); - return; - } - - switch (m_mode) { - case Mode::Empty: - case Mode::Table: - case Mode::LinkView: - return; - case Mode::Query: - m_query.sync_view_if_needed(); - m_table_view = m_query.find_all(); - if (m_sort) { - m_table_view.sort(m_sort.column_indices, m_sort.ascending); - } - m_mode = Mode::TableView; - break; - case Mode::TableView: - 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); - } - m_has_used_table_view = true; - m_table_view.sync_if_needed(); - break; - } -} - -size_t Results::index_of(Row const& row) -{ - validate_read(); - if (!row) { - throw DetatchedAccessorException{}; - } - if (m_table && row.get_table() != m_table) { - throw IncorrectTableException(m_object_schema->name, - ObjectStore::object_type_for_table_name(row.get_table()->get_name()), - "Attempting to get the index of a Row of the wrong type" - ); - } - return index_of(row.get_index()); -} - -size_t Results::index_of(size_t row_ndx) -{ - validate_read(); - switch (m_mode) { - case Mode::Empty: - return not_found; - case Mode::Table: - return row_ndx; - case Mode::LinkView: - if (update_linkview()) - return m_link_view->find(row_ndx); - REALM_FALLTHROUGH; - case Mode::Query: - case Mode::TableView: - update_tableview(); - return m_table_view.find_by_source_ndx(row_ndx); - } - REALM_UNREACHABLE(); -} - -template -util::Optional Results::aggregate(size_t column, bool return_none_for_empty, - const char* name, - Int agg_int, Float agg_float, - Double agg_double, Timestamp agg_timestamp) -{ - validate_read(); - if (!m_table) - return none; - if (column > m_table->get_column_count()) - throw OutOfBoundsIndexException{column, m_table->get_column_count()}; - - auto do_agg = [&](auto const& getter) -> util::Optional { - switch (m_mode) { - case Mode::Empty: - return none; - case Mode::Table: - if (return_none_for_empty && m_table->size() == 0) - return none; - return util::Optional(getter(*m_table)); - case Mode::LinkView: - m_query = this->get_query(); - m_mode = Mode::Query; - REALM_FALLTHROUGH; - case Mode::Query: - case Mode::TableView: - this->update_tableview(); - if (return_none_for_empty && m_table_view.size() == 0) - return none; - return util::Optional(getter(m_table_view)); - } - REALM_UNREACHABLE(); - }; - - switch (m_table->get_column_type(column)) - { - case type_Timestamp: return do_agg(agg_timestamp); - case type_Double: return do_agg(agg_double); - case type_Float: return do_agg(agg_float); - case type_Int: return do_agg(agg_int); - default: - throw UnsupportedColumnTypeException{column, m_table, name}; - } -} - -util::Optional Results::max(size_t column) -{ - return aggregate(column, true, "max", - [=](auto const& table) { return table.maximum_int(column); }, - [=](auto const& table) { return table.maximum_float(column); }, - [=](auto const& table) { return table.maximum_double(column); }, - [=](auto const& table) { return table.maximum_timestamp(column); }); -} - -util::Optional Results::min(size_t column) -{ - return aggregate(column, true, "min", - [=](auto const& table) { return table.minimum_int(column); }, - [=](auto const& table) { return table.minimum_float(column); }, - [=](auto const& table) { return table.minimum_double(column); }, - [=](auto const& table) { return table.minimum_timestamp(column); }); -} - -util::Optional Results::sum(size_t column) -{ - return aggregate(column, false, "sum", - [=](auto const& table) { return table.sum_int(column); }, - [=](auto const& table) { return table.sum_float(column); }, - [=](auto const& table) { return table.sum_double(column); }, - [=](auto const&) -> util::None { throw UnsupportedColumnTypeException{column, m_table, "sum"}; }); -} - -util::Optional Results::average(size_t column) -{ - return aggregate(column, true, "average", - [=](auto const& table) { return table.average_int(column); }, - [=](auto const& table) { return table.average_float(column); }, - [=](auto const& table) { return table.average_double(column); }, - [=](auto const&) -> util::None { throw UnsupportedColumnTypeException{column, m_table, "average"}; }); -} - -void Results::clear() -{ - switch (m_mode) { - case Mode::Empty: - return; - case Mode::Table: - validate_write(); - m_table->clear(); - break; - case Mode::Query: - // Not using Query:remove() because building the tableview and - // clearing it is actually significantly faster - case Mode::TableView: - validate_write(); - update_tableview(); - - 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: - validate_write(); - m_link_view->remove_all_target_rows(); - break; - } -} - -Query Results::get_query() const -{ - validate_read(); - switch (m_mode) { - case Mode::Empty: - case Mode::Query: - return m_query; - case Mode::TableView: { - // A TableView has an associated Query if it was produced by Query::find_all. This is indicated - // by TableView::get_query returning a Query with a non-null table. - Query query = m_table_view.get_query(); - if (query.get_table()) { - return query; - } - - // The TableView has no associated query so create one with no conditions that is restricted - // to the rows in the TableView. - 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: - return m_table->where(m_link_view); - case Mode::Table: - return m_table->where(); - } - REALM_UNREACHABLE(); -} - -TableView Results::get_tableview() -{ - validate_read(); - switch (m_mode) { - case Mode::Empty: - return {}; - case Mode::LinkView: - if (update_linkview()) - return m_table->where(m_link_view).find_all(); - REALM_FALLTHROUGH; - case Mode::Query: - case Mode::TableView: - update_tableview(); - return m_table_view; - case Mode::Table: - return m_table->where().find_all(); - } - REALM_UNREACHABLE(); -} - -Results Results::sort(realm::SortOrder&& sort) const -{ - REALM_ASSERT(sort.column_indices.size() == sort.ascending.size()); - return Results(m_realm, get_query(), std::move(sort)); -} - -Results Results::filter(Query&& q) const -{ - 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() -{ - if (m_realm->config().read_only) { - throw InvalidTransactionException("Cannot create asynchronous query for read-only Realms"); - } - 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; - m_notifier = std::make_shared<_impl::ResultsNotifier>(*this); - _impl::RealmCoordinator::register_notifier(m_notifier); - } -} - -NotificationToken Results::async(std::function target) -{ - prepare_async(); - auto wrap = [=](CollectionChangeSet, std::exception_ptr e) { target(e); }; - return {m_notifier, m_notifier->add_callback(wrap)}; -} - -NotificationToken Results::add_notification_callback(CollectionChangeCallback cb) -{ - prepare_async(); - return {m_notifier, m_notifier->add_callback(std::move(cb))}; -} - -bool Results::is_in_table_order() const -{ - switch (m_mode) { - case Mode::Empty: - case Mode::Table: - return true; - case Mode::LinkView: - return false; - case Mode::Query: - return m_query.produces_results_in_table_order() && !m_sort; - case Mode::TableView: - return m_table_view.is_in_table_order(); - } - REALM_UNREACHABLE(); // keep gcc happy -} - -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) { - results.m_wants_background_updates = results.m_has_used_table_view; - } - - results.m_table_view = std::move(tv); - results.m_mode = Mode::TableView; - results.m_has_used_table_view = false; - REALM_ASSERT(results.m_table_view.is_in_sync()); - REALM_ASSERT(results.m_table_view.is_attached()); -} - -Results::OutOfBoundsIndexException::OutOfBoundsIndexException(size_t r, size_t c) -: std::out_of_range(util::format("Requested index %1 greater than max %2", r, c)) -, requested(r), valid_count(c) {} - -Results::UnsupportedColumnTypeException::UnsupportedColumnTypeException(size_t column, const Table* table, const char* operation) -: 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) -, column_name(table->get_column_name(column)) -, column_type(table->get_column_type(column)) -{ -} diff --git a/src/object-store/src/results.hpp b/src/object-store/src/results.hpp deleted file mode 100644 index e91e70eb..00000000 --- a/src/object-store/src/results.hpp +++ /dev/null @@ -1,234 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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_RESULTS_HPP -#define REALM_RESULTS_HPP - -#include "collection_notifications.hpp" -#include "shared_realm.hpp" -#include "impl/collection_notifier.hpp" - -#include -#include - -namespace realm { -template class BasicRowExpr; -using RowExpr = BasicRowExpr
; -class Mixed; -class ObjectSchema; - -namespace _impl { - class ResultsNotifier; -} - -struct SortOrder { - std::vector column_indices; - std::vector ascending; - - explicit operator bool() const { return !column_indices.empty(); } -}; - -class Results { -public: - // Results can be either be backed by nothing, a thin wrapper around a table, - // or a wrapper around a query and a sort order which creates and updates - // the tableview as needed - Results(); - 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 - Results(Results&&); - Results& operator=(Results&&); - Results(const Results&); - Results& operator=(const Results&); - - // Get the Realm - SharedRealm get_realm() const { return m_realm; } - - // Object schema describing the vendored object type - 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 - Query get_query() const; - - // Get the currently applied sort order for this Results - SortOrder const& get_sort() const noexcept { return m_sort; } - - // Get a tableview containing the same rows as this Results - TableView get_tableview(); - - // Get the object type which will be returned by get() - StringData get_object_type() const noexcept; - - // Get the LinkView this Results is derived from, if any - LinkViewRef get_linkview() const { return m_link_view; } - - // Get the size of this results - // Can be either O(1) or O(N) depending on the state of things - size_t size(); - - // Get the row accessor for the given index - // Throws OutOfBoundsIndexException if index >= size() - RowExpr get(size_t index); - - // Get a row accessor for the first/last row, or none if the results are empty - // More efficient than calling size()+get() - util::Optional first(); - util::Optional last(); - - // Get the first index of the given row in this results, or not_found - // Throws DetachedAccessorException if row is not attached - // Throws IncorrectTableException if row belongs to a different table - size_t index_of(size_t row_ndx); - size_t index_of(Row const& row); - - // Delete all of the rows in this Results from the Realm - // size() will always be zero afterwards - // Throws InvalidTransactionException if not in a write transaction - void clear(); - - // Create a new Results by further filtering or sorting this Results - 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 - // Throws UnsupportedColumnTypeException for sum/average on timestamp or non-numeric column - // Throws OutOfBoundsIndexException for an out-of-bounds column - util::Optional max(size_t column); - util::Optional min(size_t column); - util::Optional average(size_t column); - util::Optional sum(size_t column); - - enum class Mode { - 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 - }; - // 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::logic_error { - InvalidatedException() : std::logic_error("Access to invalidated Results objects") {} - }; - - // The input index parameter was out of bounds - struct OutOfBoundsIndexException : public std::out_of_range { - OutOfBoundsIndexException(size_t r, size_t c); - const size_t requested; - const size_t valid_count; - }; - - // The input Row object is not attached - 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::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::logic_error { - size_t column_index; - StringData column_name; - DataType column_type; - - UnsupportedColumnTypeException(size_t column, const Table* table, const char* operation); - }; - - // Create an async query from this Results - // The query will be run on a background thread and delivered to the callback, - // and then rerun after each commit (if needed) and redelivered if it changed - NotificationToken async(std::function target); - NotificationToken add_notification_callback(CollectionChangeCallback cb); - - bool wants_background_updates() const { return m_wants_background_updates; } - - // Returns whether the rows are guaranteed to be in table order. - bool is_in_table_order() const; - - // Helper type to let ResultsNotifier update the tableview without giving access - // to any other privates or letting anyone else do so - class Internal { - friend class _impl::ResultsNotifier; - static void set_table_view(Results& results, TableView&& tv); - }; - -private: - enum class UpdatePolicy { - Auto, // Update automatically to reflect changes in the underlying data. - Never, // Never update. - }; - - SharedRealm m_realm; - 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; - - _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(bool wants_notifications = true); - bool update_linkview(); - - void validate_read() const; - void validate_write() const; - - void prepare_async(); - - template - util::Optional aggregate(size_t column, bool return_none_for_empty, - const char* name, - Int agg_int, Float agg_float, - Double agg_double, Timestamp agg_timestamp); - - void set_table_view(TableView&& tv); -}; -} - -#endif /* REALM_RESULTS_HPP */ diff --git a/src/object-store/src/schema.cpp b/src/object-store/src/schema.cpp deleted file mode 100644 index ed02a902..00000000 --- a/src/object-store/src/schema.cpp +++ /dev/null @@ -1,130 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "schema.hpp" - -#include "object_schema.hpp" -#include "object_store.hpp" - -#include - -using namespace realm; - -static bool compare_by_name(ObjectSchema const& lft, ObjectSchema const& rgt) { - return lft.name < rgt.name; -} - -Schema::Schema(std::initializer_list types) : Schema(base(types)) { } - -Schema::Schema(base types) : base(std::move(types)) { - std::sort(begin(), end(), compare_by_name); -} - -Schema::iterator Schema::find(std::string const& name) -{ - ObjectSchema cmp; - cmp.name = name; - return find(cmp); -} - -Schema::const_iterator Schema::find(std::string const& name) const -{ - return const_cast(this)->find(name); -} - -Schema::iterator Schema::find(ObjectSchema const& object) noexcept -{ - auto it = std::lower_bound(begin(), end(), object, compare_by_name); - if (it != end() && it->name != object.name) { - it = end(); - } - return it; -} - -Schema::const_iterator Schema::find(ObjectSchema const& object) const noexcept -{ - return const_cast(this)->find(object); -} - -void Schema::validate() const -{ - std::vector exceptions; - for (auto const& object : *this) { - const Property *primary = nullptr; - - std::vector all_properties = object.persisted_properties; - all_properties.insert(all_properties.end(), object.computed_properties.begin(), object.computed_properties.end()); - - for (auto const& prop : all_properties) { - // check object_type existence - if (!prop.object_type.empty()) { - auto it = find(prop.object_type); - if (it == end()) { - exceptions.emplace_back(MissingObjectTypeException(object.name, prop)); - } - // validate linking objects property. - else if (!prop.link_origin_property_name.empty()) { - using ErrorType = InvalidLinkingObjectsPropertyException::Type; - util::Optional error; - - const Property *origin_property = it->property_for_name(prop.link_origin_property_name); - if (!origin_property) { - error = ErrorType::OriginPropertyDoesNotExist; - } - else if (origin_property->type != PropertyType::Object && origin_property->type != PropertyType::Array) { - error = ErrorType::OriginPropertyIsNotALink; - } - else if (origin_property->object_type != object.name) { - error = ErrorType::OriginPropertyInvalidLinkTarget; - } - - if (error) { - exceptions.emplace_back(InvalidLinkingObjectsPropertyException(*error, object.name, prop)); - } - } - } - - // check nullablity - if (prop.is_nullable) { - if (prop.type == PropertyType::Array || prop.type == PropertyType::Any || prop.type == PropertyType::LinkingObjects) { - exceptions.emplace_back(InvalidNullabilityException(object.name, prop)); - } - } - else if (prop.type == PropertyType::Object) { - exceptions.emplace_back(InvalidNullabilityException(object.name, prop)); - } - - // check primary keys - if (prop.is_primary) { - if (primary) { - exceptions.emplace_back(DuplicatePrimaryKeysException(object.name)); - } - primary = ∝ - } - - // check indexable - if (prop.is_indexed && !prop.is_indexable()) { - exceptions.emplace_back(PropertyTypeNotIndexableException(object.name, prop)); - } - } - } - - if (exceptions.size()) { - throw SchemaValidationException(exceptions); - } -} diff --git a/src/object-store/src/schema.hpp b/src/object-store/src/schema.hpp deleted file mode 100644 index 30ee153b..00000000 --- a/src/object-store/src/schema.hpp +++ /dev/null @@ -1,57 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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_SCHEMA_HPP -#define REALM_SCHEMA_HPP - -#include -#include - -namespace realm { -class ObjectSchema; - -class Schema : private std::vector { -private: - using base = std::vector; -public: - // Create a schema from a vector of ObjectSchema - Schema(base types); - Schema(std::initializer_list types); - - // find an ObjectSchema by name - iterator find(std::string const& name); - const_iterator find(std::string const& name) const; - - // find an ObjectSchema with the same name as the passed in one - iterator find(ObjectSchema const& object) noexcept; - const_iterator find(ObjectSchema const& object) const noexcept; - - // Verify that this schema is internally consistent (i.e. all properties are - // valid, links link to types that actually exist, etc.) - void validate() const; - - using base::iterator; - using base::const_iterator; - using base::begin; - using base::end; - using base::empty; - using base::size; -}; -} - -#endif /* defined(REALM_SCHEMA_HPP) */ diff --git a/src/object-store/src/shared_realm.cpp b/src/object-store/src/shared_realm.cpp deleted file mode 100644 index 228a92a6..00000000 --- a/src/object-store/src/shared_realm.cpp +++ /dev/null @@ -1,514 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "shared_realm.hpp" - -#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" - -#include -#include - -using namespace realm; -using namespace realm::_impl; - -Realm::Config::Config(const Config& c) -: path(c.path) -, encryption_key(c.encryption_key) -, schema_version(c.schema_version) -, migration_function(c.migration_function) -, read_only(c.read_only) -, in_memory(c.in_memory) -, cache(c.cache) -, disable_format_upgrade(c.disable_format_upgrade) -, automatic_change_notifications(c.automatic_change_notifications) -{ - if (c.schema) { - schema = std::make_unique(*c.schema); - } -} - -Realm::Config::Config() : schema_version(ObjectStore::NotVersioned) { } -Realm::Config::Config(Config&&) = default; -Realm::Config::~Config() = default; - -Realm::Config& Realm::Config::operator=(realm::Realm::Config const& c) -{ - if (&c != this) { - *this = Config(c); - } - return *this; -} - -Realm::Realm(Config config) -: m_config(std::move(config)) -{ - open_with_config(m_config, m_history, m_shared_group, m_read_only_group, this); - - if (m_read_only_group) { - m_group = m_read_only_group.get(); - } -} - -REALM_NOINLINE static void translate_file_exception(StringData path, bool read_only=false) -{ - try { - throw; - } - catch (util::File::PermissionDenied const& ex) { - throw RealmFileException(RealmFileException::Kind::PermissionDenied, ex.get_path(), - util::format("Unable to open a realm at path '%1'. Please use a path where your app has %2 permissions.", - ex.get_path(), read_only ? "read" : "read-write"), - ex.what()); - } - 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()), - ex.what()); - } - catch (util::File::NotFound const& ex) { - throw RealmFileException(RealmFileException::Kind::NotFound, ex.get_path(), - 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 - // don't want two copies of the path in the error, so strip it out if it - // appears, and then include it in our prefix. - std::string underlying = ex.what(); - auto pos = underlying.find(ex.get_path()); - if (pos != std::string::npos && pos > 0) { - // One extra char at each end for the quotes - 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()); - } - catch (IncompatibleLockFile const& ex) { - throw RealmFileException(RealmFileException::Kind::IncompatibleLockFile, path, - "Realm file is currently open in another process " - "which cannot share access with this process. " - "All processes sharing a single file must be the same architecture.", - ex.what()); - } - catch (FileFormatUpgradeRequired const& ex) { - throw RealmFileException(RealmFileException::Kind::FormatUpgradeRequired, path, - "The Realm file format must be allowed to be upgraded " - "in order to proceed.", - ex.what()); - } -} - -void Realm::open_with_config(const Config& config, - std::unique_ptr& history, - std::unique_ptr& shared_group, - std::unique_ptr& read_only_group, - Realm *realm) -{ - if (config.encryption_key.data() && config.encryption_key.size() != 64) { - throw InvalidEncryptionKeyException(); - } - try { - if (config.read_only) { - read_only_group = std::make_unique(config.path, config.encryption_key.data(), Group::mode_ReadOnly); - } - else { - history = realm::make_client_history(config.path, config.encryption_key.data()); - SharedGroup::DurabilityLevel durability = config.in_memory ? SharedGroup::durability_MemOnly : - SharedGroup::durability_Full; - shared_group = std::make_unique(*history, durability, config.encryption_key.data(), !config.disable_format_upgrade, - [&](int from_version, int to_version) { - if (realm) { - realm->upgrade_initial_version = from_version; - realm->upgrade_final_version = to_version; - } - }); - } - } - catch (...) { - translate_file_exception(config.path, config.read_only); - } -} - -void Realm::init(std::shared_ptr coordinator) -{ - m_coordinator = std::move(coordinator); - - // if there is an existing realm at the current path steal its schema/column mapping - if (auto existing = m_coordinator->get_schema()) { - m_config.schema = std::make_unique(*existing); - return; - } - - try { - // otherwise get the schema from the group - auto target_schema = std::move(m_config.schema); - auto target_schema_version = m_config.schema_version; - m_config.schema_version = ObjectStore::get_schema_version(read_group()); - m_config.schema = std::make_unique(ObjectStore::schema_from_group(read_group())); - - // if a target schema is supplied, verify that it matches or migrate to - // it, as neeeded - if (target_schema) { - if (m_config.read_only) { - if (m_config.schema_version == ObjectStore::NotVersioned) { - throw UninitializedRealmException("Can't open an un-initialized Realm without a Schema"); - } - target_schema->validate(); - ObjectStore::verify_schema(*m_config.schema, *target_schema, true); - m_config.schema = std::move(target_schema); - } - else { - update_schema(std::move(target_schema), target_schema_version); - } - } - - // End the read transaction created to validation/update the - // schema to avoid pinning the version even if the user never - // actually reads data - if (!m_config.read_only) { - invalidate(); - } - } - catch (...) { - // Trying to unregister from the coordinator before we finish - // construction will result in a deadlock - m_coordinator = nullptr; - throw; - } -} - -Realm::~Realm() -{ - if (m_coordinator) { - m_coordinator->unregister_realm(this); - } -} - -Group *Realm::read_group() -{ - if (!m_group) { - m_group = &const_cast(m_shared_group->begin_read()); - } - return m_group; -} - -SharedRealm Realm::get_shared_realm(Config config) -{ - auto coordinator = RealmCoordinator::get_coordinator(config.path); - return coordinator->get_realm(std::move(config)); -} - -void Realm::update_schema(std::unique_ptr schema, uint64_t version) -{ - schema->validate(); - - auto needs_update = [&] { - // If the schema version matches, just verify that the schema itself also matches - bool needs_write = !m_config.read_only && (m_config.schema_version != version || ObjectStore::needs_update(*m_config.schema, *schema)); - if (needs_write) { - return true; - } - - ObjectStore::verify_schema(*m_config.schema, *schema, m_config.read_only); - m_config.schema = std::move(schema); - m_config.schema_version = version; - m_coordinator->update_schema(*m_config.schema); - return false; - }; - - if (!needs_update()) { - return; - } - - read_group(); - transaction::begin(*m_shared_group, m_binding_context.get(), - /* error on schema changes */ false); - - struct WriteTransactionGuard { - Realm& realm; - ~WriteTransactionGuard() { - if (realm.is_in_transaction()) { - realm.cancel_transaction(); - } - } - } write_transaction_guard{*this}; - - // Recheck the schema version after beginning the write transaction - // If it changed then someone else initialized the schema and we need to - // recheck everything - auto current_schema_version = ObjectStore::get_schema_version(read_group()); - if (current_schema_version != m_config.schema_version) { - m_config.schema_version = current_schema_version; - *m_config.schema = ObjectStore::schema_from_group(read_group()); - - if (!needs_update()) { - cancel_transaction(); - return; - } - } - - Config old_config(m_config); - auto migration_function = [&](Group*, Schema&) { - SharedRealm old_realm(new Realm(old_config)); - // Need to open in read-write mode so that it uses a SharedGroup, but - // users shouldn't actually be able to write via the old realm - old_realm->m_config.read_only = true; - - if (m_config.migration_function) { - m_config.migration_function(old_realm, shared_from_this()); - } - m_config.migration_function = nullptr; - }; - - try { - m_config.schema = std::move(schema); - m_config.schema_version = version; - - ObjectStore::update_realm_with_schema(read_group(), *old_config.schema, - version, *m_config.schema, - migration_function); - commit_transaction(); - } - catch (...) { - m_config.schema = std::move(old_config.schema); - m_config.schema_version = old_config.schema_version; - throw; - } - - m_coordinator->update_schema(*m_config.schema); -} - -static void check_read_write(Realm *realm) -{ - if (realm->config().read_only) { - throw InvalidTransactionException("Can't perform transactions on read-only Realms."); - } -} - -void Realm::verify_thread() const -{ - if (m_thread_id != util::get_thread_id()) { - throw IncorrectThreadException(); - } -} - -void Realm::verify_in_write() const -{ - if (!is_in_transaction()) { - throw InvalidTransactionException("Cannot modify managed objects outside of a write transaction."); - } -} - -bool Realm::is_in_transaction() const noexcept -{ - if (!m_shared_group) { - return false; - } - return m_shared_group->get_transact_stage() == SharedGroup::transact_Writing; -} - -void Realm::begin_transaction() -{ - check_read_write(this); - verify_thread(); - - if (is_in_transaction()) { - throw InvalidTransactionException("The Realm is already in a write transaction"); - } - - // make sure we have a read transaction - read_group(); - - transaction::begin(*m_shared_group, m_binding_context.get()); -} - -void Realm::commit_transaction() -{ - check_read_write(this); - verify_thread(); - - if (!is_in_transaction()) { - throw InvalidTransactionException("Can't commit a non-existing write transaction"); - } - - transaction::commit(*m_shared_group, m_binding_context.get()); - m_coordinator->send_commit_notifications(); -} - -void Realm::cancel_transaction() -{ - check_read_write(this); - verify_thread(); - - if (!is_in_transaction()) { - throw InvalidTransactionException("Can't cancel a non-existing write transaction"); - } - - transaction::cancel(*m_shared_group, m_binding_context.get()); -} - -void Realm::invalidate() -{ - verify_thread(); - check_read_write(this); - - if (is_in_transaction()) { - cancel_transaction(); - } - if (!m_group) { - return; - } - - m_shared_group->end_read(); - m_group = nullptr; -} - -bool Realm::compact() -{ - verify_thread(); - - if (m_config.read_only) { - throw InvalidTransactionException("Can't compact a read-only Realm"); - } - if (is_in_transaction()) { - throw InvalidTransactionException("Can't compact a Realm within a write transaction"); - } - - Group* group = read_group(); - for (auto &object_schema : *m_config.schema) { - ObjectStore::table_for_object_type(group, object_schema.name)->optimize(); - } - m_shared_group->end_read(); - m_group = nullptr; - - return m_shared_group->compact(); -} - -void Realm::write_copy(StringData path, BinaryData key) -{ - REALM_ASSERT(!key.data() || key.size() == 64); - verify_thread(); - try { - read_group()->write(path, key.data()); - } - catch (...) { - translate_file_exception(path); - } -} - -void Realm::notify() -{ - verify_thread(); - - if (m_shared_group->has_changed()) { // Throws - if (m_binding_context) { - m_binding_context->changes_available(); - } - if (m_auto_refresh) { - if (m_group) { - m_coordinator->advance_to_ready(*this); - } - else if (m_binding_context) { - m_binding_context->did_change({}, {}); - } - } - } - else { - m_coordinator->process_available_async(*this); - } -} - -bool Realm::refresh() -{ - verify_thread(); - check_read_write(this); - - // can't be any new changes if we're in a write transaction - if (is_in_transaction()) { - return false; - } - - // advance transaction if database has changed - if (!m_shared_group->has_changed()) { // Throws - return false; - } - - if (m_group) { - transaction::advance(*m_shared_group, m_binding_context.get()); - m_coordinator->process_available_async(*this); - } - else { - // Create the read transaction - read_group(); - } - - return true; -} - -bool Realm::can_deliver_notifications() const noexcept -{ - if (m_config.read_only) { - return false; - } - - if (m_binding_context && !m_binding_context->can_deliver_notifications()) { - return false; - } - - return true; -} - -uint64_t Realm::get_schema_version(const realm::Realm::Config &config) -{ - auto coordinator = RealmCoordinator::get_existing_coordinator(config.path); - if (coordinator) { - return coordinator->get_schema_version(); - } - - return ObjectStore::get_schema_version(Realm(config).read_group()); -} - -void Realm::close() -{ - if (m_coordinator) { - m_coordinator->unregister_realm(this); - } - - m_group = nullptr; - m_shared_group = nullptr; - m_history = nullptr; - m_read_only_group = nullptr; - m_binding_context = nullptr; - m_coordinator = nullptr; -} - -util::Optional Realm::file_format_upgraded_from_version() const -{ - if (upgrade_initial_version != upgrade_final_version) { - return upgrade_initial_version; - } - return util::Optional(); -} - -MismatchedConfigException::MismatchedConfigException(StringData message, StringData 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 deleted file mode 100644 index 562ffc69..00000000 --- a/src/object-store/src/shared_realm.hpp +++ /dev/null @@ -1,257 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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_REALM_HPP -#define REALM_REALM_HPP - -#include "util/thread_id.hpp" - -#include -#include -#include -#include - -namespace realm { - class BinaryData; - class BindingContext; - class Group; - class Realm; - class Replication; - class Schema; - class SharedGroup; - class StringData; - typedef std::shared_ptr SharedRealm; - typedef std::weak_ptr WeakRealm; - - namespace _impl { - class CollectionNotifier; - class ListNotifier; - class RealmCoordinator; - class ResultsNotifier; - } - - namespace util { - template class Optional; - } - - class Realm : public std::enable_shared_from_this { - public: - typedef std::function MigrationFunction; - - struct Config { - std::string path; - // User-supplied encryption key. Must be either empty or 64 bytes. - std::vector encryption_key; - - // Optional schema for the file. If nullptr, the existing schema - // from the file opened will be used. If present, the file will be - // migrated to the schema if needed. - std::unique_ptr schema; - uint64_t schema_version; - - MigrationFunction migration_function; - - bool read_only = false; - bool in_memory = false; - - // The following are intended for internal/testing purposes and - // should not be publicly exposed in binding APIs - - // If false, always return a new Realm instance, and don't return - // that Realm instance for other requests for a cached Realm. Useful - // for dynamic Realms and for tests that need multiple instances on - // one thread - bool cache = true; - // Throw an exception rather than automatically upgrading the file - // format. Used by the browser to warn the user that it'll modify - // the file. - bool disable_format_upgrade = false; - // Disable the background worker thread for producing change - // notifications. Useful for tests for those notifications so that - // everything can be done deterministically on one thread, and - // speeds up tests that don't need notifications. - bool automatic_change_notifications = true; - - Config(); - Config(Config&&); - Config(const Config& c); - ~Config(); - - Config& operator=(Config const&); - Config& operator=(Config&&) = default; - }; - - // Get a cached Realm or create a new one if no cached copies exists - // Caching is done by path - mismatches for in_memory and read_only - // Config properties will raise an exception - // If schema/schema_version is specified, update_schema is called - // automatically on the realm and a migration is performed. If not - // specified, the schema version and schema are dynamically read from - // the the existing Realm. - static SharedRealm get_shared_realm(Config config); - - // Updates a Realm to a given target schema/version creating tables and - // updating indexes as necessary. Uses the existing migration function - // on the Config, and the resulting Schema and version with updated - // column mappings are set on the realms config upon success. - void update_schema(std::unique_ptr schema, uint64_t version); - - static uint64_t get_schema_version(Config const& config); - - const Config &config() const { return m_config; } - - void begin_transaction(); - void commit_transaction(); - void cancel_transaction(); - bool is_in_transaction() const noexcept; - bool is_in_read_transaction() const { return !!m_group; } - - bool refresh(); - void set_auto_refresh(bool auto_refresh) { m_auto_refresh = auto_refresh; } - bool auto_refresh() const { return m_auto_refresh; } - void notify(); - - void invalidate(); - bool compact(); - void write_copy(StringData path, BinaryData encryption_key); - - thread_id_t thread_id() const { return m_thread_id; } - void verify_thread() const; - void verify_in_write() const; - - bool can_deliver_notifications() const noexcept; - - // Close this Realm and remove it from the cache. Continuing to use a - // Realm after closing it will produce undefined behavior. - void close(); - - bool is_closed() { return !m_read_only_group && !m_shared_group; } - - // returns the file format version upgraded from if an upgrade took place - util::Optional file_format_upgraded_from_version() const; - - ~Realm(); - - void init(std::shared_ptr<_impl::RealmCoordinator> coordinator); - Realm(Config config); - - // Expose some internal functionality to other parts of the ObjectStore - // without making it public to everyone - class Internal { - friend class _impl::CollectionNotifier; - friend class _impl::ListNotifier; - friend class _impl::RealmCoordinator; - friend class _impl::ResultsNotifier; - - // ResultsNotifier and ListNotifier need access to the SharedGroup - // to be able to call the handover functions, which are not very wrappable - static SharedGroup& get_shared_group(Realm& realm) { return *realm.m_shared_group; } - - // CollectionNotifier needs to be able to access the owning - // coordinator to wake up the worker thread when a callback is - // added, and coordinators need to be able to get themselves from a Realm - static _impl::RealmCoordinator& get_coordinator(Realm& realm) { return *realm.m_coordinator; } - }; - - static void open_with_config(const Config& config, - std::unique_ptr& history, - std::unique_ptr& shared_group, - std::unique_ptr& read_only_group, - Realm *realm = nullptr); - - private: - Config m_config; - thread_id_t m_thread_id = util::get_thread_id(); - bool m_auto_refresh = true; - - std::unique_ptr m_history; - std::unique_ptr m_shared_group; - std::unique_ptr m_read_only_group; - - Group *m_group = nullptr; - - std::shared_ptr<_impl::RealmCoordinator> m_coordinator; - - // File format versions populated when a file format upgrade takes place during realm opening - int upgrade_initial_version = 0, upgrade_final_version = 0; - - public: - std::unique_ptr m_binding_context; - - // FIXME private - Group *read_group(); - }; - - class RealmFileException : public std::runtime_error { - public: - enum class Kind { - /** Thrown for any I/O related exception scenarios when a realm is opened. */ - AccessError, - /** Thrown if the user does not have permission to open or create - the specified file in the specified access mode when the realm is opened. */ - PermissionDenied, - /** Thrown if create_Always was specified and the file did already exist when the realm is opened. */ - Exists, - /** Thrown if no_create was specified and the file was not found when the realm is opened. */ - NotFound, - /** Thrown if the database file is currently open in another - process which cannot share with the current process due to an - architecture mismatch. */ - IncompatibleLockFile, - /** Thrown if the file needs to be upgraded to a new format, but upgrades have been explicitly disabled. */ - FormatUpgradeRequired, - }; - RealmFileException(Kind kind, std::string path, std::string message, std::string underlying) : - std::runtime_error(std::move(message)), m_kind(kind), m_path(std::move(path)), m_underlying(std::move(underlying)) {} - Kind kind() const { return m_kind; } - const std::string& path() const { return m_path; } - const std::string& underlying() const { return m_underlying; } - - private: - Kind m_kind; - std::string m_path; - std::string m_underlying; - }; - - class MismatchedConfigException : public std::logic_error { - public: - MismatchedConfigException(StringData message, StringData path); - }; - - class InvalidTransactionException : public std::logic_error { - public: - InvalidTransactionException(std::string message) : std::logic_error(move(message)) {} - }; - - class IncorrectThreadException : public std::logic_error { - public: - IncorrectThreadException() : std::logic_error("Realm accessed from incorrect thread.") {} - }; - - class UninitializedRealmException : public std::runtime_error { - public: - UninitializedRealmException(std::string message) : std::runtime_error(move(message)) {} - }; - - class InvalidEncryptionKeyException : public std::logic_error { - public: - InvalidEncryptionKeyException() : std::logic_error("Encryption key must be 64 bytes.") {} - }; -} - -#endif /* defined(REALM_REALM_HPP) */ diff --git a/src/object-store/src/util/atomic_shared_ptr.hpp b/src/object-store/src/util/atomic_shared_ptr.hpp deleted file mode 100644 index 1d07e022..00000000 --- a/src/object-store/src/util/atomic_shared_ptr.hpp +++ /dev/null @@ -1,137 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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_ATOMIC_SHARED_PTR_HPP -#define REALM_ATOMIC_SHARED_PTR_HPP - -#include -#include -#include - -namespace realm { -namespace _impl { - -// Check if std::atomic_load has an overload taking a std::shared_ptr, and set -// HasAtomicPtrOps to either true_type or false_type - -template struct make_void { typedef void type; }; -template using void_t = typename make_void::type; - -template> -struct HasAtomicPtrOps : std::false_type { }; - -template -struct HasAtomicPtrOps()))>> : std::true_type { }; -} // namespace _impl - -namespace util { -// A wrapper for std::shared_ptr that enables sharing a shared_ptr instance -// (and not just a thing *pointed to* by a shared_ptr) between threads. Is -// lock-free iff the underlying shared_ptr implementation supports atomic -// operations. Currently the only implemented operation other than copy/move -// construction/assignment is exchange(). -template>::value> -class AtomicSharedPtr; - -template -class AtomicSharedPtr { -public: - AtomicSharedPtr() = default; - AtomicSharedPtr(std::shared_ptr ptr) : m_ptr(std::move(ptr)) { } - - AtomicSharedPtr(AtomicSharedPtr const& ptr) : m_ptr(std::atomic_load(&ptr.m_ptr)) { } - AtomicSharedPtr(AtomicSharedPtr&& ptr) : m_ptr(std::atomic_exchange(&ptr.m_ptr, {})) { } - - AtomicSharedPtr& operator=(AtomicSharedPtr const& ptr) - { - if (&ptr != this) { - std::atomic_store(&m_ptr, std::atomic_load(&ptr.m_ptr)); - } - return *this; - } - - AtomicSharedPtr& operator=(AtomicSharedPtr&& ptr) - { - std::atomic_store(&m_ptr, std::atomic_exchange(&ptr.m_ptr, {})); - return *this; - } - - std::shared_ptr exchange(std::shared_ptr ptr) - { - return std::atomic_exchange(&m_ptr, std::move(ptr)); - } - -private: - std::shared_ptr m_ptr = nullptr; -}; - -template -class AtomicSharedPtr { -public: - AtomicSharedPtr() = default; - AtomicSharedPtr(std::shared_ptr ptr) : m_ptr(std::move(ptr)) { } - - AtomicSharedPtr(AtomicSharedPtr const& ptr) - { - std::lock_guard lock(ptr.m_mutex); - m_ptr = ptr.m_ptr; - } - AtomicSharedPtr(AtomicSharedPtr&& ptr) - { - std::lock_guard lock(ptr.m_mutex); - m_ptr = std::move(ptr.m_ptr); - } - - AtomicSharedPtr& operator=(AtomicSharedPtr const& ptr) - { - if (&ptr != this) { - // std::lock() ensures that these are locked in a consistent order - // to avoid deadlock - std::lock(m_mutex, ptr.m_mutex); - m_ptr = ptr.m_ptr; - m_mutex.unlock(); - ptr.m_mutex.unlock(); - } - return *this; - } - - AtomicSharedPtr& operator=(AtomicSharedPtr&& ptr) - { - std::lock(m_mutex, ptr.m_mutex); - m_ptr = std::move(ptr.m_ptr); - m_mutex.unlock(); - ptr.m_mutex.unlock(); - return *this; - } - - std::shared_ptr exchange(std::shared_ptr ptr) - { - std::lock_guard lock(m_mutex); - m_ptr.swap(ptr); - return ptr; - } - -private: - std::mutex m_mutex; - std::shared_ptr m_ptr = nullptr; -}; - -} -} - -#endif // REALM_ATOMIC_SHARED_PTR_HPP diff --git a/src/object-store/src/util/compiler.hpp b/src/object-store/src/util/compiler.hpp deleted file mode 100644 index ee9a0d01..00000000 --- a/src/object-store/src/util/compiler.hpp +++ /dev/null @@ -1,34 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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/src/util/format.cpp b/src/object-store/src/util/format.cpp deleted file mode 100644 index 4103c8d9..00000000 --- a/src/object-store/src/util/format.cpp +++ /dev/null @@ -1,82 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "util/format.hpp" - -#include - -#include -#include - -namespace realm { namespace _impl { -Printable::Printable(StringData value) : m_type(Type::String), m_string(value.data()) { } - -void Printable::print(std::ostream& out) const -{ - switch (m_type) { - case Printable::Type::Bool: - out << (m_uint ? "true" : "false"); - break; - case Printable::Type::Uint: - out << m_uint; - break; - case Printable::Type::Int: - out << m_int; - break; - case Printable::Type::String: - out << m_string; - break; - } -} - -std::string format(const char* fmt, std::initializer_list values) -{ - std::stringstream ss; - while (*fmt) { - auto next = strchr(fmt, '%'); - - // emit the rest of the format string if there are no more percents - if (!next) { - ss << fmt; - break; - } - - // emit everything up to the next percent - ss.write(fmt, next - fmt); - ++next; - REALM_ASSERT(*next); - - // %% produces a single escaped % - if (*next == '%') { - ss << '%'; - fmt = next + 1; - continue; - } - REALM_ASSERT(isdigit(*next)); - - // The const_cast is safe because stroul does not actually modify - // the pointed-to string, but it lacks a const overload - auto index = strtoul(next, const_cast(&fmt), 10) - 1; - REALM_ASSERT(index < values.size()); - (values.begin() + index)->print(ss); - } - return ss.str(); -} - -} // namespace _impl -} // namespace realm diff --git a/src/object-store/src/util/format.hpp b/src/object-store/src/util/format.hpp deleted file mode 100644 index 8bf9a5d9..00000000 --- a/src/object-store/src/util/format.hpp +++ /dev/null @@ -1,75 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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_FORMAT_HPP -#define REALM_UTIL_FORMAT_HPP - -#include -#include -#include -#include - -namespace realm { -class StringData; - -namespace _impl { -class Printable { -public: - Printable(bool value) : m_type(Type::Bool), m_uint(value) { } - Printable(unsigned char value) : m_type(Type::Uint), m_uint(value) { } - Printable(unsigned int value) : m_type(Type::Uint), m_uint(value) { } - Printable(unsigned long value) : m_type(Type::Uint), m_uint(value) { } - Printable(unsigned long long value) : m_type(Type::Uint), m_uint(value) { } - Printable(char value) : m_type(Type::Int), m_int(value) { } - Printable(int value) : m_type(Type::Int), m_int(value) { } - Printable(long value) : m_type(Type::Int), m_int(value) { } - Printable(long long value) : m_type(Type::Int), m_int(value) { } - Printable(const char* value) : m_type(Type::String), m_string(value) { } - Printable(std::string const& value) : m_type(Type::String), m_string(value.c_str()) { } - Printable(StringData value); - - void print(std::ostream& out) const; - -private: - enum class Type { - Bool, - Int, - Uint, - String - } m_type; - - union { - uintmax_t m_uint; - intmax_t m_int; - const char* m_string; - }; -}; -std::string format(const char* fmt, std::initializer_list); -} // namespace _impl - -namespace util { -template -std::string format(const char* fmt, Args&&... args) -{ - return _impl::format(fmt, {_impl::Printable(args)...}); -} - -} // namespace util -} // namespace realm - -#endif // REALM_UTIL_FORMAT_HPP diff --git a/src/object-store/src/util/thread_id.cpp b/src/object-store/src/util/thread_id.cpp deleted file mode 100644 index 6c76adb5..00000000 --- a/src/object-store/src/util/thread_id.cpp +++ /dev/null @@ -1,37 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "thread_id.hpp" -#include "thread_local.hpp" - -#include - -using namespace realm; - -// Since std::thread::id may be reused after a thread is destroyed, we use -// an atomically incremented, thread-local identifier instead. -thread_id_t realm::util::get_thread_id() { - static std::atomic id_counter; - static REALM_THREAD_LOCAL_TYPE(thread_id_t) thread_id = 0; - - if (REALM_UNLIKELY(!thread_id)) { - thread_id = ++id_counter; - } - - return thread_id; -} diff --git a/src/object-store/src/util/thread_id.hpp b/src/object-store/src/util/thread_id.hpp deleted file mode 100644 index b3294a04..00000000 --- a/src/object-store/src/util/thread_id.hpp +++ /dev/null @@ -1,37 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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_THREAD_ID_HPP -#define REALM_THREAD_ID_HPP - -#include - -namespace realm { - -using thread_id_t = std::size_t; - -namespace util { - -// Since std::thread::id may be reused after a thread is destroyed, we use -// an atomically incremented, thread-local identifier instead. -thread_id_t get_thread_id(); - -} // namespace util -} // namespace realm - -#endif // REALM_THREAD_ID_HPP diff --git a/src/object-store/src/util/thread_local.hpp b/src/object-store/src/util/thread_local.hpp deleted file mode 100644 index ba1dd499..00000000 --- a/src/object-store/src/util/thread_local.hpp +++ /dev/null @@ -1,80 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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_THREAD_LOCAL_HPP -#define REALM_THREAD_LOCAL_HPP - -#include - -#if (!defined(__clang__) || REALM_HAVE_CLANG_FEATURE(tls) || REALM_HAVE_CLANG_FEATURE(cxx_thread_local)) && \ - (!REALM_PLATFORM_APPLE || __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000 || MAC_OS_X_VERSION_MIN_REQUIRED >= 1070) - -#define REALM_THREAD_LOCAL_TYPE(type) REALM_THREAD_LOCAL type - -#else - -#define REALM_THREAD_LOCAL_TYPE(type) realm::_impl::ThreadLocal - -#include - -namespace realm { -namespace _impl { - -template -class ThreadLocal { - public: - ThreadLocal() : m_initial_value() { - init_key(); - } - ThreadLocal(const T &value) : m_initial_value(value) { - init_key(); - } - ~ThreadLocal() { - pthread_key_delete(m_key); - } - - operator T&() { - void* ptr = pthread_getspecific(m_key); - if (!ptr) { - ptr = new T(m_initial_value); - pthread_setspecific(m_key, ptr); - } - return *static_cast(ptr); - } - T& operator=(const T &value) { - T& value_ref = operator T&(); - value_ref = value; - return value_ref; - } - - private: - T m_initial_value; - pthread_key_t m_key; - - void init_key() { - pthread_key_create(&m_key, [](void* ptr) { - delete static_cast(ptr); - }); - } -}; - -} // namespace _impl -} // namespace realm - -#endif -#endif // REALM_THREAD_LOCAL_HPP diff --git a/src/object-store/tests/CMakeLists.txt b/src/object-store/tests/CMakeLists.txt deleted file mode 100644 index 6e055b4d..00000000 --- a/src/object-store/tests/CMakeLists.txt +++ /dev/null @@ -1,26 +0,0 @@ -include_directories(../external/catch/single_include .) - -set(HEADERS - util/index_helpers.hpp - util/test_file.hpp -) - -set(SOURCES - collection_change_indices.cpp - index_set.cpp - list.cpp - main.cpp - parser.cpp - results.cpp - transaction_log_parsing.cpp - util/test_file.cpp -) - -add_executable(tests ${SOURCES} ${HEADERS}) -target_link_libraries(tests realm-object-store) - -create_coverage_target(generate-coverage tests) - -add_custom_target(run-tests USES_TERMINAL DEPENDS tests COMMAND ./tests) - -add_subdirectory(notifications-fuzzer) diff --git a/src/object-store/tests/collection_change_indices.cpp b/src/object-store/tests/collection_change_indices.cpp deleted file mode 100644 index d0f9ec92..00000000 --- a/src/object-store/tests/collection_change_indices.cpp +++ /dev/null @@ -1,986 +0,0 @@ -#include "catch.hpp" - -#include "impl/collection_notifier.hpp" - -#include "util/index_helpers.hpp" - -#include - -using namespace realm; - -TEST_CASE("[collection_change] insert()") { - _impl::CollectionChangeBuilder c; - - SECTION("adds the row to the insertions set") { - c.insert(5); - c.insert(8); - REQUIRE_INDICES(c.insertions, 5, 8); - } - - SECTION("shifts previous insertions and modifications") { - c.insert(5); - c.modify(8); - - c.insert(1); - REQUIRE_INDICES(c.insertions, 1, 6); - REQUIRE_INDICES(c.modifications, 9); - } - - SECTION("does not shift previous deletions") { - c.erase(8); - c.erase(3); - c.insert(5); - - REQUIRE_INDICES(c.insertions, 5); - REQUIRE_INDICES(c.deletions, 3, 8); - } - - SECTION("shifts destination of previous moves after the insertion point") { - c.moves = {{10, 5}, {10, 2}, {3, 10}}; - c.insert(4); - REQUIRE_MOVES(c, {10, 6}, {10, 2}, {3, 11}); - } -} - -TEST_CASE("[collection_change] modify()") { - _impl::CollectionChangeBuilder c; - - SECTION("marks the row as modified") { - c.modify(5); - REQUIRE_INDICES(c.modifications, 5); - } - - SECTION("also marks newly inserted rows as modified") { - c.insert(5); - c.modify(5); - REQUIRE_INDICES(c.modifications, 5); - } - - SECTION("is idempotent") { - c.modify(5); - c.modify(5); - c.modify(5); - c.modify(5); - REQUIRE_INDICES(c.modifications, 5); - } -} - -TEST_CASE("[collection_change] erase()") { - _impl::CollectionChangeBuilder c; - - SECTION("adds the row to the deletions set") { - c.erase(5); - REQUIRE_INDICES(c.deletions, 5); - } - - SECTION("is shifted for previous deletions") { - c.erase(5); - c.erase(6); - REQUIRE_INDICES(c.deletions, 5, 7); - } - - SECTION("is shifted for previous insertions") { - c.insert(5); - c.erase(6); - REQUIRE_INDICES(c.deletions, 5); - } - - SECTION("removes previous insertions") { - c.insert(5); - c.erase(5); - REQUIRE(c.insertions.empty()); - REQUIRE(c.deletions.empty()); - } - - SECTION("removes previous modifications") { - c.modify(5); - c.erase(5); - REQUIRE(c.modifications.empty()); - REQUIRE_INDICES(c.deletions, 5); - } - - SECTION("shifts previous modifications") { - c.modify(5); - c.erase(4); - REQUIRE_INDICES(c.modifications, 4); - REQUIRE_INDICES(c.deletions, 4); - } - - SECTION("removes previous moves to the row being erased") { - c.moves = {{10, 5}}; - c.erase(5); - REQUIRE(c.moves.empty()); - } - - SECTION("shifts the destination of previous moves") { - c.moves = {{10, 5}, {10, 2}, {3, 10}}; - c.erase(4); - REQUIRE_MOVES(c, {10, 4}, {10, 2}, {3, 9}); - } -} - -TEST_CASE("[collection_change] move_over()") { - _impl::CollectionChangeBuilder c; - - SECTION("is just erase when row == last_row") { - c.move_over(10, 10); - c.parse_complete(); - - REQUIRE_INDICES(c.deletions, 10); - REQUIRE(c.insertions.empty()); - REQUIRE(c.moves.empty()); - } - - SECTION("is just erase when row + 1 == last_row") { - c.move_over(0, 6); - c.move_over(4, 5); - c.move_over(0, 4); - c.move_over(2, 3); - c.parse_complete(); - c.clean_up_stale_moves(); - - REQUIRE_INDICES(c.deletions, 0, 2, 4, 5, 6); - REQUIRE_INDICES(c.insertions, 0); - REQUIRE_MOVES(c, {5, 0}); - } - - SECTION("marks the old last row as moved") { - c.move_over(5, 8); - c.parse_complete(); - REQUIRE_MOVES(c, {8, 5}); - } - - SECTION("does not mark the old last row as moved if it was newly inserted") { - c.insert(8); - c.move_over(5, 8); - c.parse_complete(); - REQUIRE(c.moves.empty()); - } - - SECTION("removes previous modifications for the removed row") { - c.modify(5); - c.move_over(5, 8); - c.parse_complete(); - REQUIRE(c.modifications.empty()); - } - - SECTION("updates previous insertions for the old last row") { - c.insert(5); - c.move_over(3, 5); - c.parse_complete(); - REQUIRE_INDICES(c.insertions, 3); - } - - SECTION("updates previous modifications for the old last row") { - c.modify(5); - c.move_over(3, 5); - c.parse_complete(); - REQUIRE_INDICES(c.modifications, 3); - } - - SECTION("removes moves to the target") { - c.move_over(5, 10); - c.move_over(5, 8); - c.parse_complete(); - REQUIRE_MOVES(c, {8, 5}); - } - - SECTION("updates moves to the source") { - c.move_over(8, 10); - c.move_over(5, 8); - c.parse_complete(); - REQUIRE_MOVES(c, {10, 5}); - } - - SECTION("removes moves to the row when row == last_row") { - c.move_over(0, 1); - c.move_over(0, 0); - c.parse_complete(); - - REQUIRE_INDICES(c.deletions, 0, 1); - REQUIRE(c.insertions.empty()); - REQUIRE(c.moves.empty()); - } - - SECTION("is not shifted by previous calls to move_over()") { - c.move_over(5, 10); - c.move_over(6, 9); - c.parse_complete(); - REQUIRE_INDICES(c.deletions, 5, 6, 9, 10); - REQUIRE_INDICES(c.insertions, 5, 6); - REQUIRE_MOVES(c, {9, 6}, {10, 5}); - } - - SECTION("marks the moved-over row as deleted when chaining moves") { - c.move_over(5, 10); - c.move_over(0, 5); - c.parse_complete(); - - REQUIRE_INDICES(c.deletions, 0, 5, 10); - REQUIRE_INDICES(c.insertions, 0); - REQUIRE_MOVES(c, {10, 0}); - } -} - -TEST_CASE("[collection_change] clear()") { - _impl::CollectionChangeBuilder c; - - SECTION("removes all insertions") { - c.insertions = {1, 2, 3}; - c.clear(0); - REQUIRE(c.insertions.empty()); - } - - SECTION("removes all modifications") { - c.modifications = {1, 2, 3}; - c.clear(0); - REQUIRE(c.modifications.empty()); - } - - SECTION("removes all moves") { - c.moves = {{1, 3}}; - c.clear(0); - REQUIRE(c.moves.empty()); - } - - SECTION("sets deletions to the number of rows before any changes") { - c.insertions = {1, 2, 3}; - c.clear(5); - REQUIRE_INDICES(c.deletions, 0, 1); - - c.deletions = {1, 2, 3}; - c.clear(5); - REQUIRE_INDICES(c.deletions, 0, 1, 2, 3, 4, 5, 6, 7); - } - - SECTION("sets deletions SIZE_T_MAX if that if the given previous size") { - c.insertions = {1, 2, 3}; - c.clear(std::numeric_limits::max()); - REQUIRE(!c.deletions.empty()); - REQUIRE(++c.deletions.begin() == c.deletions.end()); - REQUIRE(c.deletions.begin()->first == 0); - REQUIRE(c.deletions.begin()->second == std::numeric_limits::max()); - } -} - -TEST_CASE("[collection_change] move()") { - _impl::CollectionChangeBuilder c; - - SECTION("adds the move to the list of moves") { - c.move(5, 6); - REQUIRE_MOVES(c, {5, 6}); - } - - SECTION("updates previous moves to the source of this move") { - c.move(5, 6); - c.move(6, 7); - REQUIRE_MOVES(c, {5, 7}); - } - - SECTION("shifts previous moves and is shifted by them") { - c.move(5, 10); - c.move(6, 12); - REQUIRE_MOVES(c, {5, 9}, {7, 12}); - - c.move(10, 0); - REQUIRE_MOVES(c, {5, 10}, {7, 12}, {11, 0}); - } - - SECTION("does not report a move if the source is newly inserted") { - c.insert(5); - c.move(5, 10); - REQUIRE_INDICES(c.insertions, 10); - REQUIRE(c.moves.empty()); - } - - SECTION("shifts previous insertions and modifications") { - c.insert(5); - c.modify(6); - c.move(10, 0); - REQUIRE_INDICES(c.insertions, 0, 6); - REQUIRE_INDICES(c.modifications, 7); - REQUIRE_MOVES(c, {9, 0}); - } - - SECTION("marks the target row as modified if the source row was") { - c.modify(5); - - c.move(5, 10); - REQUIRE_INDICES(c.modifications, 10); - - c.move(6, 12); - REQUIRE_INDICES(c.modifications, 9); - } - - SECTION("bumps previous moves to the same location") { - c.move(5, 10); - c.move(7, 10); - REQUIRE_MOVES(c, {5, 9}, {8, 10}); - - c = {}; - c.move(5, 10); - c.move(15, 10); - REQUIRE_MOVES(c, {5, 11}, {15, 10}); - } - - SECTION("collapses redundant swaps of adjacent rows to a no-op") { - c.move(7, 8); - c.move(7, 8); - c.clean_up_stale_moves(); - REQUIRE(c.empty()); - } -} - -TEST_CASE("[collection_change] calculate() unsorted") { - _impl::CollectionChangeBuilder c; - - auto all_modified = [](size_t) { return true; }; - auto none_modified = [](size_t) { return false; }; - const auto npos = size_t(-1); - - SECTION("returns an empty set when input and output are identical") { - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3}, {1, 2, 3}, none_modified, true); - REQUIRE(c.empty()); - } - - SECTION("marks all as inserted when prev is empty") { - c = _impl::CollectionChangeBuilder::calculate({}, {1, 2, 3}, all_modified, true); - REQUIRE_INDICES(c.insertions, 0, 1, 2); - } - - SECTION("marks all as deleted when new is empty") { - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3}, {}, all_modified, true); - REQUIRE_INDICES(c.deletions, 0, 1, 2); - } - - SECTION("marks npos rows in prev as deleted") { - c = _impl::CollectionChangeBuilder::calculate({npos, 1, 2, 3, npos}, {1, 2, 3}, all_modified, true); - REQUIRE_INDICES(c.deletions, 0, 4); - } - - SECTION("marks modified rows which do not move as modified") { - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3}, {1, 2, 3}, all_modified, true); - REQUIRE_INDICES(c.modifications, 0, 1, 2); - } - - SECTION("does not mark unmodified rows as modified") { - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3}, {1, 2, 3}, none_modified, true); - REQUIRE(c.modifications.empty()); - } - - SECTION("marks newly added rows as insertions") { - c = _impl::CollectionChangeBuilder::calculate({2, 3}, {1, 2, 3}, all_modified, true); - REQUIRE_INDICES(c.insertions, 0); - - c = _impl::CollectionChangeBuilder::calculate({1, 3}, {1, 2, 3}, all_modified, true); - REQUIRE_INDICES(c.insertions, 1); - - c = _impl::CollectionChangeBuilder::calculate({1, 2}, {1, 2, 3}, all_modified, true); - REQUIRE_INDICES(c.insertions, 2); - } - - SECTION("marks removed rows as deleted") { - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3}, {1, 2}, all_modified, true); - REQUIRE_INDICES(c.deletions, 2); - - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3}, {1, 3}, all_modified, true); - REQUIRE_INDICES(c.deletions, 1); - - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3}, {2, 3}, all_modified, true); - REQUIRE_INDICES(c.deletions, 0); - } - - SECTION("marks rows as both inserted and deleted") { - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3}, {1, 3, 4}, all_modified, true); - REQUIRE_INDICES(c.deletions, 1); - REQUIRE_INDICES(c.insertions, 2); - REQUIRE(c.moves.empty()); - } - - SECTION("marks rows as modified even if they moved") { - c = _impl::CollectionChangeBuilder::calculate({5, 3}, {3, 5}, all_modified, true); - REQUIRE_MOVES(c, {1, 0}); - REQUIRE_INDICES(c.modifications, 0, 1); - } - - SECTION("does not mark rows as modified if they are new") { - c = _impl::CollectionChangeBuilder::calculate({3}, {3, 5}, all_modified, true); - REQUIRE_INDICES(c.modifications, 0); - } - - SECTION("reports moves which can be produced by move_last_over()") { - auto calc = [&](std::vector values) { - return _impl::CollectionChangeBuilder::calculate(values, {1, 2, 3}, none_modified, true); - }; - - REQUIRE(calc({1, 2, 3}).empty()); - REQUIRE_MOVES(calc({1, 3, 2}), {2, 1}); - REQUIRE_MOVES(calc({2, 1, 3}), {1, 0}); - REQUIRE_MOVES(calc({2, 3, 1}), {2, 0}); - REQUIRE_MOVES(calc({3, 1, 2}), {1, 0}, {2, 1}); - REQUIRE_MOVES(calc({3, 2, 1}), {2, 0}, {1, 1}); - } -} - -TEST_CASE("[collection_change] calculate() sorted") { - _impl::CollectionChangeBuilder c; - - auto all_modified = [](size_t) { return true; }; - auto none_modified = [](size_t) { return false; }; - const auto npos = size_t(-1); - - SECTION("returns an empty set when input and output are identical") { - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3}, {1, 2, 3}, none_modified, false); - REQUIRE(c.empty()); - } - - SECTION("marks all as inserted when prev is empty") { - c = _impl::CollectionChangeBuilder::calculate({}, {1, 2, 3}, all_modified, false); - REQUIRE_INDICES(c.insertions, 0, 1, 2); - } - - SECTION("marks all as deleted when new is empty") { - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3}, {}, all_modified, false); - REQUIRE_INDICES(c.deletions, 0, 1, 2); - } - - SECTION("marks npos rows in prev as deleted") { - c = _impl::CollectionChangeBuilder::calculate({npos, 1, 2, 3, npos}, {1, 2, 3}, all_modified, false); - REQUIRE_INDICES(c.deletions, 0, 4); - } - - SECTION("marks modified rows which do not move as modified") { - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3}, {1, 2, 3}, all_modified, false); - REQUIRE_INDICES(c.modifications, 0, 1, 2); - } - - SECTION("does not mark unmodified rows as modified") { - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3}, {1, 2, 3}, none_modified, false); - REQUIRE(c.modifications.empty()); - } - - SECTION("marks newly added rows as insertions") { - c = _impl::CollectionChangeBuilder::calculate({2, 3}, {1, 2, 3}, all_modified, false); - REQUIRE_INDICES(c.insertions, 0); - - c = _impl::CollectionChangeBuilder::calculate({1, 3}, {1, 2, 3}, all_modified, false); - REQUIRE_INDICES(c.insertions, 1); - - c = _impl::CollectionChangeBuilder::calculate({1, 2}, {1, 2, 3}, all_modified, false); - REQUIRE_INDICES(c.insertions, 2); - } - - SECTION("marks removed rows as deleted") { - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3}, {1, 2}, all_modified, false); - REQUIRE_INDICES(c.deletions, 2); - - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3}, {1, 3}, all_modified, false); - REQUIRE_INDICES(c.deletions, 1); - - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3}, {2, 3}, all_modified, false); - REQUIRE_INDICES(c.deletions, 0); - } - - SECTION("marks rows as both inserted and deleted") { - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3}, {1, 3, 4}, all_modified, false); - REQUIRE_INDICES(c.deletions, 1); - REQUIRE_INDICES(c.insertions, 2); - REQUIRE(c.moves.empty()); - } - - SECTION("marks rows as modified even if they moved") { - c = _impl::CollectionChangeBuilder::calculate({3, 5}, {5, 3}, all_modified, false); - REQUIRE_INDICES(c.deletions, 1); - REQUIRE_INDICES(c.insertions, 0); - REQUIRE_INDICES(c.modifications, 0, 1); - } - - SECTION("does not mark rows as modified if they are new") { - c = _impl::CollectionChangeBuilder::calculate({3}, {3, 5}, all_modified, false); - REQUIRE_INDICES(c.modifications, 0); - } - - SECTION("reports inserts/deletes for simple reorderings") { - auto calc = [&](std::vector old_rows, std::vector new_rows) { - return _impl::CollectionChangeBuilder::calculate(old_rows, new_rows, none_modified, false); - }; - - c = calc({1, 2, 3}, {1, 2, 3}); - REQUIRE(c.insertions.empty()); - REQUIRE(c.deletions.empty()); - - c = calc({1, 2, 3}, {1, 3, 2}); - REQUIRE_INDICES(c.insertions, 1); - REQUIRE_INDICES(c.deletions, 2); - - c = calc({1, 2, 3}, {2, 1, 3}); - REQUIRE_INDICES(c.insertions, 0); - REQUIRE_INDICES(c.deletions, 1); - - c = calc({1, 2, 3}, {2, 3, 1}); - REQUIRE_INDICES(c.insertions, 2); - REQUIRE_INDICES(c.deletions, 0); - - c = calc({1, 2, 3}, {3, 1, 2}); - REQUIRE_INDICES(c.insertions, 0); - REQUIRE_INDICES(c.deletions, 2); - - c = calc({1, 2, 3}, {3, 2, 1}); - REQUIRE_INDICES(c.insertions, 0, 1); - REQUIRE_INDICES(c.deletions, 1, 2); - - c = calc({1, 3, 2}, {1, 2, 3}); - REQUIRE_INDICES(c.insertions, 1); - REQUIRE_INDICES(c.deletions, 2); - - c = calc({1, 3, 2}, {1, 3, 2}); - REQUIRE(c.insertions.empty()); - REQUIRE(c.deletions.empty()); - - c = calc({1, 3, 2}, {2, 1, 3}); - REQUIRE_INDICES(c.insertions, 0); - REQUIRE_INDICES(c.deletions, 2); - - c = calc({1, 3, 2}, {2, 3, 1}); - REQUIRE_INDICES(c.insertions, 0, 1); - REQUIRE_INDICES(c.deletions, 1, 2); - - c = calc({1, 3, 2}, {3, 1, 2}); - REQUIRE_INDICES(c.insertions, 0); - REQUIRE_INDICES(c.deletions, 1); - - c = calc({1, 3, 2}, {3, 2, 1}); - REQUIRE_INDICES(c.insertions, 2); - REQUIRE_INDICES(c.deletions, 0); - - c = calc({2, 1, 3}, {1, 2, 3}); - REQUIRE_INDICES(c.insertions, 0); - REQUIRE_INDICES(c.deletions, 1); - - c = calc({2, 1, 3}, {1, 3, 2}); - REQUIRE_INDICES(c.insertions, 2); - REQUIRE_INDICES(c.deletions, 0); - - c = calc({2, 1, 3}, {2, 1, 3}); - REQUIRE(c.insertions.empty()); - REQUIRE(c.deletions.empty()); - - c = calc({2, 1, 3}, {2, 3, 1}); - REQUIRE_INDICES(c.insertions, 1); - REQUIRE_INDICES(c.deletions, 2); - - c = calc({2, 1, 3}, {3, 1, 2}); - REQUIRE_INDICES(c.insertions, 0, 1); - REQUIRE_INDICES(c.deletions, 1, 2); - - c = calc({2, 1, 3}, {3, 2, 1}); - REQUIRE_INDICES(c.insertions, 0); - REQUIRE_INDICES(c.deletions, 2); - - c = calc({2, 3, 1}, {1, 2, 3}); - REQUIRE_INDICES(c.insertions, 0); - REQUIRE_INDICES(c.deletions, 2); - - c = calc({2, 3, 1}, {1, 3, 2}); - REQUIRE_INDICES(c.insertions, 0, 1); - REQUIRE_INDICES(c.deletions, 1, 2); - - c = calc({2, 3, 1}, {2, 1, 3}); - REQUIRE_INDICES(c.insertions, 1); - REQUIRE_INDICES(c.deletions, 2); - - c = calc({2, 3, 1}, {2, 3, 1}); - REQUIRE(c.insertions.empty()); - REQUIRE(c.deletions.empty()); - - c = calc({2, 3, 1}, {3, 1, 2}); - REQUIRE_INDICES(c.insertions, 2); - REQUIRE_INDICES(c.deletions, 0); - - c = calc({2, 3, 1}, {3, 2, 1}); - REQUIRE_INDICES(c.insertions, 0); - REQUIRE_INDICES(c.deletions, 1); - - c = calc({3, 1, 2}, {1, 2, 3}); - REQUIRE_INDICES(c.insertions, 2); - REQUIRE_INDICES(c.deletions, 0); - - c = calc({3, 1, 2}, {1, 3, 2}); - REQUIRE_INDICES(c.insertions, 0); - REQUIRE_INDICES(c.deletions, 1); - - c = calc({3, 1, 2}, {2, 1, 3}); - REQUIRE_INDICES(c.insertions, 0, 1); - REQUIRE_INDICES(c.deletions, 1, 2); - - c = calc({3, 1, 2}, {2, 3, 1}); - REQUIRE_INDICES(c.insertions, 0); - REQUIRE_INDICES(c.deletions, 2); - - c = calc({3, 1, 2}, {3, 1, 2}); - REQUIRE(c.insertions.empty()); - REQUIRE(c.deletions.empty()); - - c = calc({3, 1, 2}, {3, 2, 1}); - REQUIRE_INDICES(c.insertions, 1); - REQUIRE_INDICES(c.deletions, 2); - - c = calc({3, 2, 1}, {1, 2, 3}); - REQUIRE_INDICES(c.insertions, 0, 1); - REQUIRE_INDICES(c.deletions, 1, 2); - - c = calc({3, 2, 1}, {1, 3, 2}); - REQUIRE_INDICES(c.insertions, 0); - REQUIRE_INDICES(c.deletions, 2); - - c = calc({3, 2, 1}, {2, 1, 3}); - REQUIRE_INDICES(c.insertions, 2); - REQUIRE_INDICES(c.deletions, 0); - - c = calc({3, 2, 1}, {2, 3, 1}); - REQUIRE_INDICES(c.insertions, 0); - REQUIRE_INDICES(c.deletions, 1); - - c = calc({3, 2, 1}, {3, 1, 2}); - REQUIRE_INDICES(c.insertions, 1); - REQUIRE_INDICES(c.deletions, 2); - - c = calc({3, 2, 1}, {3, 2, 1}); - REQUIRE(c.insertions.empty()); - REQUIRE(c.deletions.empty()); - } - - SECTION("prefers to produce diffs where modified rows are the ones to move when it is ambiguous") { - auto two_modified = [](size_t ndx) { return ndx == 2; }; - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3}, {1, 3, 2}, two_modified, false); - REQUIRE_INDICES(c.deletions, 1); - REQUIRE_INDICES(c.insertions, 2); - - auto three_modified = [](size_t ndx) { return ndx == 3; }; - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3}, {1, 3, 2}, three_modified, false); - REQUIRE_INDICES(c.deletions, 2); - REQUIRE_INDICES(c.insertions, 1); - } - - SECTION("prefers smaller diffs over larger diffs moving only modified rows") { - auto two_modified = [](size_t ndx) { return ndx == 2; }; - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3}, {2, 3, 1}, two_modified, false); - REQUIRE_INDICES(c.deletions, 0); - REQUIRE_INDICES(c.insertions, 2); - } - - SECTION("supports duplicate indices") { - c = _impl::CollectionChangeBuilder::calculate({1, 1, 2, 2, 3, 3}, - {1, 2, 3, 1, 2, 3}, - all_modified, false); - REQUIRE_INDICES(c.deletions, 3, 5); - REQUIRE_INDICES(c.insertions, 1, 2); - } - - SECTION("deletes and inserts the last option when any in a range could be deleted") { - c = _impl::CollectionChangeBuilder::calculate({3, 2, 1, 1, 2, 3}, - {1, 1, 2, 2, 3, 3}, - all_modified, false); - REQUIRE_INDICES(c.deletions, 0, 1); - REQUIRE_INDICES(c.insertions, 3, 5); - } - - SECTION("reports insertions/deletions when the number of duplicate entries changes") { - c = _impl::CollectionChangeBuilder::calculate({1, 1, 1, 1, 2, 3}, - {1, 2, 3, 1}, - all_modified, false); - REQUIRE_INDICES(c.deletions, 1, 2, 3); - REQUIRE_INDICES(c.insertions, 3); - - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3, 1}, - {1, 1, 1, 1, 2, 3}, - all_modified, false); - REQUIRE_INDICES(c.deletions, 3); - REQUIRE_INDICES(c.insertions, 1, 2, 3); - } - - SECTION("properly recurses into smaller subblocks") { - std::vector prev = {10, 1, 2, 11, 3, 4, 5, 12, 6, 7, 13}; - std::vector next = {13, 1, 2, 12, 3, 4, 5, 11, 6, 7, 10}; - c = _impl::CollectionChangeBuilder::calculate(prev, next, all_modified, false); - REQUIRE_INDICES(c.deletions, 0, 3, 7, 10); - REQUIRE_INDICES(c.insertions, 0, 3, 7, 10); - } - - SECTION("produces diffs which let merge collapse insert -> move -> delete to no-op") { - auto four_modified = [](size_t ndx) { return ndx == 4; }; - for (int insert_pos = 0; insert_pos < 4; ++insert_pos) { - for (int move_to_pos = 0; move_to_pos < 4; ++move_to_pos) { - if (insert_pos == move_to_pos) - continue; - CAPTURE(insert_pos); - CAPTURE(move_to_pos); - - std::vector after_insert = {1, 2, 3}; - after_insert.insert(after_insert.begin() + insert_pos, 4); - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3}, after_insert, four_modified, false); - - std::vector after_move = {1, 2, 3}; - after_move.insert(after_move.begin() + move_to_pos, 4); - c.merge(_impl::CollectionChangeBuilder::calculate(after_insert, after_move, four_modified, false)); - - c.merge(_impl::CollectionChangeBuilder::calculate(after_move, {1, 2, 3}, four_modified, false)); - REQUIRE(c.empty()); - } - } - } -} - -TEST_CASE("[collection_change] merge()") { - _impl::CollectionChangeBuilder c; - - SECTION("is a no-op if the new set is empty") { - c = {{1, 2, 3}, {4, 5}, {6, 7}, {{8, 9}}}; - c.merge({}); - REQUIRE_INDICES(c.deletions, 1, 2, 3, 8); - REQUIRE_INDICES(c.insertions, 4, 5, 9); - REQUIRE_INDICES(c.modifications, 6, 7); - REQUIRE_MOVES(c, {8, 9}); - } - - SECTION("replaces the set with the new set if the old set is empty") { - c.merge({{1, 2, 3}, {4, 5}, {6, 7}, {{8, 9}}}); - REQUIRE_INDICES(c.deletions, 1, 2, 3, 8); - REQUIRE_INDICES(c.insertions, 4, 5, 9); - REQUIRE_INDICES(c.modifications, 6, 7); - REQUIRE_MOVES(c, {8, 9}); - } - - SECTION("shifts deletions by previous deletions") { - c = {{5}, {}, {}, {}}; - c.merge({{3}, {}, {}, {}}); - REQUIRE_INDICES(c.deletions, 3, 5); - - c = {{5}, {}, {}, {}}; - c.merge({{4}, {}, {}, {}}); - REQUIRE_INDICES(c.deletions, 4, 5); - - c = {{5}, {}, {}, {}}; - c.merge({{5}, {}, {}, {}}); - REQUIRE_INDICES(c.deletions, 5, 6); - - c = {{5}, {}, {}, {}}; - c.merge({{6}, {}, {}, {}}); - REQUIRE_INDICES(c.deletions, 5, 7); - } - - SECTION("shifts deletions by previous insertions") { - c = {{}, {5}, {}, {}}; - c.merge({{4}, {}, {}, {}}); - REQUIRE_INDICES(c.deletions, 4); - - c = {{}, {5}, {}, {}}; - c.merge({{6}, {}, {}, {}}); - REQUIRE_INDICES(c.deletions, 5); - } - - SECTION("shifts previous insertions by deletions") { - c = {{}, {2, 3}, {}, {}}; - c.merge({{1}, {}, {}, {}}); - REQUIRE_INDICES(c.insertions, 1, 2); - } - - SECTION("removes previous insertions for newly deleted rows") { - c = {{}, {1, 2}, {}, {}}; - c.merge({{2}, {}, {}, {}}); - REQUIRE_INDICES(c.insertions, 1); - } - - SECTION("removes previous modifications for newly deleted rows") { - c = {{}, {}, {2, 3}, {}}; - c.merge({{2}, {}, {}, {}}); - REQUIRE_INDICES(c.modifications, 2); - } - - SECTION("shifts previous modifications for deletions of other rows") { - c = {{}, {}, {2, 3}, {}}; - c.merge({{1}, {}, {}, {}}); - REQUIRE_INDICES(c.modifications, 1, 2); - } - - SECTION("removes moves to rows which have been deleted") { - c = {{}, {}, {}, {{2, 3}}}; - c.merge({{3}, {}, {}, {}}); - REQUIRE(c.moves.empty()); - } - - SECTION("shifts destinations of previous moves to reflect new deletions") { - c = {{}, {}, {}, {{2, 5}}}; - c.merge({{3}, {}, {}, {}}); - REQUIRE_MOVES(c, {2, 4}); - } - - SECTION("does not modify old deletions based on new insertions") { - c = {{1, 3}, {}, {}, {}}; - c.merge({{}, {1, 2, 3}, {}, {}}); - REQUIRE_INDICES(c.deletions, 1, 3); - REQUIRE_INDICES(c.insertions, 1, 2, 3); - } - - SECTION("shifts previous insertions to reflect new insertions") { - c = {{}, {1, 5}, {}, {}}; - c.merge({{}, {1, 4}, {}, {}}); - REQUIRE_INDICES(c.insertions, 1, 2, 4, 7); - } - - SECTION("shifts previous modifications to reflect new insertions") { - c = {{}, {}, {1, 5}, {}}; - c.merge({{}, {1, 4}, {}, {}}); - REQUIRE_INDICES(c.modifications, 2, 7); - REQUIRE_INDICES(c.insertions, 1, 4); - } - - SECTION("shifts previous move destinations to reflect new insertions") { - c = {{}, {}, {}, {{2, 5}}}; - c.merge({{}, {3}, {}}); - REQUIRE_MOVES(c, {2, 6}); - } - - SECTION("does not modify old deletions based on new modifications") { - c = {{1, 2, 3}, {}, {}, {}}; - c.merge({{}, {}, {2}}); - REQUIRE_INDICES(c.deletions, 1, 2, 3); - REQUIRE_INDICES(c.modifications, 2); - } - - SECTION("tracks modifications made to previously inserted rows") { - c = {{}, {2}, {}, {}}; - c.merge({{}, {}, {1, 2, 3}}); - REQUIRE_INDICES(c.insertions, 2); - REQUIRE_INDICES(c.modifications, 1, 2, 3); - } - - SECTION("unions modifications with old modifications") { - c = {{}, {}, {2}, {}}; - c.merge({{}, {}, {1, 2, 3}}); - REQUIRE_INDICES(c.modifications, 1, 2, 3); - } - - SECTION("tracks modifications for previous moves") { - c = {{}, {}, {}, {{1, 2}}}; - c.merge({{}, {}, {2, 3}}); - REQUIRE_INDICES(c.modifications, 2, 3); - } - - SECTION("updates new move sources to reflect previous inserts and deletes") { - c = {{1}, {}, {}, {}}; - c.merge({{}, {}, {}, {{2, 3}}}); - REQUIRE_MOVES(c, {3, 3}); - - c = {{}, {1}, {}, {}}; - c.merge({{}, {}, {}, {{2, 3}}}); - REQUIRE_MOVES(c, {1, 3}); - - c = {{2}, {4}, {}, {}}; - c.merge({{}, {}, {}, {{5, 10}}}); - REQUIRE_MOVES(c, {5, 10}); - } - - SECTION("updates the row modified for rows moved after a modification") { - c = {{}, {}, {1}, {}}; - c.merge({{}, {}, {}, {{1, 3}}}); - REQUIRE_INDICES(c.modifications, 3); - REQUIRE_MOVES(c, {1, 3}); - } - - SECTION("updates the row modified for chained moves") { - c = {{}, {}, {1}, {}}; - c.merge({{}, {}, {}, {{1, 3}}}); - c.merge({{}, {}, {}, {{3, 5}}}); - REQUIRE_INDICES(c.modifications, 5); - REQUIRE_MOVES(c, {1, 5}); - } - - SECTION("updates the row inserted for moves of previously new rows") { - c = {{}, {1}, {}, {}}; - c.merge({{}, {}, {}, {{1, 3}}}); - REQUIRE(c.moves.empty()); - REQUIRE_INDICES(c.insertions, 3); - } - - SECTION("updates old moves when the destination is moved again") { - c = {{}, {}, {}, {{1, 3}}}; - c.merge({{}, {}, {}, {{3, 5}}}); - REQUIRE_MOVES(c, {1, 5}); - } - - SECTION("shifts destination of previous moves to reflect new moves like an insert/delete pair would") { - c = {{}, {}, {}, {{1, 3}}}; - c.merge({{}, {}, {}, {{2, 5}}}); - REQUIRE_MOVES(c, {1, 2}, {3, 5}); - - c = {{}, {}, {}, {{1, 10}}}; - c.merge({{}, {}, {}, {{2, 5}}}); - REQUIRE_MOVES(c, {1, 10}, {3, 5}); - - c = {{}, {}, {}, {{5, 10}}}; - c.merge({{}, {}, {}, {{12, 2}}}); - REQUIRE_MOVES(c, {5, 11}, {12, 2}); - } - - SECTION("moves shift previous inserts like an insert/delete pair would") { - c = {{}, {5}}; - c.merge({{}, {}, {}, {{2, 6}}}); - REQUIRE_INDICES(c.insertions, 4, 6); - } - - SECTION("moves shift previous modifications like an insert/delete pair would") { - c = {{}, {}, {5}}; - c.merge({{}, {}, {}, {{2, 6}}}); - REQUIRE_INDICES(c.modifications, 4); - } - - SECTION("moves are shifted by previous deletions like an insert/delete pair would") { - c = {{5}}; - c.merge({{}, {}, {}, {{2, 6}}}); - REQUIRE_MOVES(c, {2, 6}); - - c = {{5}}; - c.merge({{}, {}, {}, {{6, 2}}}); - REQUIRE_MOVES(c, {7, 2}); - } - - SECTION("leapfrogging rows collapse to an empty changeset") { - c = {{1}, {0}, {}, {{1, 0}}}; - c.merge({{1}, {0}, {}, {{1, 0}}}); - REQUIRE(c.empty()); - } - - SECTION("modify -> move -> unmove leaves row marked as modified") { - c = {{}, {}, {1}}; - c.merge({{1}, {2}, {}, {{1, 2}}}); - c.merge({{1}}); - - REQUIRE_INDICES(c.deletions, 2); - REQUIRE(c.insertions.empty()); - REQUIRE(c.moves.empty()); - REQUIRE_INDICES(c.modifications, 1); - } - - SECTION("modifying a previously moved row which stops being a move due to more deletions") { - // Make it stop being a move in the same transaction as the modify - c = {{1, 2}, {0, 1}, {}, {{1, 0}, {2, 1}}}; - c.merge({{0, 2}, {1}, {0}, {}}); - - REQUIRE_INDICES(c.deletions, 0, 1); - REQUIRE_INDICES(c.insertions, 1); - REQUIRE_INDICES(c.modifications, 0); - REQUIRE(c.moves.empty()); - - // Same net change, but make it no longer a move in the transaction after the modify - c = {{1, 2}, {0, 1}, {}, {{1, 0}, {2, 1}}}; - c.merge({{}, {}, {1}, {}}); - c.merge({{0, 2}, {0}, {}, {{2, 0}}}); - c.merge({{0}, {1}, {}, {}}); - - REQUIRE_INDICES(c.deletions, 0, 1); - REQUIRE_INDICES(c.insertions, 1); - REQUIRE_INDICES(c.modifications, 0); - REQUIRE(c.moves.empty()); - } -} diff --git a/src/object-store/tests/index_set.cpp b/src/object-store/tests/index_set.cpp deleted file mode 100644 index ff62a05d..00000000 --- a/src/object-store/tests/index_set.cpp +++ /dev/null @@ -1,592 +0,0 @@ -#include "catch.hpp" - -#include "index_set.hpp" - -#include "util/index_helpers.hpp" - -TEST_CASE("[index_set] contains()") { - SECTION("returns false if the index is before the first entry in the set") { - realm::IndexSet set = {1, 2, 5}; - REQUIRE_FALSE(set.contains(0)); - } - - SECTION("returns false if the index is after the last entry in the set") { - realm::IndexSet set = {1, 2, 5}; - REQUIRE_FALSE(set.contains(6)); - } - - SECTION("returns false if the index is between ranges in the set") { - realm::IndexSet set = {1, 2, 5}; - REQUIRE_FALSE(set.contains(4)); - } - - SECTION("returns true if the index is in the set") { - realm::IndexSet set = {1, 2, 5}; - REQUIRE(set.contains(1)); - REQUIRE(set.contains(2)); - REQUIRE(set.contains(5)); - } -} - -TEST_CASE("[index_set] count()") { - SECTION("returns the number of indices in the set in the given range") { - realm::IndexSet set = {1, 2, 3, 5}; - REQUIRE(set.count(0, 6) == 4); - REQUIRE(set.count(0, 5) == 3); - REQUIRE(set.count(0, 4) == 3); - REQUIRE(set.count(0, 3) == 2); - REQUIRE(set.count(0, 2) == 1); - REQUIRE(set.count(0, 1) == 0); - REQUIRE(set.count(0, 0) == 0); - - REQUIRE(set.count(0, 6) == 4); - REQUIRE(set.count(1, 6) == 4); - REQUIRE(set.count(2, 6) == 3); - REQUIRE(set.count(3, 6) == 2); - REQUIRE(set.count(4, 6) == 1); - REQUIRE(set.count(5, 6) == 1); - REQUIRE(set.count(6, 6) == 0); - } - - SECTION("includes full ranges in the middle") { - realm::IndexSet set = {1, 3, 4, 5, 10}; - REQUIRE(set.count(0, 11) == 5); - } - - SECTION("truncates ranges at the beginning and end") { - realm::IndexSet set = {1, 2, 3, 5, 6, 7, 8, 9}; - REQUIRE(set.count(3, 9) == 5); - } - - SECTION("handles full chunks well") { - size_t count = realm::_impl::ChunkedRangeVector::max_size * 4; - realm::IndexSet set; - for (size_t i = 0; i < count; ++i) { - set.add(i * 3); - set.add(i * 3 + 1); - } - - for (size_t i = 0; i < count * 3; ++i) { - REQUIRE(set.count(i) == 2 * count - (i + 1) * 2 / 3); - REQUIRE(set.count(0, i) == (i + 1) / 3 + (i + 2) / 3); - } - } -} - -TEST_CASE("[index_set] add()") { - realm::IndexSet set; - - SECTION("extends existing ranges when next to an edge") { - set.add(1); - REQUIRE_INDICES(set, 1); - - set.add(2); - REQUIRE_INDICES(set, 1, 2); - - set.add(0); - REQUIRE_INDICES(set, 0, 1, 2); - } - - SECTION("does not extend ranges over gaps") { - set.add(0); - REQUIRE_INDICES(set, 0); - - set.add(2); - REQUIRE_INDICES(set, 0, 2); - } - - SECTION("does nothing when the index is already in the set") { - set.add(0); - set.add(0); - REQUIRE_INDICES(set, 0); - } - - SECTION("merges existing ranges when adding the index between them") { - set = {0, 2, 4}; - - set.add(1); - REQUIRE_INDICES(set, 0, 1, 2, 4); - } - - SECTION("combines multiple index sets without any shifting") { - set = {0, 2, 6}; - - set.add({1, 4, 5}); - REQUIRE_INDICES(set, 0, 1, 2, 4, 5, 6); - } - - SECTION("handles front additions of ranges") { - for (size_t i = 20; i > 0; i -= 2) - set.add(i); - REQUIRE_INDICES(set, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20); - } - - SECTION("merges ranges even when they are in different chunks") { - realm::IndexSet set2; - for (int i = 0; i < 20; ++i) { - set.add(i * 2); - set2.add(i); - set2.add(i * 2); - } - set.add(set2); - REQUIRE(set.count() == 30); - } -} - -TEST_CASE("[index_set] add_shifted()") { - realm::IndexSet set; - - SECTION("on an empty set is just add()") { - set.add_shifted(5); - REQUIRE_INDICES(set, 5); - } - - SECTION("before the first range is just add()") { - set = {10}; - set.add_shifted(5); - REQUIRE_INDICES(set, 5, 10); - } - - SECTION("on first index of a range extends the range") { - set = {5}; - - set.add_shifted(5); - REQUIRE_INDICES(set, 5, 6); - - set.add_shifted(5); - REQUIRE_INDICES(set, 5, 6, 7); - } - - SECTION("in the middle of a range is shifted by that range") { - set = {5, 6, 7}; - set.add_shifted(6); - REQUIRE_INDICES(set, 5, 6, 7, 9); - } - - SECTION("after the last range adds the total count to the index to be added") { - set = {5}; - - set.add_shifted(6); - REQUIRE_INDICES(set, 5, 7); - - set.add_shifted(10); - REQUIRE_INDICES(set, 5, 7, 12); - } - - SECTION("in between ranges can be bumped into the next range") { - set = {5, 7}; - set.add_shifted(6); - REQUIRE_INDICES(set, 5, 7, 8); - } -} - -TEST_CASE("[index_set] add_shifted_by()") { - realm::IndexSet set; - - SECTION("does nothing given an empty set to add") { - set = {5, 6, 7}; - set.add_shifted_by({5, 6}, {}); - REQUIRE_INDICES(set, 5, 6, 7); - } - - SECTION("does nothing if values is a subset of shifted_by") { - set = {5, 6, 7}; - set.add_shifted_by({3, 4}, {3, 4}); - REQUIRE_INDICES(set, 5, 6, 7); - } - - SECTION("just adds the indices when they are all before the old indices and the shifted-by set is empty") { - set = {5, 6}; - set.add_shifted_by({}, {3, 4}); - REQUIRE_INDICES(set, 3, 4, 5, 6); - } - - SECTION("adds the indices shifted by the old count when they are all after the old indices and the shifted-by set is empty") { - set = {5, 6}; - set.add_shifted_by({}, {7, 9, 11, 13}); - REQUIRE_INDICES(set, 5, 6, 9, 11, 13, 15); - } - - SECTION("acts like bulk add_shifted() when shifted_by is empty") { - set = {5, 10, 15, 20, 25}; - set.add_shifted_by({}, {4, 5, 11}); - REQUIRE_INDICES(set, 4, 5, 6, 10, 13, 15, 20, 25); - } - - SECTION("shifts indices in values back by the number of indices in shifted_by before them") { - set = {5}; - set.add_shifted_by({0, 2, 3}, {6}); - REQUIRE_INDICES(set, 3, 5); - - set = {5}; - set.add_shifted_by({1, 3}, {4}); - REQUIRE_INDICES(set, 2, 5); - } - - SECTION("discards indices in both shifted_by and values") { - set = {5}; - set.add_shifted_by({2}, {2, 4}); - REQUIRE_INDICES(set, 3, 5); - } -} - -TEST_CASE("[index_set] set()") { - realm::IndexSet set; - - SECTION("clears the existing indices and replaces with the range [0, value)") { - set = {8, 9}; - set.set(5); - REQUIRE_INDICES(set, 0, 1, 2, 3, 4); - } -} - -TEST_CASE("[index_set] insert_at()") { - realm::IndexSet set; - - SECTION("on an empty set is add()") { - set.insert_at(5); - REQUIRE_INDICES(set, 5); - - set = {}; - set.insert_at({1, 3, 5}); - REQUIRE_INDICES(set, 1, 3, 5); - } - - SECTION("with an empty set is a no-op") { - set = {5, 6}; - set.insert_at(realm::IndexSet{}); - REQUIRE_INDICES(set, 5, 6); - } - - SECTION("extends ranges containing the target range") { - set = {5, 6}; - - set.insert_at(5); - REQUIRE_INDICES(set, 5, 6, 7); - - set.insert_at(6, 2); - REQUIRE_INDICES(set, 5, 6, 7, 8, 9); - - set.insert_at({5, 7, 11}); - REQUIRE_INDICES(set, 5, 6, 7, 8, 9, 10, 11, 12); - } - - SECTION("shifts ranges after the insertion point") { - set = {5, 6}; - - set.insert_at(3); - REQUIRE_INDICES(set, 3, 6, 7); - - set.insert_at(0, 2); - REQUIRE_INDICES(set, 0, 1, 5, 8, 9); - } - - SECTION("does not shift ranges before the insertion point") { - set = {5, 6}; - - set.insert_at(10); - REQUIRE_INDICES(set, 5, 6, 10); - - set.insert_at({15, 16}); - REQUIRE_INDICES(set, 5, 6, 10, 15, 16); - } - - SECTION("can not join ranges") { - set = {5, 7}; - set.insert_at(6); - REQUIRE_INDICES(set, 5, 6, 8); - } - - SECTION("adds later ranges after shifting for previous insertions") { - set = {5, 10}; - set.insert_at({5, 10}); - REQUIRE_INDICES(set, 5, 6, 10, 12); - } -} - -TEST_CASE("[index_set] shift_for_insert_at()") { - realm::IndexSet set; - - SECTION("does nothing given an empty set of insertion points") { - set = {5, 8}; - set.shift_for_insert_at(realm::IndexSet{}); - REQUIRE_INDICES(set, 5, 8); - } - - SECTION("does nothing when called on an empty set") { - set = {}; - set.shift_for_insert_at({5, 8}); - REQUIRE(set.empty()); - } - - SECTION("does nothing when the insertion points are all after the current indices") { - set = {10, 20}; - set.shift_for_insert_at({30, 40}); - REQUIRE_INDICES(set, 10, 20); - } - - SECTION("does shift when the insertion points are all before the current indices") { - set = {10, 20}; - set.shift_for_insert_at({2, 4}); - REQUIRE_INDICES(set, 12, 22); - } - - SECTION("shifts indices at or after the insertion points") { - set = {5}; - - set.shift_for_insert_at(4); - REQUIRE_INDICES(set, 6); - - set.shift_for_insert_at(6); - REQUIRE_INDICES(set, 7); - - set.shift_for_insert_at({3, 8}); - REQUIRE_INDICES(set, 9); - } - - SECTION("shifts indices by the count specified") { - set = {5}; - set.shift_for_insert_at(3, 10); - REQUIRE_INDICES(set, 15); - } - - SECTION("does not shift indices before the insertion points") { - set = {5}; - - set.shift_for_insert_at(6); - REQUIRE_INDICES(set, 5); - - set.shift_for_insert_at({3, 8}); - REQUIRE_INDICES(set, 6); - } - - SECTION("splits ranges containing the insertion points") { - set = {5, 6, 7, 8}; - - set.shift_for_insert_at(6); - REQUIRE_INDICES(set, 5, 7, 8, 9); - - set.shift_for_insert_at({8, 10, 12}); - REQUIRE_INDICES(set, 5, 7, 9, 11); - } -} - -TEST_CASE("[index_set] erase_at()") { - realm::IndexSet set; - - SECTION("is a no-op on an empty set") { - set.erase_at(10); - REQUIRE(set.empty()); - - set.erase_at({1, 5, 8}); - REQUIRE(set.empty()); - } - - SECTION("does nothing when given an empty set") { - set = {5}; - set.erase_at(realm::IndexSet{}); - REQUIRE_INDICES(set, 5); - } - - SECTION("removes the specified indices") { - set = {5}; - set.erase_at(5); - REQUIRE(set.empty()); - - set = {4, 7}; - set.erase_at({4, 7}); - REQUIRE(set.empty()); - } - - SECTION("does not modify indices before the removed one") { - set = {5, 8}; - set.erase_at(8); - REQUIRE_INDICES(set, 5); - - set = {5, 8, 9}; - set.erase_at({8, 9}); - REQUIRE_INDICES(set, 5); - } - - SECTION("shifts indices after the removed one") { - set = {5, 8}; - set.erase_at(5); - REQUIRE_INDICES(set, 7); - - set = {5, 10, 15, 20}; - set.erase_at({5, 10}); - REQUIRE_INDICES(set, 13, 18); - } - - SECTION("shrinks ranges when used on one of the edges of them") { - set = {5, 6, 7, 8}; - set.erase_at(8); - REQUIRE_INDICES(set, 5, 6, 7); - set.erase_at(5); - REQUIRE_INDICES(set, 5, 6); - - set = {5, 6, 7, 8}; - set.erase_at({5, 8}); - REQUIRE_INDICES(set, 5, 6); - } - - SECTION("shrinks ranges when used in the middle of them") { - set = {5, 6, 7, 8}; - set.erase_at(7); - REQUIRE_INDICES(set, 5, 6, 7); - - set = {5, 6, 7, 8}; - set.erase_at({6, 7}); - REQUIRE_INDICES(set, 5, 6); - } - - SECTION("merges ranges when the gap between them is deleted") { - set = {3, 5}; - set.erase_at(4); - REQUIRE_INDICES(set, 3, 4); - - set = {3, 5, 7}; - set.erase_at({4, 6}); - REQUIRE_INDICES(set, 3, 4, 5); - } -} - -TEST_CASE("[index_set] erase_or_unshift()") { - realm::IndexSet set; - - SECTION("removes the given index") { - set = {1, 2}; - set.erase_or_unshift(2); - REQUIRE_INDICES(set, 1); - } - - SECTION("shifts indexes after the given index") { - set = {1, 5}; - set.erase_or_unshift(2); - REQUIRE_INDICES(set, 1, 4); - } - - SECTION("returns npos for indices in the set") { - set = {1, 3, 5}; - REQUIRE(realm::IndexSet(set).erase_or_unshift(1) == realm::IndexSet::npos); - REQUIRE(realm::IndexSet(set).erase_or_unshift(3) == realm::IndexSet::npos); - REQUIRE(realm::IndexSet(set).erase_or_unshift(5) == realm::IndexSet::npos); - } - - SECTION("returns the number of indices in the set before the index for ones not in the set") { - set = {1, 3, 5, 6}; - REQUIRE(realm::IndexSet(set).erase_or_unshift(0) == 0); - REQUIRE(realm::IndexSet(set).erase_or_unshift(2) == 1); - REQUIRE(realm::IndexSet(set).erase_or_unshift(4) == 2); - REQUIRE(realm::IndexSet(set).erase_or_unshift(7) == 3); - } - -} - -TEST_CASE("[index_set] remove()") { - realm::IndexSet set; - - SECTION("is a no-op if the set is empty") { - set.remove(4); - REQUIRE(set.empty()); - - set.remove({1, 2, 3}); - REQUIRE(set.empty()); - } - - SECTION("is a no-op if the set to remove is empty") { - set = {5}; - set.remove(realm::IndexSet{}); - REQUIRE_INDICES(set, 5); - } - - SECTION("is a no-op if the index to remove is not in the set") { - set = {5}; - set.remove(4); - set.remove(6); - set.remove({4, 6}); - REQUIRE_INDICES(set, 5); - } - - SECTION("removes one-element ranges") { - set = {5}; - set.remove(5); - REQUIRE(set.empty()); - - set = {5}; - set.remove({3, 4, 5}); - REQUIRE(set.empty()); - } - - SECTION("shrinks ranges beginning with the index") { - set = {5, 6, 7}; - set.remove(5); - REQUIRE_INDICES(set, 6, 7); - - set = {5, 6, 7}; - set.remove({3, 5}); - REQUIRE_INDICES(set, 6, 7); - } - - SECTION("shrinks ranges ending with the index") { - set = {5, 6, 7}; - set.remove(7); - REQUIRE_INDICES(set, 5, 6); - - set = {5, 6, 7}; - set.remove({3, 7}); - REQUIRE_INDICES(set, 5, 6); - } - - SECTION("splits ranges containing the index") { - set = {5, 6, 7}; - set.remove(6); - REQUIRE_INDICES(set, 5, 7); - - set = {5, 6, 7}; - set.remove({3, 6}); - REQUIRE_INDICES(set, 5, 7); - } - - SECTION("does not shift other indices and uses unshifted positions") { - set = {5, 6, 7, 10, 11, 12, 13, 15}; - set.remove({6, 11, 13}); - REQUIRE_INDICES(set, 5, 7, 10, 12, 15); - } -} - -TEST_CASE("[index_set] shift()") { - realm::IndexSet set; - - SECTION("is ind + count(0, ind), but adds the count-so-far to the stop index") { - set = {1, 3, 5, 6}; - REQUIRE(set.shift(0) == 0); - REQUIRE(set.shift(1) == 2); - REQUIRE(set.shift(2) == 4); - REQUIRE(set.shift(3) == 7); - REQUIRE(set.shift(4) == 8); - } -} - -TEST_CASE("[index_set] unshift()") { - realm::IndexSet set; - - SECTION("is index - count(0, index)") { - set = {1, 3, 5, 6}; - REQUIRE(set.unshift(0) == 0); - REQUIRE(set.unshift(2) == 1); - REQUIRE(set.unshift(4) == 2); - REQUIRE(set.unshift(7) == 3); - REQUIRE(set.unshift(8) == 4); - } -} - -TEST_CASE("[index_set] clear()") { - realm::IndexSet set; - - SECTION("removes all indices from the set") { - set = {1, 2, 3}; - set.clear(); - REQUIRE(set.empty()); - } -} diff --git a/src/object-store/tests/list.cpp b/src/object-store/tests/list.cpp deleted file mode 100644 index b98a57f4..00000000 --- a/src/object-store/tests/list.cpp +++ /dev/null @@ -1,487 +0,0 @@ -#include "catch.hpp" - -#include "util/test_file.hpp" -#include "util/index_helpers.hpp" - -#include "binding_context.hpp" -#include "list.hpp" -#include "object_schema.hpp" -#include "property.hpp" -#include "results.hpp" -#include "schema.hpp" - -#include "impl/realm_coordinator.hpp" - -#include -#include -#include - -using namespace realm; - -TEST_CASE("list") { - InMemoryTestFile config; - config.automatic_change_notifications = false; - config.cache = false; - config.schema = std::make_unique(Schema{ - {"origin", "", { - {"array", PropertyType::Array, "target"} - }}, - {"target", "", { - {"value", PropertyType::Int} - }}, - {"other_origin", "", { - {"array", PropertyType::Array, "other_target"} - }}, - {"other_target", "", { - {"value", PropertyType::Int} - }}, - }); - - auto r = Realm::get_shared_realm(config); - auto& coordinator = *_impl::RealmCoordinator::get_existing_coordinator(config.path); - - auto origin = r->read_group()->get_table("class_origin"); - auto target = r->read_group()->get_table("class_target"); - - r->begin_transaction(); - - target->add_empty_row(10); - for (int i = 0; i < 10; ++i) - target->set_int(0, i, i); - - origin->add_empty_row(2); - LinkViewRef lv = origin->get_linklist(0, 0); - for (int i = 0; i < 10; ++i) - lv->add(i); - LinkViewRef lv2 = origin->get_linklist(0, 1); - for (int i = 0; i < 10; ++i) - lv2->add(i); - - r->commit_transaction(); - - SECTION("add_notification_block()") { - CollectionChangeSet change; - List lst(r, lv); - - auto write = [&](auto&& f) { - r->begin_transaction(); - f(); - r->commit_transaction(); - - advance_and_notify(*r); - }; - - auto require_change = [&] { - auto token = lst.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) { - change = c; - }); - advance_and_notify(*r); - return token; - }; - - auto require_no_change = [&] { - bool first = true; - auto token = lst.add_notification_callback([&, first](CollectionChangeSet, std::exception_ptr) mutable { - REQUIRE(first); - first = false; - }); - advance_and_notify(*r); - return token; - }; - - SECTION("modifying the list sends a change notifications") { - auto token = require_change(); - write([&] { lst.remove(5); }); - REQUIRE_INDICES(change.deletions, 5); - } - - SECTION("modifying a different list doesn't send a change notification") { - auto token = require_no_change(); - write([&] { lv2->remove(5); }); - } - - SECTION("deleting the list sends a change notification") { - auto token = require_change(); - write([&] { origin->move_last_over(0); }); - REQUIRE_INDICES(change.deletions, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9); - - // Should not resend delete all notification after another commit - change = {}; - write([&] { target->add_empty_row(); }); - REQUIRE(change.empty()); - } - - SECTION("modifying one of the target rows sends a change notification") { - auto token = require_change(); - write([&] { lst.get(5).set_int(0, 6); }); - REQUIRE_INDICES(change.modifications, 5); - } - - SECTION("deleting a target row sends a change notification") { - auto token = require_change(); - write([&] { target->move_last_over(5); }); - REQUIRE_INDICES(change.deletions, 5); - } - - SECTION("adding a row and then modifying the target row does not mark the row as modified") { - auto token = require_change(); - write([&] { - lst.add(5); - target->set_int(0, 5, 10); - }); - REQUIRE_INDICES(change.insertions, 10); - REQUIRE_INDICES(change.modifications, 5); - } - - SECTION("modifying and then moving a row reports move/insert but not modification") { - auto token = require_change(); - write([&] { - target->set_int(0, 5, 10); - lst.move(5, 8); - }); - REQUIRE_INDICES(change.insertions, 8); - REQUIRE_INDICES(change.deletions, 5); - REQUIRE_MOVES(change, {5, 8}); - REQUIRE(change.modifications.empty()); - } - - SECTION("modifying a row which appears multiple times in a list marks them all as modified") { - r->begin_transaction(); - lst.add(5); - r->commit_transaction(); - - auto token = require_change(); - write([&] { target->set_int(0, 5, 10); }); - REQUIRE_INDICES(change.modifications, 5, 10); - } - - SECTION("deleting a row which appears multiple times in a list marks them all as modified") { - r->begin_transaction(); - lst.add(5); - r->commit_transaction(); - - auto token = require_change(); - write([&] { target->move_last_over(5); }); - REQUIRE_INDICES(change.deletions, 5, 10); - } - - SECTION("clearing the target table sends a change notification") { - auto token = require_change(); - write([&] { target->clear(); }); - REQUIRE_INDICES(change.deletions, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9); - } - - SECTION("moving a target row does not send a change notification") { - // Remove a row from the LV so that we have one to delete that's not in the list - r->begin_transaction(); - lv->remove(2); - r->commit_transaction(); - - auto token = require_no_change(); - write([&] { target->move_last_over(2); }); - } - - SECTION("multiple LinkViws for the same LinkList can get notifications") { - r->begin_transaction(); - target->clear(); - target->add_empty_row(5); - r->commit_transaction(); - - 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, lv); - }; - auto change_list = [&] { - r->begin_transaction(); - if (lv->size()) { - target->set_int(0, lv->size() - 1, lv->size()); - } - lv->add(lv->size()); - r->commit_transaction(); - }; - - List lists[3]; - NotificationToken tokens[3]; - CollectionChangeSet changes[3]; - - for (int i = 0; i < 3; ++i) { - lists[i] = get_list(); - tokens[i] = lists[i].add_notification_callback([i, &changes](CollectionChangeSet c, std::exception_ptr) { - changes[i] = std::move(c); - }); - change_list(); - } - - // Each of the Lists now has a different source version and state at - // that version, so they should all see different changes despite - // being for the same LinkList - for (auto& list : lists) - advance_and_notify(*list.get_realm()); - - REQUIRE_INDICES(changes[0].insertions, 0, 1, 2); - REQUIRE(changes[0].modifications.empty()); - - REQUIRE_INDICES(changes[1].insertions, 1, 2); - REQUIRE_INDICES(changes[1].modifications, 0); - - REQUIRE_INDICES(changes[2].insertions, 2); - REQUIRE_INDICES(changes[2].modifications, 1); - - // After making another change, they should all get the same notification - change_list(); - for (auto& list : lists) - advance_and_notify(*list.get_realm()); - - for (int i = 0; i < 3; ++i) { - REQUIRE_INDICES(changes[i].insertions, 3); - REQUIRE_INDICES(changes[i].modifications, 2); - } - } - - SECTION("tables-of-interest are tracked properly for multiple source versions") { - auto other_origin = r->read_group()->get_table("class_other_origin"); - auto other_target = r->read_group()->get_table("class_other_target"); - - r->begin_transaction(); - other_target->add_empty_row(); - other_origin->add_empty_row(); - LinkViewRef lv2 = other_origin->get_linklist(0, 0); - lv2->add(0); - r->commit_transaction(); - - 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 - // version have different tables watched for modifications - CollectionChangeSet changes1, changes2; - auto token1 = lst.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) { - changes1 = std::move(c); - }); - - r->begin_transaction(); r->commit_transaction(); - - auto token2 = lst2.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) { - changes2 = std::move(c); - }); - - r->begin_transaction(); - target->set_int(0, 0, 10); - r->commit_transaction(); - advance_and_notify(*r); - - REQUIRE_INDICES(changes1.modifications, 0); - REQUIRE(changes2.empty()); - } - - SECTION("modifications are reported for rows that are moved and then moved back in a second transaction") { - auto token = require_change(); - - r->begin_transaction(); - lv->get(5).set_int(0, 10); - lv->get(1).set_int(0, 10); - lv->move(5, 8); - lv->move(1, 2); - r->commit_transaction(); - - coordinator.on_change(); - - write([&]{ - lv->move(8, 5); - }); - - REQUIRE_INDICES(change.deletions, 1); - REQUIRE_INDICES(change.insertions, 2); - REQUIRE_INDICES(change.modifications, 5); - REQUIRE_MOVES(change, {1, 2}); - } - } - - SECTION("sorted add_notification_block()") { - List lst(r, lv); - Results results = lst.sort({{0}, {false}}); - - int notification_calls = 0; - CollectionChangeSet change; - auto token = results.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr err) { - REQUIRE_FALSE(err); - change = c; - ++notification_calls; - }); - - advance_and_notify(*r); - - auto write = [&](auto&& f) { - r->begin_transaction(); - f(); - r->commit_transaction(); - - advance_and_notify(*r); - }; - - SECTION("add duplicates") { - write([&] { - lst.add(5); - lst.add(5); - lst.add(5); - }); - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.insertions, 5, 6, 7); - } - - SECTION("change order by modifying target") { - write([&] { - lst.get(5).set_int(0, 15); - }); - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.deletions, 4); - REQUIRE_INDICES(change.insertions, 0); - } - - SECTION("swap") { - write([&] { - lst.swap(1, 2); - }); - REQUIRE(notification_calls == 1); - } - - SECTION("move") { - write([&] { - lst.move(5, 3); - }); - REQUIRE(notification_calls == 1); - } - } - - SECTION("filtered add_notification_block()") { - List lst(r, lv); - Results results = lst.filter(target->where().less(0, 9)); - - int notification_calls = 0; - CollectionChangeSet change; - auto token = results.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr err) { - REQUIRE_FALSE(err); - change = c; - ++notification_calls; - }); - - advance_and_notify(*r); - - auto write = [&](auto&& f) { - r->begin_transaction(); - f(); - r->commit_transaction(); - - advance_and_notify(*r); - }; - - SECTION("add duplicates") { - write([&] { - lst.add(5); - lst.add(5); - lst.add(5); - }); - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.insertions, 9, 10, 11); - } - - SECTION("swap") { - write([&] { - lst.swap(1, 2); - }); - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.deletions, 2); - REQUIRE_INDICES(change.insertions, 1); - - write([&] { - lst.swap(5, 8); - }); - REQUIRE(notification_calls == 3); - REQUIRE_INDICES(change.deletions, 5, 8); - REQUIRE_INDICES(change.insertions, 5, 8); - } - - SECTION("move") { - write([&] { - lst.move(5, 3); - }); - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.deletions, 5); - REQUIRE_INDICES(change.insertions, 3); - } - - SECTION("move non-matching entry") { - write([&] { - lst.move(9, 3); - }); - REQUIRE(notification_calls == 1); - } - } - - SECTION("sort()") { - auto objectschema = &*r->config().schema->find("target"); - List list(r, lv); - auto results = list.sort({{0}, {false}}); - - REQUIRE(&results.get_object_schema() == objectschema); - REQUIRE(results.get_mode() == Results::Mode::LinkView); - REQUIRE(results.size() == 10); - REQUIRE(results.sum(0) == 45); - - for (size_t i = 0; i < 10; ++i) { - REQUIRE(results.get(i).get_index() == 9 - i); - } - } - - SECTION("filter()") { - 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); - REQUIRE(results.get_mode() == Results::Mode::Query); - REQUIRE(results.size() == 4); - - for (size_t i = 0; i < 4; ++i) { - 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/main.cpp b/src/object-store/tests/main.cpp deleted file mode 100644 index 0c7c351f..00000000 --- a/src/object-store/tests/main.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#define CATCH_CONFIG_MAIN -#include "catch.hpp" diff --git a/src/object-store/tests/notifications-fuzzer/CMakeLists.txt b/src/object-store/tests/notifications-fuzzer/CMakeLists.txt deleted file mode 100644 index a23d7c16..00000000 --- a/src/object-store/tests/notifications-fuzzer/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -macro(build_fuzzer_variant variant) - add_executable(${variant} command_file.hpp command_file.cpp ${variant}.cpp) - target_link_libraries(${variant} realm-object-store) - set_target_properties(${variant} PROPERTIES - EXCLUDE_FROM_ALL 1 - EXCLUDE_FROM_DEFAULT_BUILD 1) -endmacro() - -build_fuzzer_variant(fuzzer) -build_fuzzer_variant(fuzz-sorted-query) -build_fuzzer_variant(fuzz-unsorted-query) -build_fuzzer_variant(fuzz-sorted-linkview) -build_fuzzer_variant(fuzz-unsorted-linkview) diff --git a/src/object-store/tests/notifications-fuzzer/command_file.cpp b/src/object-store/tests/notifications-fuzzer/command_file.cpp deleted file mode 100644 index 7fbde719..00000000 --- a/src/object-store/tests/notifications-fuzzer/command_file.cpp +++ /dev/null @@ -1,228 +0,0 @@ -#include "command_file.hpp" - -#include "impl/realm_coordinator.hpp" -#include "shared_realm.hpp" - -#include -#include - -#include - -using namespace fuzzer; -using namespace realm; - -#if 0 -#define log(...) fprintf(stderr, __VA_ARGS__) -#else -#define log(...) -#endif - -template -static T read_value(std::istream& input) -{ - T ret; - input >> ret; - return ret; -} - -template -static auto make_reader(void (*fn)(RealmState&, Args...)) { - return [=](std::istream& input) { - return std::bind(fn, std::placeholders::_1, read_value(input)...); - }; -} - -static void run_add(RealmState& state, int64_t value) -{ - log("add %lld\n", value); - size_t ndx = state.table.add_empty_row(); - state.table.set_int(0, ndx, state.uid++); - state.table.set_int(1, ndx, value); -} - -static void run_modify(RealmState& state, size_t index, int64_t value) -{ - if (index < state.table.size()) { - log("modify %zu %lld\n", index, value); - state.table.set_int(1, index, value); - state.modified.push_back(state.table.get_int(0, index)); - } -} - -static void run_delete(RealmState& state, size_t index) -{ - if (index < state.table.size()) { - log("delete %zu (%lld)\n", index, state.table.get_int(1, index)); - state.table.move_last_over(index); - } -} - -static void run_commit(RealmState& state) -{ - log("commit\n"); - state.realm.commit_transaction(); - state.coordinator.on_change(); - state.realm.begin_transaction(); -} - -static void run_lv_insert(RealmState& state, size_t pos, size_t target) -{ - if (!state.lv) return; - if (target < state.table.size() && pos <= state.lv->size()) { - log("lv insert %zu %zu\n", pos, target); - state.lv->insert(pos, target); - } -} - -static void run_lv_set(RealmState& state, size_t pos, size_t target) -{ - if (!state.lv) return; - if (target < state.table.size() && pos < state.lv->size()) { - log("lv set %zu %zu\n", pos, target); - // We can't reliably detect self-assignment for verification, so don't do it - if (state.lv->get(pos).get_index() != target) - state.lv->set(pos, target); - } -} - -static void run_lv_move(RealmState& state, size_t from, size_t to) -{ - if (!state.lv) return; - if (from < state.lv->size() && to < state.lv->size()) { - log("lv move %zu %zu\n", from, to); - // FIXME: only do the move if it has an effect to avoid getting a - // notification which we weren't expecting. This is really urgh. - for (size_t i = std::min(from, to); i < std::max(from, to); ++i) { - if (state.lv->get(i).get_index() != state.lv->get(i + 1).get_index()) { - state.lv->move(from, to); - break; - } - } - } -} - -static void run_lv_swap(RealmState& state, size_t ndx1, size_t ndx2) -{ - if (!state.lv) return; - if (ndx1 < state.lv->size() && ndx2 < state.lv->size()) { - log("lv swap %zu %zu\n", ndx1, ndx2); - if (state.lv->get(ndx1).get_index() != state.lv->get(ndx2).get_index()) { - state.lv->swap(ndx1, ndx2); - // FIXME: swap() needs to produce moves so that a pair of swaps can - // be collapsed away. Currently it just marks the rows as modified. - state.modified.push_back(state.lv->get(ndx1).get_int(0)); - state.modified.push_back(state.lv->get(ndx2).get_int(0)); - } - } -} - -static void run_lv_remove(RealmState& state, size_t pos) -{ - if (!state.lv) return; - if (pos < state.lv->size()) { - log("lv remove %zu\n", pos); - state.lv->remove(pos); - } -} - -static void run_lv_remove_target(RealmState& state, size_t pos) -{ - if (!state.lv) return; - if (pos < state.lv->size()) { - log("lv target remove %zu\n", pos); - state.lv->remove_target_row(pos); - } -} - -static std::map(std::istream&)>> readers = { - // Row functions - {'a', make_reader(run_add)}, - {'c', make_reader(run_commit)}, - {'d', make_reader(run_delete)}, - {'m', make_reader(run_modify)}, - - // LinkView functions - {'i', make_reader(run_lv_insert)}, - {'s', make_reader(run_lv_set)}, - {'o', make_reader(run_lv_move)}, - {'w', make_reader(run_lv_swap)}, - {'r', make_reader(run_lv_remove)}, - {'t', make_reader(run_lv_remove_target)}, -}; - -template -static std::vector read_int_list(std::istream& input_stream) -{ - std::vector ret; - std::string line; - while (std::getline(input_stream, line) && !line.empty()) { - try { - ret.push_back(std::stoll(line)); - log("%lld\n", (long long)ret.back()); - } - catch (std::invalid_argument) { - // not an error - } - catch (std::out_of_range) { - // not an error - } - } - log("\n"); - return ret; -} - -CommandFile::CommandFile(std::istream& input) -: initial_values(read_int_list(input)) -, initial_list_indices(read_int_list(input)) -{ - if (!input.good()) - return; - - while (input.good()) { - char op = '\0'; - input >> op; - if (!input.good()) - break; - - auto it = readers.find(op); - if (it == readers.end()) - continue; - - auto fn = it->second(input); - if (!input.good()) - return; - commands.push_back(std::move(fn)); - } -} - -void CommandFile::import(RealmState& state) -{ - auto& table = state.table; - - state.realm.begin_transaction(); - - table.clear(); - size_t ndx = table.add_empty_row(initial_values.size()); - for (auto value : initial_values) { - table.set_int(0, ndx, state.uid++); - table.set_int(1, ndx++, value); - } - - state.lv->clear(); - for (auto value : initial_list_indices) { - if (value < table.size()) - state.lv->add(value); - } - - state.realm.commit_transaction(); - -} - -void CommandFile::run(RealmState& state) -{ - state.realm.begin_transaction(); - for (auto& command : commands) { - command(state); - } - state.realm.commit_transaction(); -} diff --git a/src/object-store/tests/notifications-fuzzer/command_file.hpp b/src/object-store/tests/notifications-fuzzer/command_file.hpp deleted file mode 100644 index 7de23d43..00000000 --- a/src/object-store/tests/notifications-fuzzer/command_file.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#include - -#include -#include -#include -#include - -namespace realm { - class Table; - class LinkView; - class Realm; - namespace _impl { - class RealmCoordinator; - } -} - -namespace fuzzer { -struct RealmState { - realm::Realm& realm; - realm::_impl::RealmCoordinator& coordinator; - - realm::Table& table; - realm::LinkViewRef lv; - int64_t uid; - std::vector modified; -}; - -struct CommandFile { - std::vector initial_values; - std::vector initial_list_indices; - std::vector> commands; - - CommandFile(std::istream& input); - - void import(RealmState& state); - void run(RealmState& state); -}; -} \ No newline at end of file diff --git a/src/object-store/tests/notifications-fuzzer/fuzz-sorted-linkview.cpp b/src/object-store/tests/notifications-fuzzer/fuzz-sorted-linkview.cpp deleted file mode 100644 index 13d9bec0..00000000 --- a/src/object-store/tests/notifications-fuzzer/fuzz-sorted-linkview.cpp +++ /dev/null @@ -1,3 +0,0 @@ -#define FUZZ_SORTED 1 -#define FUZZ_LINKVIEW 1 -#include "fuzzer.cpp" diff --git a/src/object-store/tests/notifications-fuzzer/fuzz-sorted-query.cpp b/src/object-store/tests/notifications-fuzzer/fuzz-sorted-query.cpp deleted file mode 100644 index b32e9dc3..00000000 --- a/src/object-store/tests/notifications-fuzzer/fuzz-sorted-query.cpp +++ /dev/null @@ -1,3 +0,0 @@ -#define FUZZ_SORTED 1 -#define FUZZ_LINKVIEW 0 -#include "fuzzer.cpp" diff --git a/src/object-store/tests/notifications-fuzzer/fuzz-unsorted-linkview.cpp b/src/object-store/tests/notifications-fuzzer/fuzz-unsorted-linkview.cpp deleted file mode 100644 index 24d25184..00000000 --- a/src/object-store/tests/notifications-fuzzer/fuzz-unsorted-linkview.cpp +++ /dev/null @@ -1,3 +0,0 @@ -#define FUZZ_SORTED 0 -#define FUZZ_LINKVIEW 1 -#include "fuzzer.cpp" diff --git a/src/object-store/tests/notifications-fuzzer/fuzz-unsorted-query.cpp b/src/object-store/tests/notifications-fuzzer/fuzz-unsorted-query.cpp deleted file mode 100644 index 6dec4c74..00000000 --- a/src/object-store/tests/notifications-fuzzer/fuzz-unsorted-query.cpp +++ /dev/null @@ -1,3 +0,0 @@ -#define FUZZ_SORTED 0 -#define FUZZ_LINKVIEW 0 -#include "fuzzer.cpp" diff --git a/src/object-store/tests/notifications-fuzzer/fuzzer.cpp b/src/object-store/tests/notifications-fuzzer/fuzzer.cpp deleted file mode 100644 index 6b088e85..00000000 --- a/src/object-store/tests/notifications-fuzzer/fuzzer.cpp +++ /dev/null @@ -1,291 +0,0 @@ -#include "command_file.hpp" - -#include "list.hpp" -#include "object_schema.hpp" -#include "property.hpp" -#include "results.hpp" -#include "schema.hpp" -#include "impl/realm_coordinator.hpp" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -using namespace realm; - -#ifndef FUZZ_SORTED -#define FUZZ_SORTED 0 -#endif - -#ifndef FUZZ_LINKVIEW -#define FUZZ_LINKVIEW 0 -#endif - -#define FUZZ_LOG 0 - -// Read from a fd until eof into a string -// Needs to use unbuffered i/o to work properly with afl -static void read_all(std::string& buffer, int fd) -{ - buffer.clear(); - size_t offset = 0; - while (true) { - buffer.resize(offset + 4096); - ssize_t bytes_read = read(fd, &buffer[offset], 4096); - if (bytes_read < 4096) { - buffer.resize(offset + bytes_read); - break; - } - offset += 4096; - } -} - -static Query query(fuzzer::RealmState& state) -{ -#if FUZZ_LINKVIEW - return state.table.where(state.lv); -#else - return state.table.where().greater(1, 100).less(1, 50000); -#endif -} - -static TableView tableview(fuzzer::RealmState& state) -{ - auto tv = query(state).find_all(); -#if FUZZ_SORTED - tv.sort({1, 0}, {true, true}); -#endif - return tv; -} - -// Apply the changes from the command file and then return whether a change -// notification should occur -static bool apply_changes(fuzzer::CommandFile& commands, fuzzer::RealmState& state) -{ - auto tv = tableview(state); -#if FUZZ_LOG - for (size_t i = 0; i < tv.size(); ++i) - fprintf(stderr, "pre: %lld\n", tv.get_int(0, i)); -#endif - - commands.run(state); - - auto tv2 = tableview(state); - if (tv.size() != tv2.size()) - return true; - - for (size_t i = 0; i < tv.size(); ++i) { -#if FUZZ_LOG - fprintf(stderr, "%lld %lld\n", tv.get_int(0, i), tv2.get_int(0, i)); -#endif - if (!tv.is_row_attached(i)) - return true; - if (tv.get_int(0, i) != tv2.get_int(0, i)) - return true; - if (find(begin(state.modified), end(state.modified), tv.get_int(0, i)) != end(state.modified)) - return true; - } - - return false; -} - -static auto verify(CollectionChangeIndices const& changes, std::vector values, fuzzer::RealmState& state) -{ - auto tv = tableview(state); - - // Apply the changes from the transaction log to our copy of the - // initial, using UITableView's batching rules (i.e. delete, then - // insert, then update) - auto it = util::make_reverse_iterator(changes.deletions.end()); - auto end = util::make_reverse_iterator(changes.deletions.begin()); - for (; it != end; ++it) { - values.erase(values.begin() + it->first, values.begin() + it->second); - } - - for (auto i : changes.insertions.as_indexes()) { - values.insert(values.begin() + i, tv.get_int(1, i)); - } - - if (values.size() != tv.size()) { - abort(); - } - - for (auto i : changes.modifications.as_indexes()) { - if (changes.insertions.contains(i)) - abort(); - values[i] = tv.get_int(1, i); - } - -#if FUZZ_SORTED - if (!std::is_sorted(values.begin(), values.end())) - abort(); -#endif - - for (size_t i = 0; i < values.size(); ++i) { - if (values[i] != tv.get_int(1, i)) { -#if FUZZ_LOG - fprintf(stderr, "%lld %lld\n", values[i], tv.get_int(1, i)); -#endif - abort(); - } - } - - return values; -} - -static void verify_no_op(CollectionChangeIndices const& changes, std::vector values, fuzzer::RealmState& state) -{ - auto new_values = verify(changes, values, state); - if (!std::equal(begin(values), end(values), begin(new_values), end(new_values))) - abort(); -} - -static void test(Realm::Config const& config, SharedRealm& r, SharedRealm& r2, std::istream& input_stream) -{ - fuzzer::RealmState state = { - *r, - *_impl::RealmCoordinator::get_existing_coordinator(r->config().path), - *r->read_group()->get_table("class_object"), - r->read_group()->get_table("class_linklist")->get_linklist(0, 0), - 0, - {} - }; - - fuzzer::CommandFile command(input_stream); - if (command.initial_values.empty()) { - return; - } - command.import(state); - - fuzzer::RealmState state2 = { - *r2, - state.coordinator, - *r2->read_group()->get_table("class_object"), -#if FUZZ_LINKVIEW - r2->read_group()->get_table("class_linklist")->get_linklist(0, 0), -#else - {}, -#endif - state.uid, - {} - }; - -#if FUZZ_LINKVIEW && !FUZZ_SORTED - auto results = List(r, ObjectSchema(), state.lv); -#else - auto results = Results(r, ObjectSchema(), query(state)) -#if FUZZ_SORTED - .sort({{1, 0}, {true, true}}) -#endif - ; -#endif // FUZZ_LINKVIEW - - std::vector initial_values; - for (size_t i = 0; i < results.size(); ++i) - initial_values.push_back(results.get(i).get_int(1)); - - CollectionChangeIndices changes; - int notification_calls = 0; - auto token = results.add_notification_callback([&](CollectionChangeIndices c, std::exception_ptr err) { - if (notification_calls > 0 && c.empty()) - abort(); - changes = c; - ++notification_calls; - }); - - state.coordinator.on_change(); r->notify(); - if (notification_calls != 1) { - abort(); - } - - bool expect_notification = apply_changes(command, state2); - state.coordinator.on_change(); r->notify(); - - if (expect_notification) { - if (notification_calls != 2) - abort(); - verify(changes, initial_values, state); - } - else { - if (notification_calls == 2) - verify_no_op(changes, initial_values, state); - } -} - -int main(int argc, char** argv) { - std::ios_base::sync_with_stdio(false); - realm::disable_sync_to_disk(); - - Realm::Config config; - config.path = "fuzzer.realm"; - config.cache = false; - config.in_memory = true; - config.automatic_change_notifications = false; - - Schema schema{ - {"object", "", { - {"id", PropertyTypeInt}, - {"value", PropertyTypeInt} - }}, - {"linklist", "", { - {"list", PropertyTypeArray, "object"} - }} - }; - - config.schema = std::make_unique(schema); - unlink(config.path.c_str()); - - auto r = Realm::get_shared_realm(config); - auto r2 = Realm::get_shared_realm(config); - auto& coordinator = *_impl::RealmCoordinator::get_existing_coordinator(config.path); - - r->begin_transaction(); - r->read_group()->get_table("class_linklist")->add_empty_row(); - r->commit_transaction(); - - auto test_on = [&](auto& buffer) { - std::istringstream ss(buffer); - test(config, r, r2, ss); - if (r->is_in_transaction()) - r->cancel_transaction(); - r2->invalidate(); - coordinator.on_change(); - }; - - if (argc > 1) { - std::string buffer; - for (int i = 1; i < argc; ++i) { - int fd = open(argv[i], O_RDONLY); - if (fd < 0) - abort(); - read_all(buffer, fd); - close(fd); - - test_on(buffer); - } - unlink(config.path.c_str()); - return 0; - } - -#ifdef __AFL_HAVE_MANUAL_CONTROL - std::string buffer; - while (__AFL_LOOP(1000)) { - read_all(buffer, 0); - test_on(buffer); - } -#else - std::string buffer; - read_all(buffer, 0); - test_on(buffer); -#endif - - unlink(config.path.c_str()); - return 0; -} diff --git a/src/object-store/tests/notifications-fuzzer/input-lv/0 b/src/object-store/tests/notifications-fuzzer/input-lv/0 deleted file mode 100644 index cb6a3bd9..00000000 --- a/src/object-store/tests/notifications-fuzzer/input-lv/0 +++ /dev/null @@ -1,38 +0,0 @@ -3 -100 -200 -400 -1000 -2000 -50 -80 -150 -180 -6000 -5000 -60000 - -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 - -a 500 -d 12 -c -m 11 10000 -a 800 -i 5 13 -s 3 8 -o 2 10 -w 1 6 -r 7 -t 11 diff --git a/src/object-store/tests/notifications-fuzzer/input/0 b/src/object-store/tests/notifications-fuzzer/input/0 deleted file mode 100644 index 675ab157..00000000 --- a/src/object-store/tests/notifications-fuzzer/input/0 +++ /dev/null @@ -1,20 +0,0 @@ -3 -100 -200 -400 -1000 -2000 -50 -80 -150 -180 -6000 -5000 -60000 - - -a 500 -d 12 -c -m 11 10000 -a 800 diff --git a/src/object-store/tests/notifications-fuzzer/input/1 b/src/object-store/tests/notifications-fuzzer/input/1 deleted file mode 100644 index 1cbf4855..00000000 --- a/src/object-store/tests/notifications-fuzzer/input/1 +++ /dev/null @@ -1,34 +0,0 @@ -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 - - -a 114 -a 115 -a 116 -a 117 -a 118 -a 119 -a 120 -a 121 -a 122 -c -m 4 200 -m 3 201 -m 2 202 -m 1 203 -m 5 203 -m 6 204 -m 7 205 -c -d 11 diff --git a/src/object-store/tests/parser.cpp b/src/object-store/tests/parser.cpp deleted file mode 100644 index d7058d32..00000000 --- a/src/object-store/tests/parser.cpp +++ /dev/null @@ -1,168 +0,0 @@ -#include "catch.hpp" -#include "parser/parser.hpp" - -#include -#include - -static std::vector valid_queries = { - // true/false predicates - "truepredicate", - "falsepredicate", - " TRUEPREDICATE ", - " FALSEPREDICATE ", - "truepredicates = falsepredicates", // keypaths - - // characters/strings - "\"\" = ''", - "'azAZ09/ :()[]{}<>,.^@-+=*&~`' = '\\\" \\' \\\\ \\/ \\b \\f \\n \\r \\t \\0'", - "\"azAZ09/\" = \"\\\" \\' \\\\ \\/ \\b \\f \\n \\r \\t \\0\"", - "'\\uffFf' = '\\u0020'", - "'\\u01111' = 'asdf\\u0111asdf'", - - // expressions (numbers, bools, keypaths, arguments) - "-1 = 12", - "0 = 001", - "0x0 = -0X398235fcAb", - "10. = -.034", - "10.0 = 5.034", - "true = false", - "truelove = false", - "true = falsey", - "nullified = null", - "_ = a", - "_a = _.aZ", - "a09._br.z = __-__.Z-9", - "$0 = $19", - "$0=$0", - - // operators - "0=0", - "0 = 0", - "0 =[c] 0", - "0!=0", - "0 != 0", - "0==0", - "0 == 0", - "0==[c]0", - "0 == [c] 0", - "0>0", - "0 > 0", - "0>=0", - "0 >= 0", - "0<0", - "0 < 0", - "0<=0", - "0 <= 0", - "0 contains 0", - "a CONTAINS[c] b", - "a contains [c] b", - "'a'CONTAINS[c]b", - "0 BeGiNsWiTh 0", - "0 ENDSWITH 0", - "contains contains 'contains'", - "beginswith beginswith 'beginswith'", - "endswith endswith 'endswith'", - "NOT NOT != 'NOT'", - "AND == 'AND' AND OR == 'OR'", - // FIXME - bug - // "truepredicate == 'falsepredicate' && truepredicate", - - // atoms/groups - "(0=0)", - "( 0=0 )", - "((0=0))", - "!0=0", - "! 0=0", - "!(0=0)", - "! (0=0)", - "NOT0=0", // keypath NOT0 - "NOT0.a=0", // keypath NOT0 - "NOT0a.b=0", // keypath NOT0a - "not-1=1", - "not 0=0", - "NOT(0=0)", - "not (0=0)", - "NOT (!0=0)", - - // compound - "a==a && a==a", - "a==a || a==a", - "a==a&&a==a||a=a", - "a==a and a==a", - "a==a OR a==a", - "and=='AND'&&'or'=='||'", - "and == or && ORE > GRAND", - "a=1AND NOTb=2", -}; - -static std::vector invalid_queries = { - "predicate", - "'\\a' = ''", // invalid escape - - // invalid unicode - "'\\u0' = ''", - - // invalid strings - "\"' = ''", - "\" = ''", - "' = ''", - - // expressions - "03a = 1", - "1..0 = 1", - "1.0. = 1", - "1-0 = 1", - "0x = 1", - "- = a", - "a..b = a", - "a$a = a", - "{} = $0", - "$-1 = $0", - "$a = $0", - "$ = $", - - // operators - "0===>0", - "0 <> 0", - "0 contains1", - "a contains_something", - "endswith 0", - - // atoms/groups - "0=0)", - "(0=0", - "(0=0))", - "! =0", - "NOTNOT(0=0)", - "not.a=0", - "(!!0=0)", - "0=0 !", - - // compound - "a==a & a==a", - "a==a | a==a", - "a==a &| a==a", - "a==a && OR a==a", - "a==aORa==a", - "a==a ORa==a", - "a==a AND==a", - "a==a ANDa==a", - "a=1ANDNOT b=2", - - "truepredicate &&", - "truepredicate & truepredicate", -}; - -TEST_CASE("valid queries") { - for (auto& query : valid_queries) { - INFO("query: " << query); - CHECK_NOTHROW(realm::parser::parse(query)); - } -} - -TEST_CASE("invalid queries") { - for (auto& query : invalid_queries) { - INFO("query: " << query); - CHECK_THROWS(realm::parser::parse(query)); - } -} diff --git a/src/object-store/tests/query.json b/src/object-store/tests/query.json deleted file mode 100644 index 6cbbee55..00000000 --- a/src/object-store/tests/query.json +++ /dev/null @@ -1,381 +0,0 @@ -{ - -"dateTests" : { - "schema" : [{ - "name": "DateObject", - "properties": [{ "name": "date", "type": "date" }] - }], - "objects": [ - { "type": "DateObject", "value": [10000] }, - { "type": "DateObject", "value": [10001] }, - { "type": "DateObject", "value": [10002] } - ], - "tests": [ - ["QueryCount", 2, "DateObject", "date < $0", [2, "date"]], - ["QueryCount", 3, "DateObject", "date <= $0", [2, "date"]], - ["QueryCount", 2, "DateObject", "date > $0", [0, "date"]], - ["QueryCount", 3, "DateObject", "date >= $0", [0, "date"]], - ["QueryCount", 1, "DateObject", "date == $0", [0, "date"]], - ["QueryCount", 2, "DateObject", "date != $0", [0, "date"]], - - ["QueryThrows", "DateObject", "date == 'not a date'"], - ["QueryThrows", "DateObject", "date == 1"], - ["QueryThrows", "DateObject", "date == $0", 1] - ] -}, - -"boolTests" : { - "schema" : [{ - "name": "BoolObject", - "properties": [{ "name": "boolCol", "type": "bool" }] - }], - "objects": [ - { "type": "BoolObject", "value": [false] }, - { "type": "BoolObject", "value": [true] }, - { "type": "BoolObject", "value": [true] } - ], - "tests": [ - ["QueryCount", 2, "BoolObject", "boolCol == true"], - ["QueryCount", 1, "BoolObject", "boolCol==false"], - ["QueryCount", 1, "BoolObject", "boolCol != true"], - ["QueryCount", 2, "BoolObject", "true == boolCol"], - ["QueryCount", 2, "BoolObject", "boolCol == TRUE"], - ["QueryCount", 1, "BoolObject", "boolCol == FALSE"], - ["QueryCount", 2, "BoolObject", "boolCol == $0", true], - ["QueryCount", 1, "BoolObject", "boolCol == $0", false], - ["QueryCount", 0, "BoolObject", "boolCol == true && boolCol == false"], - ["QueryCount", 3, "BoolObject", "boolCol == true || boolCol == false"], - - ["QueryThrows", "BoolObject", "boolCol == 0"], - ["QueryThrows", "BoolObject", "boolCol == 1"], - ["QueryThrows", "BoolObject", "boolCol == 'not a bool'"], - ["QueryThrows", "BoolObject", "boolCol == $0", "not a bool"], - ["QueryThrows", "BoolObject", "boolCol > true"], - ["QueryThrows", "BoolObject", "boolCol >= true"], - ["QueryThrows", "BoolObject", "boolCol < true"], - ["QueryThrows", "BoolObject", "boolCol <= true"], - ["QueryThrows", "BoolObject", "boolCol BEGINSWITH true"], - ["QueryThrows", "BoolObject", "boolCol CONTAINS true"], - ["QueryThrows", "BoolObject", "boolCol ENDSWITH true"] - ] -}, - -"intTests" : { - "schema" : [{ - "name": "IntObject", - "properties": [{ "name": "intCol", "type": "int" }] - }], - "objects": [ - { "type": "IntObject", "value": [-1] }, - { "type": "IntObject", "value": [0] }, - { "type": "IntObject", "value": [100] } - ], - "tests": [ - ["QueryCount", 1, "IntObject", "intCol == -1"], - ["QueryCount", 1, "IntObject", "intCol==0"], - ["QueryCount", 0, "IntObject", "1 == intCol"], - ["QueryCount", 2, "IntObject", "intCol != 0"], - ["QueryCount", 2, "IntObject", "intCol > -1"], - ["QueryCount", 3, "IntObject", "intCol >= -1"], - ["QueryCount", 2, "IntObject", "intCol < 100"], - ["QueryCount", 3, "IntObject", "intCol <= 100"], - ["QueryCount", 1, "IntObject", "intCol > 0x1F"], - ["QueryCount", 1, "IntObject", "intCol == $0", 100], - - ["QueryThrows", "IntObject", "intCol == 'not an int'"], - ["QueryThrows", "IntObject", "intCol == true"], - ["QueryThrows", "IntObject", "intCol == $0", "not an int"], - ["QueryThrows", "IntObject", "intCol BEGINSWITH 1"], - ["QueryThrows", "IntObject", "intCol CONTAINS 1"], - ["QueryThrows", "IntObject", "intCol ENDSWITH 1"] - ] -}, - -"floatTests" : { - "schema" : [{ - "name": "FloatObject", - "properties": [{ "name": "floatCol", "type": "float" }] - }], - "objects": [ - { "type": "FloatObject", "value": [-1.001] }, - { "type": "FloatObject", "value": [0.0] }, - { "type": "FloatObject", "value": [100.2] } - ], - "tests": [ - ["QueryCount", 1, "FloatObject", "floatCol == -1.001"], - ["QueryCount", 1, "FloatObject", "floatCol = 0"], - ["QueryCount", 0, "FloatObject", "1 == floatCol"], - ["QueryCount", 2, "FloatObject", "floatCol != 0"], - ["QueryCount", 2, "FloatObject", "floatCol > -1.001"], - ["QueryCount", 3, "FloatObject", "floatCol >= -1.001"], - ["QueryCount", 2, "FloatObject", "floatCol < 100.2"], - ["QueryCount", 3, "FloatObject", "floatCol <= 100.2"], - ["QueryCount", 1, "FloatObject", "floatCol > 0x1F"], - ["QueryCount", 1, "FloatObject", "floatCol == $0", 100.2], - - ["QueryThrows", "FloatObject", "floatCol == 'not a float'"], - ["QueryThrows", "FloatObject", "floatCol == true"], - ["QueryThrows", "FloatObject", "floatCol == $0", "not a float"], - ["QueryThrows", "FloatObject", "floatCol BEGINSWITH 1"], - ["QueryThrows", "FloatObject", "floatCol CONTAINS 1"], - ["QueryThrows", "FloatObject", "floatCol ENDSWITH 1"], - - ["Disabled", "QueryThrows", "FloatObject", "floatCol = 3.5e+38"], - ["Disabled", "QueryThrows", "FloatObject", "floatCol = -3.5e+38"] - ] -}, - -"doubleTests" : { - "schema" : [{ - "name": "DoubleObject", - "properties": [{ "name": "doubleCol", "type": "double" }] - }], - "objects": [ - { "type": "DoubleObject", "value": [-1.001] }, - { "type": "DoubleObject", "value": [0.0] }, - { "type": "DoubleObject", "value": [100.2] } - ], - "tests": [ - ["QueryCount", 1, "DoubleObject", "doubleCol == -1.001"], - ["QueryCount", 1, "DoubleObject", "doubleCol == 0"], - ["QueryCount", 0, "DoubleObject", "1 == doubleCol"], - ["QueryCount", 2, "DoubleObject", "doubleCol != 0"], - ["QueryCount", 2, "DoubleObject", "doubleCol > -1.001"], - ["QueryCount", 3, "DoubleObject", "doubleCol >= -1.001"], - ["QueryCount", 2, "DoubleObject", "doubleCol < 100.2"], - ["QueryCount", 3, "DoubleObject", "doubleCol <= 100.2"], - ["QueryCount", 1, "DoubleObject", "doubleCol > 0x1F"], - ["QueryCount", 1, "DoubleObject", "doubleCol == $0", 100.2], - - ["QueryThrows", "DoubleObject", "doubleCol == 'not a double'"], - ["QueryThrows", "DoubleObject", "doubleCol == true"], - ["QueryThrows", "DoubleObject", "doubleCol == $0", "not a double"], - ["QueryThrows", "DoubleObject", "doubleCol BEGINSWITH 1"], - ["QueryThrows", "DoubleObject", "doubleCol CONTAINS 1"], - ["QueryThrows", "DoubleObject", "doubleCol ENDSWITH 1"] - ] -}, - -"stringTests" : { - "schema" : [{ - "name": "StringObject", - "properties": [{ "name": "stringCol", "type": "string" }] - }], - "objects": [ - { "type": "StringObject", "value": ["A"] }, - { "type": "StringObject", "value": ["a"] }, - { "type": "StringObject", "value": ["a"] }, - { "type": "StringObject", "value": ["C"] }, - { "type": "StringObject", "value": ["c"] }, - { "type": "StringObject", "value": ["abc"] }, - { "type": "StringObject", "value": ["ABC"] }, - { "type": "StringObject", "value": [""] }, - { "type": "StringObject", "value": ["\\\"\\n\\0\\r\\\\'"] } - ], - "tests": [ - ["QueryCount", 2, "StringObject", "stringCol == 'a'"], - ["QueryCount", 1, "StringObject", "'c' == stringCol"], - ["QueryCount", 2, "StringObject", "stringCol == \"a\""], - ["QueryCount", 1, "StringObject", "stringCol=='abc'"], - ["QueryCount", 1, "StringObject", "stringCol == ''"], - ["QueryCount", 8, "StringObject", "stringCol != ''"], - ["QueryCount", 1, "StringObject", "stringCol == \"\\\"\\n\\0\\r\\\\'\""], - ["QueryCount", 3, "StringObject", "stringCol BEGINSWITH 'a'"], - ["QueryCount", 1, "StringObject", "stringCol beginswith 'ab'"], - ["QueryCount", 0, "StringObject", "stringCol BEGINSWITH 'abcd'"], - ["QueryCount", 2, "StringObject", "stringCol BEGINSWITH 'A'"], - ["QueryCount", 2, "StringObject", "stringCol ENDSWITH 'c'"], - ["QueryCount", 1, "StringObject", "stringCol endswith 'bc'"], - ["QueryCount", 9, "StringObject", "stringCol ENDSWITH ''"], - ["QueryCount", 1, "StringObject", "stringCol CONTAINS 'b'"], - ["QueryCount", 2, "StringObject", "stringCol contains 'c'"], - ["QueryCount", 9, "StringObject", "stringCol CONTAINS ''"], - ["QueryCount", 2, "StringObject", "stringCol == $0", "a"], - ["QueryCount", 2, "StringObject", "stringCol ENDSWITH $0", "c"], - - ["QueryThrows", "StringObject", "stringCol == true"], - ["QueryThrows", "StringObject", "stringCol == 123"], - ["QueryThrows", "StringObject", "stringCol CONTAINS $0", 1], - - ["QueryCount", 3, "StringObject", "stringCol ==[c] 'a'"], - ["QueryCount", 5, "StringObject", "stringCol BEGINSWITH[c] 'A'"], - ["QueryCount", 4, "StringObject", "stringCol ENDSWITH[c] 'c'"], - ["QueryCount", 2, "StringObject", "stringCol CONTAINS[c] 'B'"] - ] -}, - -"binaryTests" : { - "schema" : [{ - "name": "BinaryObject", - "properties": [{ "name": "binaryCol", "type": "data" }] - }], - "objects": [ - { "type": "BinaryObject", "value": [[1, 100, 233, 255, 0]] }, - { "type": "BinaryObject", "value": [[1, 100]] }, - { "type": "BinaryObject", "value": [[100]] }, - { "type": "BinaryObject", "value": [[]] }, - { "type": "BinaryObject", "value": [[255, 0]] } - ], - "tests": [ - ["QueryCount", 1, "BinaryObject", "binaryCol == $0", [1, "binaryCol"]], - ["QueryCount", 1, "BinaryObject", "$0 == binaryCol", [2, "binaryCol"]], - ["QueryCount", 4, "BinaryObject", "binaryCol != $0", [0, "binaryCol"]], - ["QueryCount", 1, "BinaryObject", "binaryCol BEGINSWITH $0", [0, "binaryCol"]], - ["QueryCount", 2, "BinaryObject", "binaryCol BEGINSWITH $0", [1, "binaryCol"]], - ["QueryCount", 2, "BinaryObject", "binaryCol ENDSWITH $0", [4, "binaryCol"]], - ["QueryCount", 3, "BinaryObject", "binaryCol CONTAINS $0", [2, "binaryCol"]] - ] -}, - -"objectTests" : { - "schema" : [ - { "name": "IntObject", "properties": [ - { "name": "intCol", "type": "int" } - ]}, - { "name": "LinkObject", "properties": [ - { "name": "linkCol", "type": "object", "objectType": "IntObject" } - ]} - ], - "objects": [ - { "type": "LinkObject", "value": [[1]] }, - { "type": "LinkObject", "value": [[2]] }, - { "type": "LinkObject", "value": [null] } - ], - "tests": [ - ["QueryCount", 1, "LinkObject", "linkCol == $0", [0, "linkCol"]], - ["QueryCount", 1, "LinkObject", "$0 == linkCol", [1, "linkCol"]], - ["QueryCount", 2, "LinkObject", "linkCol != $0", [0, "linkCol"]], - ["QueryCount", 1, "LinkObject", "linkCol = null"], - ["QueryCount", 2, "LinkObject", "linkCol != NULL"], - ["QueryCount", 1, "LinkObject", "linkCol = $0", null], - - ["QueryThrows", "LinkObject", "linkCol > $0", [0, "linkCol"]], - ["QueryThrows", "LinkObject", "intCol = $0", [0, "linkCol"]] - ] -}, - -"compoundTests" : { - "schema" : [ - { "name": "IntObject", - "properties": [{ "name": "intCol", "type": "int" }], - "primaryKey" : "intCol" } - ], - "objects": [ - { "type": "IntObject", "value": [0] }, - { "type": "IntObject", "value": [1] }, - { "type": "IntObject", "value": [2] }, - { "type": "IntObject", "value": [3] } - ], - "tests": [ - ["ObjectSet", [], "IntObject", "intCol == 0 && intCol == 1"], - ["ObjectSet", [0, 1], "IntObject", "intCol == 0 || intCol == 1"], - ["ObjectSet", [0], "IntObject", "intCol == 0 && intCol != 1"], - ["ObjectSet", [2, 3], "IntObject", "intCol >= 2 && intCol < 4"], - ["ObjectSet", [0], "IntObject", "intCol == 0 && NOT intCol != 0"], - ["ObjectSet", [0, 3], "IntObject", "intCol == 0 || NOT (intCol == 1 || intCol == 2)"], - ["ObjectSet", [1], "IntObject", "(intCol == 0 || intCol == 1) && intCol >= 1"], - ["ObjectSet", [1], "IntObject", "intCol >= 1 && (intCol == 0 || intCol == 1)"], - ["ObjectSet", [0, 1], "IntObject", "intCol == 0 || (intCol == 1 && intCol >= 1)"], - ["ObjectSet", [0, 1], "IntObject", "(intCol == 1 && intCol >= 1) || intCol == 0"], - ["ObjectSet", [0, 1], "IntObject", "intCol == 0 || intCol == 1 && intCol >= 1"], - ["ObjectSet", [0, 1, 2],"IntObject", "intCol == 0 || intCol == 1 || intCol <= 2"], - ["ObjectSet", [0, 1], "IntObject", "intCol == 1 && intCol >= 1 || intCol == 0"], - ["ObjectSet", [0, 1], "IntObject", "intCol == 1 || intCol == 0 && intCol <= 0 && intCol >= 0"], - ["ObjectSet", [0, 1], "IntObject", "intCol == 0 || NOT (intCol == 3 && intCol >= 0) && intCol == 1"] - ] -}, - -"keyPathTests" : { - "schema" : [ - { - "name": "BasicTypesObject", - "properties": [ - { "name": "intCol", "type": "int" }, - { "name": "floatCol", "type": "float" }, - { "name": "doubleCol", "type": "double" }, - { "name": "stringCol", "type": "string" }, - { "name": "dateCol", "type": "date" }, - { "name": "dataCol", "type": "data" } - ] - }, - { - "name": "LinkTypesObject", - "primaryKey": "primaryKey", - "properties": [ - { "name": "primaryKey", "type": "int" }, - { "name": "basicLink", "type": "object", "objectType": "BasicTypesObject" }, - { "name": "linkLink", "type": "object", "objectType": "LinkTypesObject" } - ] - }], - "objects": [ - { "type": "LinkTypesObject", "value": [0, [1, 0.1, 0.001, "1", 1, [1, 10, 100]], null] }, - { "type": "LinkTypesObject", "value": [1, null, [2, [1, 0.1, 0.001, "1", 1, [1, 10, 100]], null]] }, - { "type": "LinkTypesObject", "value": [3, null, [4, [2, 0.2, 0.002, "2", 2, [2, 20, 200]], null]] } - ], - "tests": [ - ["ObjectSet", [0, 2], "LinkTypesObject", "basicLink.intCol == 1"], - ["ObjectSet", [1], "LinkTypesObject", "linkLink.basicLink.intCol == 1"], - ["ObjectSet", [1, 3], "LinkTypesObject", "linkLink.basicLink.intCol > 0"], - ["ObjectSet", [0, 2], "LinkTypesObject", "basicLink.floatCol == 0.1"], - ["ObjectSet", [1], "LinkTypesObject", "linkLink.basicLink.floatCol == 0.1"], - ["ObjectSet", [1, 3], "LinkTypesObject", "linkLink.basicLink.floatCol > 0"] - ] -}, - -"optionalTests" : { - "schema" : [ - { - "name": "OptionalTypesObject", - "primaryKey": "primaryKey", - "properties": [ - { "name": "primaryKey", "type": "int" }, - { "name": "intCol", "type": "int", "optional": true }, - { "name": "floatCol", "type": "float", "optional": true }, - { "name": "doubleCol", "type": "double", "optional": true }, - { "name": "stringCol", "type": "string", "optional": true }, - { "name": "dateCol", "type": "date", "optional": true }, - { "name": "dataCol", "type": "data", "optional": true } - ] - }, - { - "name": "LinkTypesObject", - "primaryKey": "primaryKey", - "properties": [ - { "name": "primaryKey", "type": "int" }, - { "name": "basicLink", "type": "object", "objectType": "OptionalTypesObject" } - ] - }], - "objects": [ - { "type": "LinkTypesObject", "value": [0, [0, 1, 0.1, 0.001, "1", 1, [1, 10, 100]]] }, - { "type": "LinkTypesObject", "value": [1, [1, null, null, null, null, null, null]] } - ], - "tests": [ - ["ObjectSet", [1], "OptionalTypesObject", "intCol == null"], - ["ObjectSet", [1], "OptionalTypesObject", "null == intCol"], - ["ObjectSet", [0], "OptionalTypesObject", "intCol != null"], - ["ObjectSet", [1], "OptionalTypesObject", "floatCol == null"], - ["ObjectSet", [0], "OptionalTypesObject", "floatCol != null"], - ["ObjectSet", [1], "OptionalTypesObject", "doubleCol == null"], - ["ObjectSet", [0], "OptionalTypesObject", "doubleCol != null"], - ["ObjectSet", [1], "OptionalTypesObject", "stringCol == null"], - ["ObjectSet", [0], "OptionalTypesObject", "stringCol != null"], - ["ObjectSet", [1], "OptionalTypesObject", "dateCol == null"], - ["ObjectSet", [0], "OptionalTypesObject", "dateCol != null"], - ["ObjectSet", [1], "OptionalTypesObject", "dataCol == null"], - ["ObjectSet", [0], "OptionalTypesObject", "dataCol != null"], - - ["ObjectSet", [1], "LinkTypesObject", "basicLink.intCol == null"], - ["ObjectSet", [0], "LinkTypesObject", "basicLink.intCol != null"], - ["ObjectSet", [1], "LinkTypesObject", "basicLink.floatCol == null"], - ["ObjectSet", [0], "LinkTypesObject", "basicLink.floatCol != null"], - ["ObjectSet", [1], "LinkTypesObject", "basicLink.doubleCol == null"], - ["ObjectSet", [0], "LinkTypesObject", "basicLink.doubleCol != null"], - ["ObjectSet", [1], "LinkTypesObject", "basicLink.stringCol == null"], - ["ObjectSet", [0], "LinkTypesObject", "basicLink.stringCol != null"], - ["ObjectSet", [1], "LinkTypesObject", "basicLink.dateCol == null"], - ["ObjectSet", [0], "LinkTypesObject", "basicLink.dateCol != null"], - ["QueryThrows", "LinkTypesObject", "basicLink.dataCol == null"] - ] -} - -} diff --git a/src/object-store/tests/results.cpp b/src/object-store/tests/results.cpp deleted file mode 100644 index 18bf1243..00000000 --- a/src/object-store/tests/results.cpp +++ /dev/null @@ -1,916 +0,0 @@ -#include "catch.hpp" - -#include "util/index_helpers.hpp" -#include "util/test_file.hpp" - -#include "impl/realm_coordinator.hpp" -#include "object_schema.hpp" -#include "property.hpp" -#include "results.hpp" -#include "schema.hpp" - -#include -#include -#include -#include - -#include - -using namespace realm; - -TEST_CASE("[results] notifications") { - InMemoryTestFile config; - config.cache = false; - config.automatic_change_notifications = false; - config.schema = std::make_unique(Schema{ - {"object", "", { - {"value", PropertyType::Int}, - {"link", PropertyType::Object, "linked to object", "", false, false, true} - }}, - {"other object", "", { - {"value", PropertyType::Int} - }}, - {"linking object", "", { - {"link", PropertyType::Object, "object", "", false, false, true} - }}, - {"linked to object", "", { - {"value", PropertyType::Int} - }} - }); - - auto r = Realm::get_shared_realm(config); - auto coordinator = _impl::RealmCoordinator::get_existing_coordinator(config.path); - auto table = r->read_group()->get_table("class_object"); - - r->begin_transaction(); - table->add_empty_row(10); - for (int i = 0; i < 10; ++i) - table->set_int(0, i, i * 2); - r->commit_transaction(); - - Results results(r, table->where().greater(0, 0).less(0, 10)); - - SECTION("unsorted notifications") { - int notification_calls = 0; - CollectionChangeSet change; - auto token = results.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr err) { - REQUIRE_FALSE(err); - change = c; - ++notification_calls; - }); - - advance_and_notify(*r); - - auto write = [&](auto&& f) { - r->begin_transaction(); - f(); - r->commit_transaction(); - advance_and_notify(*r); - }; - - SECTION("initial results are delivered") { - REQUIRE(notification_calls == 1); - } - - SECTION("notifications are sent asynchronously") { - r->begin_transaction(); - table->set_int(0, 0, 4); - r->commit_transaction(); - - REQUIRE(notification_calls == 1); - advance_and_notify(*r); - REQUIRE(notification_calls == 2); - } - - SECTION("notifications are not delivered when the token is destroyed before they are calculated") { - r->begin_transaction(); - table->set_int(0, 0, 4); - r->commit_transaction(); - - REQUIRE(notification_calls == 1); - token = {}; - advance_and_notify(*r); - REQUIRE(notification_calls == 1); - } - - SECTION("notifications are not delivered when the token is destroyed before they are delivered") { - r->begin_transaction(); - table->set_int(0, 0, 4); - r->commit_transaction(); - - REQUIRE(notification_calls == 1); - coordinator->on_change(); - token = {}; - r->notify(); - REQUIRE(notification_calls == 1); - } - - SECTION("notifications are delivered when a new callback is added from within a callback") { - NotificationToken token2, token3; - bool called = false; - token2 = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { - token3 = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { - called = true; - }); - }); - - advance_and_notify(*r); - REQUIRE(called); - } - - SECTION("notifications are not delivered when a callback is removed from within a callback") { - NotificationToken token2, token3; - token2 = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { - token3 = {}; - }); - token3 = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { - REQUIRE(false); - }); - - advance_and_notify(*r); - } - - SECTION("removing the current callback does not stop later ones from being called") { - NotificationToken token2, token3; - bool called = false; - token2 = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { - token2 = {}; - }); - token3 = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { - called = true; - }); - - advance_and_notify(*r); - - REQUIRE(called); - } - - SECTION("modifications to unrelated tables do not send notifications") { - write([&] { - r->read_group()->get_table("class_other object")->add_empty_row(); - }); - REQUIRE(notification_calls == 1); - } - - SECTION("irrelevant modifications to linked tables do not send notifications") { - write([&] { - r->read_group()->get_table("class_linked to object")->add_empty_row(); - }); - REQUIRE(notification_calls == 1); - } - - SECTION("irrelevant modifications to linking tables do not send notifications") { - write([&] { - r->read_group()->get_table("class_linking object")->add_empty_row(); - }); - REQUIRE(notification_calls == 1); - } - - SECTION("modifications that leave a non-matching row non-matching do not send notifications") { - write([&] { - table->set_int(0, 6, 13); - }); - REQUIRE(notification_calls == 1); - } - - SECTION("deleting non-matching rows does not send a notification") { - write([&] { - table->move_last_over(0); - table->move_last_over(6); - }); - REQUIRE(notification_calls == 1); - } - - SECTION("modifying a matching row and leaving it matching marks that row as modified") { - write([&] { - table->set_int(0, 1, 3); - }); - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.modifications, 0); - } - - SECTION("modifying a matching row to no longer match marks that row as deleted") { - write([&] { - table->set_int(0, 2, 0); - }); - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.deletions, 1); - } - - SECTION("modifying a non-matching row to match marks that row as inserted, but not modified") { - write([&] { - table->set_int(0, 7, 3); - }); - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.insertions, 4); - REQUIRE(change.modifications.empty()); - } - - SECTION("deleting a matching row marks that row as deleted") { - write([&] { - table->move_last_over(3); - }); - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.deletions, 2); - } - - SECTION("moving a matching row via deletion marks that row as moved") { - write([&] { - table->where().greater_equal(0, 10).find_all().clear(RemoveMode::unordered); - table->move_last_over(0); - }); - REQUIRE(notification_calls == 2); - REQUIRE_MOVES(change, {3, 0}); - } - - SECTION("modifications from multiple transactions are collapsed") { - r->begin_transaction(); - table->set_int(0, 0, 6); - r->commit_transaction(); - - coordinator->on_change(); - - r->begin_transaction(); - table->set_int(0, 1, 0); - r->commit_transaction(); - - REQUIRE(notification_calls == 1); - coordinator->on_change(); - r->notify(); - REQUIRE(notification_calls == 2); - } - - SECTION("inserting a row then modifying it in a second transaction does not report it as modified") { - r->begin_transaction(); - size_t ndx = table->add_empty_row(); - table->set_int(0, ndx, 6); - r->commit_transaction(); - - coordinator->on_change(); - - r->begin_transaction(); - table->set_int(0, ndx, 7); - r->commit_transaction(); - - advance_and_notify(*r); - - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.insertions, 4); - REQUIRE(change.modifications.empty()); - } - - SECTION("modification indices are pre-insert/delete") { - r->begin_transaction(); - table->set_int(0, 2, 0); - table->set_int(0, 3, 6); - r->commit_transaction(); - advance_and_notify(*r); - - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.deletions, 1); - REQUIRE_INDICES(change.modifications, 2); - } - - SECTION("notifications are not delivered when collapsing transactions results in no net change") { - r->begin_transaction(); - size_t ndx = table->add_empty_row(); - table->set_int(0, ndx, 5); - r->commit_transaction(); - - coordinator->on_change(); - - r->begin_transaction(); - table->move_last_over(ndx); - r->commit_transaction(); - - REQUIRE(notification_calls == 1); - coordinator->on_change(); - r->notify(); - REQUIRE(notification_calls == 1); - } - - SECTION("the first call of a notification can include changes if it previously ran for a different callback") { - auto token2 = results.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) { - REQUIRE(!c.empty()); - }); - - write([&] { - table->set_int(0, table->add_empty_row(), 5); - }); - } - } - - // Sort in descending order - results = results.sort({{0}, {false}}); - - SECTION("sorted notifications") { - int notification_calls = 0; - CollectionChangeSet change; - auto token = results.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr err) { - REQUIRE_FALSE(err); - change = c; - ++notification_calls; - }); - - advance_and_notify(*r); - - auto write = [&](auto&& f) { - r->begin_transaction(); - f(); - r->commit_transaction(); - advance_and_notify(*r); - }; - - SECTION("modifications that leave a non-matching row non-matching do not send notifications") { - write([&] { - table->set_int(0, 6, 13); - }); - REQUIRE(notification_calls == 1); - } - - SECTION("deleting non-matching rows does not send a notification") { - write([&] { - table->move_last_over(0); - table->move_last_over(6); - }); - REQUIRE(notification_calls == 1); - } - - SECTION("modifying a matching row and leaving it matching marks that row as modified") { - write([&] { - table->set_int(0, 1, 3); - }); - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.modifications, 3); - } - - SECTION("modifying a matching row to no longer match marks that row as deleted") { - write([&] { - table->set_int(0, 2, 0); - }); - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.deletions, 2); - } - - SECTION("modifying a non-matching row to match marks that row as inserted") { - write([&] { - table->set_int(0, 7, 3); - }); - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.insertions, 3); - } - - SECTION("deleting a matching row marks that row as deleted") { - write([&] { - table->move_last_over(3); - }); - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.deletions, 1); - } - - SECTION("moving a matching row via deletion does not send a notification") { - write([&] { - table->where().greater_equal(0, 10).find_all().clear(RemoveMode::unordered); - table->move_last_over(0); - }); - REQUIRE(notification_calls == 1); - } - - SECTION("modifying a matching row to change its position sends insert+delete") { - write([&] { - table->set_int(0, 2, 9); - }); - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.deletions, 2); - REQUIRE_INDICES(change.insertions, 0); - } - - SECTION("modifications from multiple transactions are collapsed") { - r->begin_transaction(); - table->set_int(0, 0, 5); - r->commit_transaction(); - - r->begin_transaction(); - table->set_int(0, 1, 0); - r->commit_transaction(); - - REQUIRE(notification_calls == 1); - advance_and_notify(*r); - REQUIRE(notification_calls == 2); - } - - SECTION("moving a matching row by deleting all other rows") { - r->begin_transaction(); - table->clear(); - table->add_empty_row(2); - table->set_int(0, 0, 15); - table->set_int(0, 1, 5); - r->commit_transaction(); - advance_and_notify(*r); - - write([&] { - table->move_last_over(0); - table->add_empty_row(); - table->set_int(0, 1, 3); - }); - - REQUIRE(notification_calls == 3); - REQUIRE(change.deletions.empty()); - REQUIRE_INDICES(change.insertions, 1); - } - } -} - -TEST_CASE("[results] async error handling") { - InMemoryTestFile config; - config.cache = false; - config.automatic_change_notifications = false; - config.schema = std::make_unique(Schema{ - {"object", "", { - {"value", PropertyType::Int}, - }}, - }); - - auto r = Realm::get_shared_realm(config); - auto coordinator = _impl::RealmCoordinator::get_existing_coordinator(config.path); - Results results(r, *r->read_group()->get_table("class_object")); - - class OpenFileLimiter { - public: - OpenFileLimiter() - { - // Set the max open files to zero so that opening new files will fail - getrlimit(RLIMIT_NOFILE, &m_old); - rlimit rl = m_old; - rl.rlim_cur = 0; - setrlimit(RLIMIT_NOFILE, &rl); - } - - ~OpenFileLimiter() - { - setrlimit(RLIMIT_NOFILE, &m_old); - } - - private: - rlimit m_old; - }; - - SECTION("error when opening the advancer SG") { - OpenFileLimiter limiter; - - SECTION("error is delivered asynchronously") { - bool called = false; - auto token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) { - REQUIRE(err); - called = true; - }); - - REQUIRE(!called); - coordinator->on_change(); - REQUIRE(!called); - r->notify(); - REQUIRE(called); - } - - SECTION("adding another callback does not send the error again") { - bool called = false; - auto token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) { - REQUIRE(err); - REQUIRE_FALSE(called); - called = true; - }); - - advance_and_notify(*r); - - bool called2 = false; - auto token2 = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) { - REQUIRE(err); - REQUIRE_FALSE(called2); - called2 = true; - }); - - advance_and_notify(*r); - REQUIRE(called2); - } - } - - SECTION("error when opening the executor SG") { - SECTION("error is delivered asynchronously") { - bool called = false; - auto token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) { - REQUIRE(err); - called = true; - }); - OpenFileLimiter limiter; - - REQUIRE(!called); - coordinator->on_change(); - REQUIRE(!called); - r->notify(); - REQUIRE(called); - } - - SECTION("adding another callback does not send the error again") { - bool called = false; - auto token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) { - REQUIRE(err); - REQUIRE_FALSE(called); - called = true; - }); - OpenFileLimiter limiter; - - advance_and_notify(*r); - - bool called2 = false; - auto token2 = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) { - REQUIRE(err); - REQUIRE_FALSE(called2); - called2 = true; - }); - - advance_and_notify(*r); - - REQUIRE(called2); - } - } -} - -TEST_CASE("[results] notifications after move") { - InMemoryTestFile config; - config.cache = false; - config.automatic_change_notifications = false; - config.schema = std::make_unique(Schema{ - {"object", "", { - {"value", PropertyType::Int}, - }}, - }); - - auto r = Realm::get_shared_realm(config); - auto table = r->read_group()->get_table("class_object"); - auto results = std::make_unique(r, *table); - - int notification_calls = 0; - auto token = results->add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) { - REQUIRE_FALSE(err); - ++notification_calls; - }); - - advance_and_notify(*r); - - auto write = [&](auto&& f) { - r->begin_transaction(); - f(); - r->commit_transaction(); - advance_and_notify(*r); - }; - - SECTION("notifications continue to work after Results is moved (move-constructor)") { - Results r(std::move(*results)); - results.reset(); - - write([&] { - table->set_int(0, table->add_empty_row(), 1); - }); - REQUIRE(notification_calls == 2); - } - - SECTION("notifications continue to work after Results is moved (move-assignment)") { - Results r; - r = std::move(*results); - results.reset(); - - write([&] { - table->set_int(0, table->add_empty_row(), 1); - }); - REQUIRE(notification_calls == 2); - } -} - -TEST_CASE("[results] error messages") { - InMemoryTestFile config; - config.schema = std::make_unique(Schema{ - {"object", "", { - {"value", PropertyType::String}, - }}, - }); - - auto r = Realm::get_shared_realm(config); - auto table = r->read_group()->get_table("class_object"); - Results results(r, *table); - - r->begin_transaction(); - table->add_empty_row(); - r->commit_transaction(); - - SECTION("out of bounds access") { - REQUIRE_THROWS_WITH(results.get(5), "Requested index 5 greater than max 1"); - } - - SECTION("unsupported aggregate operation") { - 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 deleted file mode 100644 index bf4d71fa..00000000 --- a/src/object-store/tests/transaction_log_parsing.cpp +++ /dev/null @@ -1,991 +0,0 @@ -#include "catch.hpp" - -#include "util/index_helpers.hpp" -#include "util/test_file.hpp" - -#include "impl/collection_notifier.hpp" -#include "impl/transact_log_handler.hpp" -#include "property.hpp" -#include "object_schema.hpp" -#include "schema.hpp" - -#include -#include -#include - -using namespace realm; - -class CaptureHelper { -public: - CaptureHelper(std::string const& path, SharedRealm const& r, LinkViewRef lv) - : m_history(make_client_history(path)) - , m_sg(*m_history, SharedGroup::durability_MemOnly) - , m_realm(r) - , m_group(m_sg.begin_read()) - , m_linkview(lv) - { - m_realm->begin_transaction(); - - m_initial.reserve(lv->size()); - for (size_t i = 0; i < lv->size(); ++i) - m_initial.push_back(lv->get(i).get_int(0)); - } - - CollectionChangeSet finish(size_t ndx) { - m_realm->commit_transaction(); - - _impl::CollectionChangeBuilder c; - _impl::TransactionChangeInfo info; - info.lists.push_back({ndx, 0, 0, &c}); - info.table_modifications_needed.resize(m_group.size(), true); - info.table_moves_needed.resize(m_group.size(), true); - _impl::transaction::advance(m_sg, info); - - if (info.lists.empty()) { - REQUIRE(!m_linkview->is_attached()); - return {}; - } - - validate(c); - return c; - } - - explicit operator bool() const { return m_realm->is_in_transaction(); } - -private: - std::unique_ptr m_history; - SharedGroup m_sg; - SharedRealm m_realm; - Group const& m_group; - - LinkViewRef m_linkview; - std::vector m_initial; - - void validate(CollectionChangeSet const& info) - { - info.insertions.verify(); - info.deletions.verify(); - info.modifications.verify(); - - std::vector move_sources; - for (auto const& move : info.moves) - move_sources.push_back(m_initial[move.from]); - - // Apply the changes from the transaction log to our copy of the - // initial, using UITableView's batching rules (i.e. delete, then - // insert, then update) - auto it = util::make_reverse_iterator(info.deletions.end()); - auto end = util::make_reverse_iterator(info.deletions.begin()); - for (; it != end; ++it) { - m_initial.erase(m_initial.begin() + it->first, m_initial.begin() + it->second); - } - - for (auto const& range : info.insertions) { - for (auto i = range.first; i < range.second; ++i) - m_initial.insert(m_initial.begin() + i, m_linkview->get(i).get_int(0)); - } - - for (auto const& range : info.modifications) { - for (auto i = range.first; i < range.second; ++i) - m_initial[i] = m_linkview->get(i).get_int(0); - } - - REQUIRE(m_linkview->is_attached()); - - // and make sure we end up with the same end result - REQUIRE(m_initial.size() == m_linkview->size()); - for (size_t i = 0; i < m_initial.size(); ++i) - CHECK(m_initial[i] == m_linkview->get(i).get_int(0)); - - // Verify that everything marked as a move actually is one - for (size_t i = 0; i < move_sources.size(); ++i) { - if (!info.modifications.contains(info.moves[i].to)) { - CHECK(m_linkview->get(info.moves[i].to).get_int(0) == move_sources[i]); - } - } - } -}; - -TEST_CASE("Transaction log parsing") { - InMemoryTestFile config; - config.automatic_change_notifications = false; - - SECTION("schema change validation") { - config.schema = std::make_unique(Schema{ - {"table", "", { - {"unindexed", PropertyType::Int}, - {"indexed", PropertyType::Int, "", "", false, true} - }}, - }); - auto r = Realm::get_shared_realm(config); - r->read_group(); - - auto history = make_client_history(config.path); - SharedGroup sg(*history, SharedGroup::durability_MemOnly); - - SECTION("adding a table is allowed") { - WriteTransaction wt(sg); - TableRef table = wt.add_table("new table"); - table->add_column(type_String, "new col"); - wt.commit(); - - REQUIRE_NOTHROW(r->refresh()); - } - - SECTION("adding an index to an existing column is allowed") { - WriteTransaction wt(sg); - TableRef table = wt.get_table("class_table"); - table->add_search_index(0); - wt.commit(); - - REQUIRE_NOTHROW(r->refresh()); - } - - SECTION("removing an index from an existing column is allowed") { - WriteTransaction wt(sg); - TableRef table = wt.get_table("class_table"); - table->remove_search_index(1); - wt.commit(); - - REQUIRE_NOTHROW(r->refresh()); - } - - SECTION("adding a column to an existing table is not allowed (but eventually should be)") { - WriteTransaction wt(sg); - TableRef table = wt.get_table("class_table"); - table->add_column(type_String, "new col"); - wt.commit(); - - REQUIRE_THROWS(r->refresh()); - } - - SECTION("removing a column is not allowed") { - WriteTransaction wt(sg); - TableRef table = wt.get_table("class_table"); - table->remove_column(1); - wt.commit(); - - REQUIRE_THROWS(r->refresh()); - } - - SECTION("removing a table is not allowed") { - WriteTransaction wt(sg); - wt.get_group().remove_table("class_table"); - wt.commit(); - - REQUIRE_THROWS(r->refresh()); - } - } - - SECTION("table change information") { - config.schema = std::make_unique(Schema{ - {"table", "", { - {"value", PropertyType::Int} - }}, - }); - - auto r = Realm::get_shared_realm(config); - auto& table = *r->read_group()->get_table("class_table"); - - r->begin_transaction(); - table.add_empty_row(10); - for (int i = 0; i < 10; ++i) - table.set_int(0, i, i); - r->commit_transaction(); - - auto track_changes = [&](std::vector tables_needed, auto&& f) { - auto history = make_client_history(config.path); - SharedGroup sg(*history, SharedGroup::durability_MemOnly); - sg.begin_read(); - - r->begin_transaction(); - f(); - r->commit_transaction(); - - _impl::TransactionChangeInfo info; - info.table_modifications_needed = tables_needed; - info.table_moves_needed = tables_needed; - _impl::transaction::advance(sg, info); - return info; - }; - - SECTION("modifying a row marks it as modified") { - auto info = track_changes({false, false, true}, [&] { - table.set_int(0, 1, 2); - }); - REQUIRE(info.tables.size() == 3); - REQUIRE_INDICES(info.tables[2].modifications, 1); - } - - SECTION("modifications to untracked tables are ignored") { - auto info = track_changes({false, false, false}, [&] { - table.set_int(0, 1, 2); - }); - REQUIRE(info.tables.empty()); - } - - SECTION("new row additions are reported") { - auto info = track_changes({false, false, true}, [&] { - table.add_empty_row(); - table.add_empty_row(); - }); - REQUIRE(info.tables.size() == 3); - REQUIRE_INDICES(info.tables[2].insertions, 10, 11); - } - - SECTION("deleting newly added rows makes them not be reported") { - auto info = track_changes({false, false, true}, [&] { - table.add_empty_row(); - table.add_empty_row(); - table.move_last_over(11); - }); - REQUIRE(info.tables.size() == 3); - REQUIRE_INDICES(info.tables[2].insertions, 10); - REQUIRE(info.tables[2].deletions.empty()); - } - - SECTION("modifying newly added rows is reported as a modification") { - auto info = track_changes({false, false, true}, [&] { - table.add_empty_row(); - table.set_int(0, 10, 10); - }); - REQUIRE(info.tables.size() == 3); - REQUIRE_INDICES(info.tables[2].insertions, 10); - REQUIRE_INDICES(info.tables[2].modifications, 10); - } - - SECTION("move_last_over() does not shift rows other than the last one") { - auto info = track_changes({false, false, true}, [&] { - table.move_last_over(2); - table.move_last_over(3); - }); - REQUIRE(info.tables.size() == 3); - REQUIRE_INDICES(info.tables[2].deletions, 2, 3, 8, 9); - REQUIRE_INDICES(info.tables[2].insertions, 2, 3); - REQUIRE_MOVES(info.tables[2], {8, 3}, {9, 2}); - } - } - - SECTION("LinkView change information") { - config.schema = std::make_unique(Schema{ - {"origin", "", { - {"array", PropertyType::Array, "target"} - }}, - {"target", "", { - {"value", PropertyType::Int} - }}, - }); - - auto r = Realm::get_shared_realm(config); - - auto origin = r->read_group()->get_table("class_origin"); - auto target = r->read_group()->get_table("class_target"); - - r->begin_transaction(); - - target->add_empty_row(10); - for (int i = 0; i < 10; ++i) - target->set_int(0, i, i); - - origin->add_empty_row(); - LinkViewRef lv = origin->get_linklist(0, 0); - for (int i = 0; i < 10; ++i) - lv->add(i); - - r->commit_transaction(); - -#define VALIDATE_CHANGES(out) \ - for (CaptureHelper helper(config.path, r, lv); helper; out = helper.finish(origin->get_index_in_group())) - - CollectionChangeSet changes; - SECTION("single change type") { - SECTION("add single") { - VALIDATE_CHANGES(changes) { - lv->add(0); - } - REQUIRE_INDICES(changes.insertions, 10); - } - SECTION("add multiple") { - VALIDATE_CHANGES(changes) { - lv->add(0); - lv->add(0); - } - REQUIRE_INDICES(changes.insertions, 10, 11); - } - - SECTION("erase single") { - VALIDATE_CHANGES(changes) { - lv->remove(5); - } - REQUIRE_INDICES(changes.deletions, 5); - } - SECTION("erase contiguous forward") { - VALIDATE_CHANGES(changes) { - lv->remove(5); - lv->remove(5); - lv->remove(5); - } - REQUIRE_INDICES(changes.deletions, 5, 6, 7); - } - SECTION("erase contiguous reverse") { - VALIDATE_CHANGES(changes) { - lv->remove(7); - lv->remove(6); - lv->remove(5); - } - REQUIRE_INDICES(changes.deletions, 5, 6, 7); - } - SECTION("erase contiguous mixed") { - VALIDATE_CHANGES(changes) { - lv->remove(5); - lv->remove(6); - lv->remove(5); - } - REQUIRE_INDICES(changes.deletions, 5, 6, 7); - } - SECTION("erase scattered forward") { - VALIDATE_CHANGES(changes) { - lv->remove(3); - lv->remove(4); - lv->remove(5); - } - REQUIRE_INDICES(changes.deletions, 3, 5, 7); - } - SECTION("erase scattered backwards") { - VALIDATE_CHANGES(changes) { - lv->remove(7); - lv->remove(5); - lv->remove(3); - } - REQUIRE_INDICES(changes.deletions, 3, 5, 7); - } - SECTION("erase scattered mixed") { - VALIDATE_CHANGES(changes) { - lv->remove(3); - lv->remove(6); - lv->remove(4); - } - REQUIRE_INDICES(changes.deletions, 3, 5, 7); - } - - SECTION("set single") { - VALIDATE_CHANGES(changes) { - lv->set(5, 0); - } - REQUIRE_INDICES(changes.modifications, 5); - } - SECTION("set contiguous") { - VALIDATE_CHANGES(changes) { - lv->set(5, 0); - lv->set(6, 0); - lv->set(7, 0); - } - REQUIRE_INDICES(changes.modifications, 5, 6, 7); - } - SECTION("set scattered") { - VALIDATE_CHANGES(changes) { - lv->set(5, 0); - lv->set(7, 0); - lv->set(9, 0); - } - REQUIRE_INDICES(changes.modifications, 5, 7, 9); - } - SECTION("set redundant") { - VALIDATE_CHANGES(changes) { - lv->set(5, 0); - lv->set(5, 0); - lv->set(5, 0); - } - REQUIRE_INDICES(changes.modifications, 5); - } - - SECTION("clear") { - VALIDATE_CHANGES(changes) { - lv->clear(); - } - REQUIRE_INDICES(changes.deletions, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9); - } - - SECTION("move backward") { - VALIDATE_CHANGES(changes) { - lv->move(5, 3); - } - REQUIRE_MOVES(changes, {5, 3}); - } - - SECTION("move forward") { - VALIDATE_CHANGES(changes) { - lv->move(1, 3); - } - REQUIRE_MOVES(changes, {1, 3}); - } - - SECTION("chained moves") { - VALIDATE_CHANGES(changes) { - lv->move(1, 3); - lv->move(3, 5); - } - REQUIRE_MOVES(changes, {1, 5}); - } - - SECTION("backwards chained moves") { - VALIDATE_CHANGES(changes) { - lv->move(5, 3); - lv->move(3, 1); - } - REQUIRE_MOVES(changes, {5, 1}); - } - - SECTION("moves shifting other moves") { - VALIDATE_CHANGES(changes) { - lv->move(1, 5); - lv->move(2, 7); - } - REQUIRE_MOVES(changes, {1, 4}, {3, 7}); - - VALIDATE_CHANGES(changes) { - lv->move(1, 5); - lv->move(7, 0); - } - REQUIRE_MOVES(changes, {1, 6}, {7, 0}); - } - - SECTION("move to current location is a no-op") { - VALIDATE_CHANGES(changes) { - lv->move(5, 5); - } - REQUIRE(changes.insertions.empty()); - REQUIRE(changes.deletions.empty()); - REQUIRE(changes.moves.empty()); - } - - SECTION("delete a target row") { - VALIDATE_CHANGES(changes) { - target->move_last_over(5); - } - REQUIRE_INDICES(changes.deletions, 5); - } - - SECTION("delete all target rows") { - VALIDATE_CHANGES(changes) { - lv->remove_all_target_rows(); - } - REQUIRE_INDICES(changes.deletions, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9); - } - - SECTION("clear target table") { - VALIDATE_CHANGES(changes) { - target->clear(); - } - REQUIRE_INDICES(changes.deletions, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9); - } - - SECTION("swap()") { - VALIDATE_CHANGES(changes) { - lv->swap(3, 5); - } - REQUIRE_INDICES(changes.modifications, 3, 5); - } - } - - SECTION("mixed change types") { - SECTION("set -> insert") { - VALIDATE_CHANGES(changes) { - lv->set(5, 0); - lv->insert(5, 0); - } - REQUIRE_INDICES(changes.insertions, 5); - REQUIRE_INDICES(changes.modifications, 6); - - VALIDATE_CHANGES(changes) { - lv->set(4, 0); - lv->insert(5, 0); - } - REQUIRE_INDICES(changes.insertions, 5); - REQUIRE_INDICES(changes.modifications, 4); - } - SECTION("insert -> set") { - VALIDATE_CHANGES(changes) { - lv->insert(5, 0); - lv->set(5, 1); - } - REQUIRE_INDICES(changes.insertions, 5); - REQUIRE_INDICES(changes.modifications, 5); - - VALIDATE_CHANGES(changes) { - lv->insert(5, 0); - lv->set(6, 1); - } - REQUIRE_INDICES(changes.insertions, 5); - REQUIRE_INDICES(changes.modifications, 6); - - VALIDATE_CHANGES(changes) { - lv->insert(6, 0); - lv->set(5, 1); - } - REQUIRE_INDICES(changes.insertions, 6); - REQUIRE_INDICES(changes.modifications, 5); - } - - SECTION("set -> erase") { - VALIDATE_CHANGES(changes) { - lv->set(5, 0); - lv->remove(5); - } - REQUIRE_INDICES(changes.deletions, 5); - REQUIRE(changes.modifications.empty()); - - VALIDATE_CHANGES(changes) { - lv->set(5, 0); - lv->remove(4); - } - REQUIRE_INDICES(changes.deletions, 4); - REQUIRE_INDICES(changes.modifications, 4); - - VALIDATE_CHANGES(changes) { - lv->set(5, 0); - lv->remove(4); - lv->remove(4); - } - REQUIRE_INDICES(changes.deletions, 4, 5); - REQUIRE(changes.modifications.empty()); - } - - SECTION("erase -> set") { - VALIDATE_CHANGES(changes) { - lv->remove(5); - lv->set(5, 0); - } - REQUIRE_INDICES(changes.deletions, 5); - REQUIRE_INDICES(changes.modifications, 5); - } - - SECTION("insert -> clear") { - VALIDATE_CHANGES(changes) { - lv->add(0); - lv->clear(); - } - REQUIRE_INDICES(changes.deletions, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9); - REQUIRE(changes.insertions.empty()); - } - - SECTION("set -> clear") { - VALIDATE_CHANGES(changes) { - lv->set(0, 5); - lv->clear(); - } - REQUIRE_INDICES(changes.deletions, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9); - REQUIRE(changes.modifications.empty()); - } - - SECTION("clear -> insert") { - VALIDATE_CHANGES(changes) { - lv->clear(); - lv->add(0); - } - REQUIRE_INDICES(changes.deletions, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9); - REQUIRE_INDICES(changes.insertions, 0); - } - - SECTION("insert -> delete") { - VALIDATE_CHANGES(changes) { - lv->add(0); - lv->remove(10); - } - REQUIRE(changes.insertions.empty()); - REQUIRE(changes.deletions.empty()); - - VALIDATE_CHANGES(changes) { - lv->add(0); - lv->remove(9); - } - REQUIRE_INDICES(changes.deletions, 9); - REQUIRE_INDICES(changes.insertions, 9); - - VALIDATE_CHANGES(changes) { - lv->insert(1, 1); - lv->insert(3, 3); - lv->insert(5, 5); - lv->remove(6); - lv->remove(4); - lv->remove(2); - } - REQUIRE_INDICES(changes.deletions, 1, 2, 3); - REQUIRE_INDICES(changes.insertions, 1, 2, 3); - - VALIDATE_CHANGES(changes) { - lv->insert(1, 1); - lv->insert(3, 3); - lv->insert(5, 5); - lv->remove(2); - lv->remove(3); - lv->remove(4); - } - REQUIRE_INDICES(changes.deletions, 1, 2, 3); - REQUIRE_INDICES(changes.insertions, 1, 2, 3); - } - - SECTION("delete -> insert") { - VALIDATE_CHANGES(changes) { - lv->remove(9); - lv->add(0); - } - REQUIRE_INDICES(changes.deletions, 9); - REQUIRE_INDICES(changes.insertions, 9); - } - - SECTION("interleaved delete and insert") { - VALIDATE_CHANGES(changes) { - lv->remove(9); - lv->remove(7); - lv->remove(5); - lv->remove(3); - lv->remove(1); - - lv->insert(4, 9); - lv->insert(3, 7); - lv->insert(2, 5); - lv->insert(1, 3); - lv->insert(0, 1); - - lv->remove(9); - lv->remove(7); - lv->remove(5); - lv->remove(3); - lv->remove(1); - } - - REQUIRE_INDICES(changes.deletions, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9); - REQUIRE_INDICES(changes.insertions, 0, 1, 2, 3, 4); - } - - SECTION("move after set is just insert+delete") { - VALIDATE_CHANGES(changes) { - lv->set(5, 6); - lv->move(5, 0); - } - - REQUIRE_INDICES(changes.deletions, 5); - REQUIRE_INDICES(changes.insertions, 0); - REQUIRE_MOVES(changes, {5, 0}); - } - - SECTION("set after move is just insert+delete") { - VALIDATE_CHANGES(changes) { - lv->move(5, 0); - lv->set(0, 6); - } - - REQUIRE_INDICES(changes.deletions, 5); - REQUIRE_INDICES(changes.insertions, 0); - REQUIRE_MOVES(changes, {5, 0}); - } - - SECTION("delete after move removes original row") { - VALIDATE_CHANGES(changes) { - lv->move(5, 0); - lv->remove(0); - } - - REQUIRE_INDICES(changes.deletions, 5); - REQUIRE(changes.moves.empty()); - } - - SECTION("moving newly inserted row just changes reported index of insert") { - VALIDATE_CHANGES(changes) { - lv->move(5, 0); - lv->remove(0); - } - - REQUIRE_INDICES(changes.deletions, 5); - REQUIRE(changes.moves.empty()); - } - - SECTION("moves shift insertions/changes like any other insertion") { - VALIDATE_CHANGES(changes) { - lv->insert(5, 5); - lv->set(6, 6); - lv->move(7, 4); - } - REQUIRE_INDICES(changes.deletions, 6); - REQUIRE_INDICES(changes.insertions, 4, 6); - REQUIRE_INDICES(changes.modifications, 7); - REQUIRE_MOVES(changes, {6, 4}); - } - - SECTION("clear after delete") { - VALIDATE_CHANGES(changes) { - lv->remove(5); - lv->clear(); - } - REQUIRE_INDICES(changes.deletions, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9); - } - - SECTION("erase before previous move target") { - VALIDATE_CHANGES(changes) { - lv->move(2, 8); - lv->remove(5); - } - REQUIRE_INDICES(changes.insertions, 7); - REQUIRE_INDICES(changes.deletions, 2, 6); - REQUIRE_MOVES(changes, {2, 7}); - } - - SECTION("insert after move updates move destination") { - VALIDATE_CHANGES(changes) { - lv->move(2, 8); - lv->insert(5, 5); - } - REQUIRE_MOVES(changes, {2, 9}); - } - } - - SECTION("deleting the linkview") { - SECTION("directly") { - VALIDATE_CHANGES(changes) { - origin->move_last_over(0); - } - REQUIRE(!lv->is_attached()); - REQUIRE(changes.insertions.empty()); - REQUIRE(changes.deletions.empty()); - REQUIRE(changes.modifications.empty()); - } - - SECTION("table clear") { - VALIDATE_CHANGES(changes) { - origin->clear(); - } - REQUIRE(!lv->is_attached()); - REQUIRE(changes.insertions.empty()); - REQUIRE(changes.deletions.empty()); - REQUIRE(changes.modifications.empty()); - } - - SECTION("delete a different lv") { - r->begin_transaction(); - origin->add_empty_row(); - r->commit_transaction(); - - VALIDATE_CHANGES(changes) { - origin->move_last_over(1); - } - REQUIRE(changes.insertions.empty()); - REQUIRE(changes.deletions.empty()); - REQUIRE(changes.modifications.empty()); - } - } - - SECTION("modifying a different linkview should not produce notifications") { - r->begin_transaction(); - origin->add_empty_row(); - LinkViewRef lv2 = origin->get_linklist(0, 1); - lv2->add(5); - r->commit_transaction(); - - VALIDATE_CHANGES(changes) { - lv2->add(1); - lv2->add(2); - lv2->remove(0); - lv2->set(0, 6); - lv2->move(1, 0); - lv2->swap(0, 1); - lv2->clear(); - lv2->add(1); - } - - REQUIRE(changes.insertions.empty()); - REQUIRE(changes.deletions.empty()); - REQUIRE(changes.modifications.empty()); - } - } -} - -TEST_CASE("DeepChangeChecker") { - InMemoryTestFile config; - config.automatic_change_notifications = false; - - config.schema = std::make_unique(Schema{ - {"table", "", { - {"int", PropertyType::Int}, - {"link", PropertyType::Object, "table", "", false, false, true}, - {"array", PropertyType::Array, "table"} - }}, - }); - - auto r = Realm::get_shared_realm(config); - auto table = r->read_group()->get_table("class_table"); - - r->begin_transaction(); - table->add_empty_row(10); - for (int i = 0; i < 10; ++i) - table->set_int(0, i, i); - r->commit_transaction(); - - auto track_changes = [&](auto&& f) { - auto history = make_client_history(config.path); - SharedGroup sg(*history, SharedGroup::durability_MemOnly); - Group const& g = sg.begin_read(); - - r->begin_transaction(); - f(); - r->commit_transaction(); - - _impl::TransactionChangeInfo info; - info.table_modifications_needed.resize(g.size(), true); - info.table_moves_needed.resize(g.size(), true); - _impl::transaction::advance(sg, info); - return info; - }; - - std::vector<_impl::DeepChangeChecker::RelatedTable> tables; - _impl::DeepChangeChecker::find_related_tables(tables, *table); - - SECTION("direct changes are tracked") { - auto info = track_changes([&] { - table->set_int(0, 9, 10); - }); - - _impl::DeepChangeChecker checker(info, *table, tables); - REQUIRE_FALSE(checker(8)); - REQUIRE(checker(9)); - } - - SECTION("changes over links are tracked") { - r->begin_transaction(); - for (int i = 0; i < 9; ++i) - table->set_link(1, i, i + 1); - r->commit_transaction(); - - auto info = track_changes([&] { - table->set_int(0, 9, 10); - }); - - REQUIRE(_impl::DeepChangeChecker(info, *table, tables)(0)); - } - - SECTION("changes over linklists are tracked") { - r->begin_transaction(); - for (int i = 0; i < 9; ++i) - table->get_linklist(2, i)->add(i + 1); - r->commit_transaction(); - - auto info = track_changes([&] { - table->set_int(0, 9, 10); - }); - - REQUIRE(_impl::DeepChangeChecker(info, *table, tables)(0)); - } - - SECTION("cycles over links do not loop forever") { - r->begin_transaction(); - table->set_link(1, 0, 0); - r->commit_transaction(); - - auto info = track_changes([&] { - table->set_int(0, 9, 10); - }); - REQUIRE_FALSE(_impl::DeepChangeChecker(info, *table, tables)(0)); - } - - SECTION("cycles over linklists do not loop forever") { - r->begin_transaction(); - table->get_linklist(2, 0)->add(0); - r->commit_transaction(); - - auto info = track_changes([&] { - table->set_int(0, 9, 10); - }); - REQUIRE_FALSE(_impl::DeepChangeChecker(info, *table, tables)(0)); - } - - SECTION("link chains are tracked up to 16 levels deep") { - r->begin_transaction(); - table->add_empty_row(10); - for (int i = 0; i < 19; ++i) - table->set_link(1, i, i + 1); - r->commit_transaction(); - - auto info = track_changes([&] { - table->set_int(0, 19, -1); - }); - - _impl::DeepChangeChecker checker(info, *table, tables); - CHECK(checker(19)); - CHECK(checker(18)); - CHECK(checker(4)); - CHECK_FALSE(checker(3)); - CHECK_FALSE(checker(2)); - - // Check in other orders to make sure that the caching doesn't effect - // the results - _impl::DeepChangeChecker checker2(info, *table, tables); - CHECK_FALSE(checker2(2)); - CHECK_FALSE(checker2(3)); - CHECK(checker2(4)); - CHECK(checker2(18)); - CHECK(checker2(19)); - - _impl::DeepChangeChecker checker3(info, *table, tables); - CHECK(checker2(4)); - CHECK_FALSE(checker2(3)); - CHECK_FALSE(checker2(2)); - CHECK(checker2(18)); - CHECK(checker2(19)); - } - - SECTION("targets moving is not a change") { - r->begin_transaction(); - table->set_link(1, 0, 9); - table->get_linklist(2, 0)->add(9); - r->commit_transaction(); - - auto info = track_changes([&] { - table->move_last_over(5); - }); - REQUIRE_FALSE(_impl::DeepChangeChecker(info, *table, tables)(0)); - } - - SECTION("changes made before a row is moved are reported") { - r->begin_transaction(); - table->set_link(1, 0, 9); - r->commit_transaction(); - - auto info = track_changes([&] { - table->set_int(0, 9, 5); - table->move_last_over(5); - }); - REQUIRE(_impl::DeepChangeChecker(info, *table, tables)(0)); - - r->begin_transaction(); - table->get_linklist(2, 0)->add(8); - r->commit_transaction(); - - info = track_changes([&] { - table->set_int(0, 8, 5); - table->move_last_over(5); - }); - REQUIRE(_impl::DeepChangeChecker(info, *table, tables)(0)); - } - - SECTION("changes made after a row is moved are reported") { - r->begin_transaction(); - table->set_link(1, 0, 9); - r->commit_transaction(); - - auto info = track_changes([&] { - table->move_last_over(5); - table->set_int(0, 5, 5); - }); - REQUIRE(_impl::DeepChangeChecker(info, *table, tables)(0)); - - r->begin_transaction(); - table->get_linklist(2, 0)->add(8); - r->commit_transaction(); - - info = track_changes([&] { - table->move_last_over(5); - table->set_int(0, 5, 5); - }); - REQUIRE(_impl::DeepChangeChecker(info, *table, tables)(0)); - } -} diff --git a/src/object-store/tests/util/index_helpers.hpp b/src/object-store/tests/util/index_helpers.hpp deleted file mode 100644 index bb5145b5..00000000 --- a/src/object-store/tests/util/index_helpers.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#define REQUIRE_INDICES(index_set, ...) do { \ - index_set.verify(); \ - std::initializer_list expected = {__VA_ARGS__}; \ - auto actual = index_set.as_indexes(); \ - INFO("Checking " #index_set); \ - REQUIRE(expected.size() == std::distance(actual.begin(), actual.end())); \ - auto begin = actual.begin(); \ - for (auto index : expected) { \ - REQUIRE(*begin++ == index); \ - } \ -} while (0) - -#define REQUIRE_MOVES(c, ...) do { \ - auto actual = (c); \ - std::initializer_list expected = {__VA_ARGS__}; \ - REQUIRE(expected.size() == actual.moves.size()); \ - auto begin = actual.moves.begin(); \ - for (auto move : expected) { \ - CHECK(begin->from == move.from); \ - CHECK(begin->to == move.to); \ - ++begin; \ - } \ -} while (0) diff --git a/src/object-store/tests/util/test_file.cpp b/src/object-store/tests/util/test_file.cpp deleted file mode 100644 index 8e63c4cb..00000000 --- a/src/object-store/tests/util/test_file.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#include "util/test_file.hpp" - -#include "impl/realm_coordinator.hpp" - -#include -#include - -#include -#include - -#if defined(__has_feature) && __has_feature(thread_sanitizer) -#include -#include -#include -#include -#endif - -TestFile::TestFile() -{ - static std::string tmpdir = [] { - realm::disable_sync_to_disk(); - - const char* dir = getenv("TMPDIR"); - if (dir && *dir) - return dir; - return "/tmp"; - }(); - path = tmpdir + "/realm.XXXXXX"; - mktemp(&path[0]); - unlink(path.c_str()); -} - -TestFile::~TestFile() -{ - unlink(path.c_str()); -} - -InMemoryTestFile::InMemoryTestFile() -{ - in_memory = true; -} - -#if defined(__has_feature) && __has_feature(thread_sanitizer) -// A helper which synchronously runs on_change() on a fixed background thread -// so that ThreadSanitizer can potentially detect issues -// This deliberately uses an unsafe spinlock for synchronization to ensure that -// the code being tested has to supply all required safety -static class TsanNotifyWorker { -public: - TsanNotifyWorker() - { - m_thread = std::thread([&] { work(); }); - } - - void work() - { - while (true) { - auto value = m_signal.load(std::memory_order_relaxed); - if (value == 0 || value == 1) - continue; - 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); - } - } - - ~TsanNotifyWorker() - { - m_signal = 2; - m_thread.join(); - } - - void on_change(const std::shared_ptr& c) - { - 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)); - realm.notify(); -} - -#else // __has_feature(thread_sanitizer) - -void advance_and_notify(realm::Realm& realm) -{ - realm::_impl::RealmCoordinator::get_existing_coordinator(realm.config().path)->on_change(); - realm.notify(); -} -#endif diff --git a/src/object-store/tests/util/test_file.hpp b/src/object-store/tests/util/test_file.hpp deleted file mode 100644 index 2f6a5135..00000000 --- a/src/object-store/tests/util/test_file.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef REALM_TEST_UTIL_TEST_FILE_HPP -#define REALM_TEST_UTIL_TEST_FILE_HPP - -#include "shared_realm.hpp" - -struct TestFile : realm::Realm::Config { - TestFile(); - ~TestFile(); -}; - -struct InMemoryTestFile : TestFile { - InMemoryTestFile(); -}; - -void advance_and_notify(realm::Realm& realm); - -#endif