diff options
author | marinofaggiana <marino@marinofaggiana.com> | 2020-08-17 16:01:21 +0300 |
---|---|---|
committer | marinofaggiana <marino@marinofaggiana.com> | 2020-08-17 16:01:21 +0300 |
commit | 172a1cd7e3010ff0fe802eac30a1472a81e95bc3 (patch) | |
tree | 7537a6e9e1314a1e553fd793921ea2fc2adcb75f /Carthage | |
parent | 8c84b6fba6cfb1d00703aadb18ea49a73b6b4ca0 (diff) |
coding
Diffstat (limited to 'Carthage')
110 files changed, 0 insertions, 33816 deletions
diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/.dockerignore b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/.dockerignore deleted file mode 120000 index 3e4e48b0b..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -.gitignore
\ No newline at end of file diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/.gitignore b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/.gitignore deleted file mode 100644 index 81a24da69..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/.gitignore +++ /dev/null @@ -1,32 +0,0 @@ -# CMake -.ninja_deps -.ninja_log -CMakeCache.txt -CMakeFiles/ -Makefile -build.ninja -cmake_install.cmake -rules.ninja -CMakeScripts/ -realm-object-store.xcodeproj/ - -# Build products -src/librealm-object-store.a -tests/tests -*.build/ -Debug/ -Release/ -RelWithDebInfo/ -MinSizeRel/ - -# IntelliJ -.idea/ - -# Visual Studio -/.vs - -# Linters -compile_commands.json - -# vim -*.swp diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/.gitmodules b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/.gitmodules deleted file mode 100644 index f667cb6e5..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "external/catch"] - path = external/catch - url = https://github.com/catchorg/Catch2 diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/CMake/CodeCoverage.cmake b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/CMake/CodeCoverage.cmake deleted file mode 100644 index e9de69d8f..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/CMake/CodeCoverage.cmake +++ /dev/null @@ -1,66 +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. -# -########################################################################### - -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 -DCATCH_CONFIG_FAST_COMPILE") -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 -o coverage.xml -f 'src/.*' -f "${CMAKE_SOURCE_DIR}/src.*" --exclude-directories "${CMAKE_BINARY_DIR}/tests" --exclude-directories="${CMAKE_SOURCE_DIR}/\.tmp" --exclude ".*realm\-core.*" --exclude ".*realm\-sync.*" - 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/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/CMake/CompilerFlags.cmake b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/CMake/CompilerFlags.cmake deleted file mode 100644 index b42604fb2..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/CMake/CompilerFlags.cmake +++ /dev/null @@ -1,127 +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(CheckSymbolExists) - -set(CMAKE_CXX_STANDARD 14) -set(CMAKE_CXX_STANDARD_REQUIRED on) -set(CMAKE_CXX_EXTENSIONS off) - -set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS - $<$<CONFIG:DEBUG>:REALM_DEBUG> - $<$<CONFIG:COVERAGE>:REALM_DEBUG> -) - -if(${CMAKE_CXX_COMPILER_ID} MATCHES "Clang" OR ${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU") - add_compile_options( - -Wall - -Wextra - -Wno-missing-field-initializers - -Wno-unevaluated-expression - -Wempty-body - -Wparentheses - -Wunknown-pragmas - -Wunreachable-code - -DREALM_HAVE_CONFIG - ) -endif() - -if(MSVC) - add_definitions( - /D_UNICODE - /DWIN32_LEAN_AND_MEAN - /D_CRT_SECURE_NO_WARNINGS - /D_SCL_SECURE_NO_WARNINGS - /D_ENABLE_EXTENDED_ALIGNED_STORAGE #https://developercommunity.visualstudio.com/comments/279328/view.html - ) - add_compile_options( - /MP # Enable multi-processor compilation - ) - if(NOT WINDOWS_STORE) - # Statically link the run-time library - # https://docs.microsoft.com/bg-bg/cpp/build/reference/md-mt-ld-use-run-time-library - # https://cmake.org/Wiki/CMake_FAQ#How_can_I_build_my_MSVC_application_with_a_static_runtime.3F - foreach(flag_var - CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE - CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) - if(${flag_var} MATCHES "/MD") - string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") - endif() - endforeach() - endif() -endif() - -if(${CMAKE_CXX_COMPILER_ID} MATCHES "Clang") - add_compile_options( - -Wassign-enum - -Wbool-conversion - -Wconditional-uninitialized - -Wconstant-conversion - -Wenum-conversion - -Wimplicit-fallthrough - -Wint-conversion - -Wmissing-prototypes - -Wnewline-eof - -Wshorten-64-to-32 - -Wthread-safety - -Wthread-safety-negative - ) -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() - -if(APPLE) - find_library(CF_LIBRARY CoreFoundation) - list(APPEND PLATFORM_LIBRARIES ${CF_LIBRARY}) -elseif(REALM_PLATFORM STREQUAL "Android") - find_library(ANDROID_LIBRARY android) - find_library(ANDROID_LOG_LIBRARY log) - list(APPEND PLATFORM_LIBRARIES ${ANDROID_LIBRARY}) - list(APPEND PLATFORM_LIBRARIES ${ANDROID_LOG_LIBRARY}) - set(PLATFORM_DEFINES "__STDC_CONSTANT_MACROS=1") -endif() - -if(NOT REALM_PLATFORM OR REALM_PLATFORM STREQUAL "Node") - find_library(UV_LIBRARY NAMES uv libuv) - if(UV_LIBRARY) - find_path(UV_INCLUDE_DIR uv.h) - - list(APPEND PLATFORM_LIBRARIES ${UV_LIBRARY}) - add_definitions(-DREALM_HAVE_UV) - endif() -endif() - -if(REALM_PLATFORM STREQUAL "Node") - set(PLATFORM_DEFINES "REALM_PLATFORM_NODE=1") - if(NOT UV_LIBRARY) - message(FATAL_ERROR "Platform set to Node but libuv was not found!") - endif() -endif() - -check_symbol_exists(epoll_create sys/epoll.h REALM_HAVE_EPOLL) -if(REALM_HAVE_EPOLL) - add_definitions(-DREALM_HAVE_EPOLL) -endif() diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/CMake/RealmCore.cmake b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/CMake/RealmCore.cmake deleted file mode 100644 index 1c440e340..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/CMake/RealmCore.cmake +++ /dev/null @@ -1,382 +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(ExternalProject) -include(ProcessorCount) - -find_package(Threads) - -# Load dependency info from dependencies.list into REALM_FOO_VERSION variables. -set(DEPENDENCIES_FILE "dependencies.list" CACHE STRING "path to dependencies list") -file(STRINGS ${DEPENDENCIES_FILE} DEPENDENCIES) -message("Dependencies: ${DEPENDENCIES}") -foreach(DEPENDENCY IN LISTS DEPENDENCIES) - string(REGEX MATCHALL "([^=]+)" COMPONENT_AND_VERSION ${DEPENDENCY}) - list(GET COMPONENT_AND_VERSION 0 COMPONENT) - list(GET COMPONENT_AND_VERSION 1 VERSION) - set(${COMPONENT} ${VERSION}) -endforeach() - - -if(APPLE) - find_library(FOUNDATION_FRAMEWORK Foundation) - find_library(SECURITY_FRAMEWORK Security) - - set(CRYPTO_LIBRARIES "") - set(SSL_LIBRARIES ${FOUNDATION_FRAMEWORK} ${SECURITY_FRAMEWORK}) -elseif(CMAKE_SYSTEM_NAME MATCHES "^Windows") - # Windows doesn't do crypto right now, but that is subject to change - set(CRYPTO_LIBRARIES "") - set(SSL_LIBRARIES "") -else() - if(NOT EXISTS ${CMAKE_BINARY_DIR}/openssl/lib/cmake/OpenSSL/OpenSSLConfig.cmake) - set(OPENSSL_URL "http://static.realm.io/downloads/openssl/${OPENSSL_VERSION}/Linux/x86_64/openssl.tgz") - if(REALM_PLATFORM STREQUAL "Android") - set(OPENSSL_URL "http://static.realm.io/downloads/openssl/${OPENSSL_VERSION}/Android/${ANDROID_ABI}/openssl.tgz") - endif() - - message(STATUS "Downloading OpenSSL...") - file(DOWNLOAD "${OPENSSL_URL}" "${CMAKE_BINARY_DIR}/openssl/openssl.tgz" STATUS download_status) - - list(GET download_status 0 status_code) - if (NOT "${status_code}" STREQUAL "0") - message(FATAL_ERROR "Downloading ${OPENSSL_URL}... Failed. Status: ${download_status}") - endif() - - message(STATUS "Uncompressing OpenSSL...") - execute_process( - COMMAND ${CMAKE_COMMAND} -E tar xfz "openssl.tgz" - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/openssl" - ) - message(STATUS "Importing OpenSSL...") - endif() - set(OpenSSL_DIR "${CMAKE_BINARY_DIR}/openssl/lib/cmake/OpenSSL") - find_package(OpenSSL REQUIRED CONFIG) - - set(CRYPTO_LIBRARIES OpenSSL::Crypto) - set(SSL_LIBRARIES OpenSSL::SSL) -endif() - -if (${CMAKE_VERSION} VERSION_GREATER "3.4.0") - set(USES_TERMINAL_BUILD USES_TERMINAL_BUILD 1) -endif() - -function(use_realm_core enable_sync core_prefix sync_prefix) - if(core_prefix) - build_existing_realm_core(${core_prefix}) - if(sync_prefix) - build_existing_realm_sync(${sync_prefix}) - endif() - elseif(enable_sync) - # FIXME: Support building against prebuilt sync binaries. - clone_and_build_realm_core("v${REALM_CORE_VERSION}") - clone_and_build_realm_sync("v${REALM_SYNC_VERSION}") - else() - if(APPLE OR REALM_PLATFORM STREQUAL "Android" OR CMAKE_SYSTEM_NAME MATCHES "^Windows") - download_realm_core(${REALM_CORE_VERSION}) - else() - clone_and_build_realm_core("v${REALM_CORE_VERSION}") - endif() - endif() -endfunction() - -function(download_realm_tarball url target libraries) - get_filename_component(tarball_name "${url}" NAME) - - set(tarball_parent_directory "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}") - set(tarball_path "${tarball_parent_directory}/${tarball_name}") - set(temp_tarball_path "/tmp/${tarball_name}") - - if (NOT EXISTS ${tarball_path}) - if (NOT EXISTS ${temp_tarball_path}) - message("Downloading ${url}.") - file(DOWNLOAD ${url} ${temp_tarball_path}.tmp SHOW_PROGRESS) - file(RENAME ${temp_tarball_path}.tmp ${temp_tarball_path}) - endif() - file(COPY ${temp_tarball_path} DESTINATION ${tarball_parent_directory}) - endif() - - if(APPLE) - add_custom_command( - COMMENT "Extracting ${tarball_name}" - OUTPUT ${libraries} - COMMAND ${CMAKE_COMMAND} -E tar xf ${tarball_path} - COMMAND ${CMAKE_COMMAND} -E remove_directory ${target} - COMMAND ${CMAKE_COMMAND} -E rename core ${target} - COMMAND ${CMAKE_COMMAND} -E touch_nocreate ${libraries}) - elseif(REALM_PLATFORM STREQUAL "Android" OR CMAKE_SYSTEM_NAME MATCHES "^Windows") - add_custom_command( - COMMENT "Extracting ${tarball_name}" - OUTPUT ${libraries} - COMMAND "${CMAKE_COMMAND}" -E make_directory "${target}" - COMMAND "${CMAKE_COMMAND}" -E chdir "${target}" "${CMAKE_COMMAND}" -E tar xf "${tarball_path}" - COMMAND "${CMAKE_COMMAND}" -E touch_nocreate ${libraries}) - endif() -endfunction() - -function(download_realm_core core_version) - if(CMAKE_SYSTEM_NAME MATCHES "Windows") - set(compression "tar.gz") - set(library_directory "lib") - - if(CMAKE_GENERATOR_PLATFORM MATCHES "^[Aa][Rr][Mm]$") - set(architecture "ARM") - elseif(CMAKE_GENERATOR_PLATFORM MATCHES "^[Xx]64$" OR CMAKE_SIZEOF_VOID_P EQUAL 8) - set(architecture "x64") - else() - set(architecture "Win32") - endif() - - if(CMAKE_SYSTEM_NAME STREQUAL "Windows") - set(platform "Windows") - elseif(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore") - set(platform "UWP") - endif() - - set(tarball_name_debug "realm-core-Debug-v${core_version}-${platform}-${architecture}-devel.tar.gz") - set(tarball_name_release "realm-core-Release-v${core_version}-${platform}-${architecture}-devel.tar.gz") - - set(url_debug "https://static.realm.io/downloads/core/${tarball_name_debug}") - set(url_release "https://static.realm.io/downloads/core/${tarball_name_release}") - - set(core_directory_parent "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}") - set(core_directory_debug "${core_directory_parent}/realm-core-${core_version}-debug") - set(core_directory_release "${core_directory_parent}/realm-core-${core_version}-release") - set(core_directory "${core_directory_debug}") - - set(core_library_debug "${core_directory_debug}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}realm-dbg${CMAKE_STATIC_LIBRARY_SUFFIX}") - set(core_library_release "${core_directory_release}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}realm${CMAKE_STATIC_LIBRARY_SUFFIX}") - set(core_parser_library_debug "${core_directory_debug}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}realm-parser-dbg${CMAKE_STATIC_LIBRARY_SUFFIX}") - set(core_parser_library_release "${core_directory_release}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}realm-parser${CMAKE_STATIC_LIBRARY_SUFFIX}") - set(core_libraries ${core_library_debug} ${core_library_release} ${core_parser_library_debug} ${core_parser_library_release}) - - download_realm_tarball(${url_debug} ${core_directory_debug} ${core_library_debug} ${core_parser_library_debug}) - download_realm_tarball(${url_release} ${core_directory_release} ${core_library_release} ${core_parser_library_release}) - else() - if(APPLE) - set(basename "realm-core-cocoa") - set(compression "tar.xz") - set(platform "-macosx") - elseif(REALM_PLATFORM STREQUAL "Android") - set(basename "realm-core-android") - set(compression "tar.gz") - set(platform "-android-${ANDROID_ABI}") - endif() - - set(tarball_name "${basename}-v${core_version}.${compression}") - set(url "https://static.realm.io/downloads/core/${tarball_name}") - set(core_directory_parent "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}") - set(core_directory "${core_directory_parent}/realm-core-${core_version}") - - set(core_library_debug ${core_directory}/${library_directory}/${CMAKE_STATIC_LIBRARY_PREFIX}realm${platform}-dbg${CMAKE_STATIC_LIBRARY_SUFFIX}) - set(core_library_release ${core_directory}/${library_directory}/${CMAKE_STATIC_LIBRARY_PREFIX}realm${platform}${CMAKE_STATIC_LIBRARY_SUFFIX}) - set(core_parser_library_debug ${core_directory}/${library_directory}/${CMAKE_STATIC_LIBRARY_PREFIX}realm-parser${platform}-dbg${CMAKE_STATIC_LIBRARY_SUFFIX}) - set(core_parser_library_release ${core_directory}/${library_directory}/${CMAKE_STATIC_LIBRARY_PREFIX}realm-parser${platform}${CMAKE_STATIC_LIBRARY_SUFFIX}) - set(core_libraries ${core_library_debug} ${core_library_release} ${core_parser_library_debug} ${core_parser_library_release}) - - download_realm_tarball(${url} ${core_directory} "${core_libraries}") - endif() - - 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_property(TARGET realm PROPERTY INTERFACE_LINK_LIBRARIES ${CRYPTO_LIBRARIES} Threads::Threads) - - # Create directories that are included in INTERFACE_INCLUDE_DIRECTORIES, as CMake requires they exist at - # configure time, when they'd otherwise not be created until we download and extract core. - file(MAKE_DIRECTORY ${core_directory}/include) - set_property(TARGET realm PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${core_directory}/include) - - add_library(realm-parser STATIC IMPORTED) - add_dependencies(realm-parser realm-core) - set_property(TARGET realm-parser PROPERTY IMPORTED_LOCATION_DEBUG ${core_parser_library_debug}) - set_property(TARGET realm-parser PROPERTY IMPORTED_LOCATION_COVERAGE ${core_parser_library_debug}) - set_property(TARGET realm-parser PROPERTY IMPORTED_LOCATION_RELEASE ${core_parser_library_release}) - set_property(TARGET realm-parser PROPERTY IMPORTED_LOCATION ${core_parser_library_release}) -endfunction() - -macro(build_realm_core) - set(core_prefix_directory "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/realm-core") - - ExternalProject_Add(realm-core - PREFIX ${core_prefix_directory} - BUILD_IN_SOURCE 1 - UPDATE_DISCONNECTED 1 - INSTALL_COMMAND "" - CONFIGURE_COMMAND cmake -B build.debug -DOpenSSL_DIR=${CMAKE_BINARY_DIR}/openssl/lib/cmake/OpenSSL -D CMAKE_BUILD_TYPE=Debug ${CORE_SANITIZER_FLAGS} -G Ninja - && cmake -B build.release -DOpenSSL_DIR=${CMAKE_BINARY_DIR}/openssl/lib/cmake/OpenSSL -D CMAKE_BUILD_TYPE=RelWithDebInfo ${CORE_SANITIZER_FLAGS} -G Ninja - - BUILD_COMMAND cmake --build build.debug --target Storage --target QueryParser - && cmake --build build.release --target Storage --target QueryParser - - ${USES_TERMINAL_BUILD} - ${ARGN} - ) - ExternalProject_Get_Property(realm-core SOURCE_DIR) - - set(core_debug_binary_dir "${SOURCE_DIR}/build.debug") - set(core_release_binary_dir "${SOURCE_DIR}/build.release") - set(core_library_debug "${core_debug_binary_dir}/src/realm/${CMAKE_STATIC_LIBRARY_PREFIX}realm-dbg${CMAKE_STATIC_LIBRARY_SUFFIX}") - set(core_library_release "${core_release_binary_dir}/src/realm/${CMAKE_STATIC_LIBRARY_PREFIX}realm${CMAKE_STATIC_LIBRARY_SUFFIX}") - set(core_parser_library_debug "${core_debug_binary_dir}/src/realm/parser/${CMAKE_STATIC_LIBRARY_PREFIX}realm-parser-dbg${CMAKE_STATIC_LIBRARY_SUFFIX}") - set(core_parser_library_release "${core_release_binary_dir}/src/realm/parser/${CMAKE_STATIC_LIBRARY_PREFIX}realm-parser${CMAKE_STATIC_LIBRARY_SUFFIX}") - - ExternalProject_Add_Step(realm-core ensure-libraries - DEPENDEES build - BYPRODUCTS ${core_library_debug} ${core_library_release} - ${core_parser_library_debug} ${core_parser_library_release} - ) - - set(core_generated_headers_dir_debug "${core_debug_binary_dir}/src") - set(core_generated_headers_dir_release "${core_release_binary_dir}/src") - - 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_property(TARGET realm PROPERTY INTERFACE_LINK_LIBRARIES ${CRYPTO_LIBRARIES} Threads::Threads) - - # Create directories that are included in INTERFACE_INCLUDE_DIRECTORIES, as CMake requires they exist at - # configure time, when they'd otherwise not be created until we download and build core. - file(MAKE_DIRECTORY "${core_generated_headers_dir_debug}" "${core_generated_headers_dir_release}" "${SOURCE_DIR}/src") - - set_property(TARGET realm PROPERTY INTERFACE_INCLUDE_DIRECTORIES - ${SOURCE_DIR}/src - $<$<CONFIG:Debug>:${core_generated_headers_dir_debug}> - $<$<NOT:$<CONFIG:Debug>>:${core_generated_headers_dir_release}> - ) - - add_library(realm-parser STATIC IMPORTED) - add_dependencies(realm realm-core) - set_property(TARGET realm-parser PROPERTY IMPORTED_LOCATION_DEBUG ${core_parser_library_debug}) - set_property(TARGET realm-parser PROPERTY IMPORTED_LOCATION_COVERAGE ${core_parser_library_debug}) - set_property(TARGET realm-parser PROPERTY IMPORTED_LOCATION_RELEASE ${core_parser_library_release}) - set_property(TARGET realm-parser PROPERTY IMPORTED_LOCATION ${core_parser_library_release}) -endmacro() - -function(clone_and_build_realm_core branch) - build_realm_core(GIT_REPOSITORY "https://github.com/realm/realm-core.git" - GIT_TAG ${branch} - ) -endfunction() - -function(build_existing_realm_core core_directory) - get_filename_component(core_directory ${core_directory} ABSOLUTE) - - build_realm_core(SOURCE_DIR ${core_directory} - URL "" - BUILD_ALWAYS 1 - ) -endfunction() - -macro(build_realm_sync) - set(sync_prefix_directory "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/realm-sync") - - ExternalProject_Get_Property(realm-core SOURCE_DIR) - set(core_directory ${SOURCE_DIR}) - - separate_arguments(sync_cfg_args UNIX_COMMAND "-DREALM_BUILD_DOGLESS=OFF ${CORE_SANITIZER_FLAGS} -G Ninja") - ExternalProject_Add(realm-sync-lib - DEPENDS realm-core - PREFIX ${sync_prefix_directory} - BUILD_IN_SOURCE 1 - UPDATE_DISCONNECTED 1 - INSTALL_COMMAND "" - CONFIGURE_COMMAND cmake -B build.debug -DCMAKE_BUILD_TYPE=Debug -DRealmCore_DIR=${core_directory}/build.debug ${sync_cfg_args} - && cmake -B build.release -DCMAKE_BUILD_TYPE=RelWithDebInfo -DRealmCore_DIR=${core_directory}/build.release ${sync_cfg_args} - BUILD_COMMAND cmake --build build.debug --target Sync --target SyncServer - && cmake --build build.release --target Sync --target SyncServer - ${USES_TERMINAL_BUILD} - ${ARGN} - ) - - ExternalProject_Get_Property(realm-sync-lib SOURCE_DIR) - - set(sync_debug_binary_dir "${SOURCE_DIR}/build.debug") - set(sync_release_binary_dir "${SOURCE_DIR}/build.release") - set(sync_library_debug "${sync_debug_binary_dir}/src/realm/${CMAKE_STATIC_LIBRARY_PREFIX}realm-sync-dbg${CMAKE_STATIC_LIBRARY_SUFFIX}") - set(sync_library_release "${sync_release_binary_dir}/src/realm/${CMAKE_STATIC_LIBRARY_PREFIX}realm-sync${CMAKE_STATIC_LIBRARY_SUFFIX}") - - ExternalProject_Add_Step(realm-sync-lib ensure-libraries - BYPRODUCTS ${sync_library_debug} ${sync_library_release} - DEPENDEES build - ) - - add_library(realm-sync STATIC IMPORTED) - add_dependencies(realm-sync realm-sync-lib) - - set_property(TARGET realm-sync PROPERTY IMPORTED_LOCATION_DEBUG ${sync_library_debug}) - set_property(TARGET realm-sync PROPERTY IMPORTED_LOCATION_COVERAGE ${sync_library_debug}) - set_property(TARGET realm-sync PROPERTY IMPORTED_LOCATION_RELEASE ${sync_library_release}) - set_property(TARGET realm-sync PROPERTY IMPORTED_LOCATION ${sync_library_release}) - - set_property(TARGET realm-sync PROPERTY INTERFACE_LINK_LIBRARIES ${SSL_LIBRARIES}) - - # Create directories that are included in INTERFACE_INCLUDE_DIRECTORIES, as CMake requires they exist at - # configure time, when they'd otherwise not be created until we download and build sync. - file(MAKE_DIRECTORY ${SOURCE_DIR}/src) - set_property(TARGET realm-sync PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${SOURCE_DIR}/src) - - # Sync server library is built as part of the sync library build - set(sync_server_library_debug "${sync_debug_binary_dir}/src/realm/${CMAKE_STATIC_LIBRARY_PREFIX}realm-server-dbg${CMAKE_STATIC_LIBRARY_SUFFIX}") - set(sync_server_library_release "${sync_release_binary_dir}/src/realm/${CMAKE_STATIC_LIBRARY_PREFIX}realm-server${CMAKE_STATIC_LIBRARY_SUFFIX}") - - ExternalProject_Add_Step(realm-sync-lib ensure-server-libraries - BYPRODUCTS ${sync_server_library_debug} ${sync_server_library_release} - DEPENDEES build - ) - - add_library(realm-sync-server STATIC IMPORTED) - add_dependencies(realm-sync-server realm-sync-lib) - - set_property(TARGET realm-sync-server PROPERTY IMPORTED_LOCATION_DEBUG ${sync_server_library_debug}) - set_property(TARGET realm-sync-server PROPERTY IMPORTED_LOCATION_COVERAGE ${sync_server_library_debug}) - set_property(TARGET realm-sync-server PROPERTY IMPORTED_LOCATION_RELEASE ${sync_server_library_release}) - set_property(TARGET realm-sync-server PROPERTY IMPORTED_LOCATION ${sync_server_library_release}) - - find_package(PkgConfig) - pkg_check_modules(YAML QUIET yaml-cpp) - set_property(TARGET realm-sync-server PROPERTY INTERFACE_LINK_LIBRARIES ${SSL_LIBRARIES} ${YAML_LDFLAGS}) -endmacro() - -function(build_existing_realm_sync sync_directory) - get_filename_component(sync_directory ${sync_directory} ABSOLUTE) - build_realm_sync(URL "" - SOURCE_DIR ${sync_directory} - BUILD_ALWAYS 1 - ) - -endfunction() - -function(clone_and_build_realm_sync branch) - set(cmake_files ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}) - - build_realm_sync(GIT_REPOSITORY "git@github.com:realm/realm-sync.git" - GIT_TAG ${branch} - CONFIGURE_COMMAND ${config_cmd} - ) - -endfunction() diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/CMake/Sanitizers.cmake b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/CMake/Sanitizers.cmake deleted file mode 100644 index 89cea81f3..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/CMake/Sanitizers.cmake +++ /dev/null @@ -1,41 +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. -# -########################################################################### - -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") - set(CORE_SANITIZER_FLAGS ${CORE_SANITIZER_FLAGS};-D;REALM_ASAN=ON) -endif() - -if(SANITIZE_THREAD) - set(SANITIZER_FLAGS "${SANITIZER_FLAGS} -fsanitize=thread") - set(CORE_SANITIZER_FLAGS ${CORE_SANITIZER_FLAGS};-D;REALM_TSAN=ON) -endif() - -if(SANITIZE_UNDEFINED) - set(SANITIZER_FLAGS "${SANITIZER_FLAGS} -fsanitize=undefined") - set(CORE_SANITIZER_FLAGS ${CORE_SANITIZER_FLAGS};-D;REALM_USAN=ON) -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/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/CMakeLists.txt b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/CMakeLists.txt deleted file mode 100644 index c1ccd4e03..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/CMakeLists.txt +++ /dev/null @@ -1,46 +0,0 @@ -cmake_minimum_required(VERSION 3.2.0) - -if(REALM_PLATFORM STREQUAL "Android") - # This must be before project() - set(CMAKE_TOOLCHAIN_FILE "${ANDROID_NDK}/build/cmake/android.toolchain.cmake") - set(ANDROID_ABI "x86" CACHE STRING "") - set(ANDROID_NATIVE_API_LEVEL "android-16" CACHE STRING "") -endif() - -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) - -# Sync is disabled unless -DREALM_ENABLE_SYNC=1 is specified when invoking CMake. -# FIXME: Flip the default once we can build against prebuilt sync binaries. -set(REALM_ENABLE_SYNC OFF CACHE BOOL "") - -if(REALM_SYNC_PREFIX AND NOT REALM_CORE_PREFIX) - message(FATAL_ERROR "REALM_CORE_PREFIX must be set when specifying REALM_SYNC_PREFIX.") -endif() -if(REALM_SYNC_PREFIX AND NOT REALM_ENABLE_SYNC) - message(FATAL_ERROR "REALM_ENABLE_SYNC must be set when specifying REALM_SYNC_PREFIX.") -endif() -if(REALM_CORE_PREFIX AND REALM_ENABLE_SYNC AND NOT REALM_SYNC_PREFIX) - message(FATAL_ERROR "REALM_SYNC_PREFIX must be set when specifying REALM_CORE_PREFIX when REALM_ENABLE_SYNC is set.") -endif() - -set(REALM_ENABLE_SERVER OFF CACHE BOOL "Enable the server-only functionality.") -if(REALM_ENABLE_SERVER AND NOT REALM_ENABLE_SYNC) - message(FATAL_ERROR "REALM_ENABLE_SERVER requires REALM_ENABLE_SYNC.") -endif() - -include(RealmCore) -use_realm_core("${REALM_ENABLE_SYNC}" "${REALM_CORE_PREFIX}" "${REALM_SYNC_PREFIX}") - -if(REALM_ENABLE_SYNC) - add_definitions(-DREALM_ENABLE_SYNC) -endif() - -add_subdirectory(src) -add_subdirectory(tests) diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/Dockerfile b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/Dockerfile deleted file mode 100644 index 8ce5bd041..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -FROM ubuntu:xenial - -RUN apt-get update && \ - apt-get install -y wget build-essential lcov curl cmake gcovr libprocps4-dev libssl-dev \ - git python-cheetah libuv1-dev ninja-build adb xutils-dev - -# Install the Android NDK -RUN mkdir -p /tmp/android-ndk && \ - cd /tmp/android-ndk && \ - wget -q http://dl.google.com/android/ndk/android-ndk-r10e-linux-x86_64.bin -O android-ndk.bin && \ - chmod a+x ./android-ndk.bin && sync && ./android-ndk.bin && \ - mv ./android-ndk-r10e /opt/android-ndk && \ - chmod -R a+rX /opt/android-ndk && \ - rm -rf /tmp/android-ndk - -ENV ANDROID_NDK_PATH /opt/android-ndk - -# Ensure a new enough version of CMake is available. -RUN cd /opt \ - && wget https://cmake.org/files/v3.15/cmake-3.15.2-Linux-x86_64.tar.gz \ - && tar zxvf cmake-3.15.2-Linux-x86_64.tar.gz - -ENV PATH "/opt/cmake-3.15.2-Linux-x86_64/bin:$PATH" diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/Jenkinsfile b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/Jenkinsfile deleted file mode 100644 index 1c74e9e3d..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/Jenkinsfile +++ /dev/null @@ -1,187 +0,0 @@ -#!groovy - -@Library('realm-ci') _ - -def getSourceArchive() { - deleteDir() - unstash 'source' -} - -def readGitTag() { - sh "git describe --exact-match --tags HEAD | tail -n 1 > tag.txt 2>&1 || true" - def tag = readFile('tag.txt').trim() - return tag -} - -def readGitSha() { - sh "git rev-parse HEAD | cut -b1-8 > sha.txt" - def sha = readFile('sha.txt').readLines().last().trim() - return sha -} - -def buildDockerEnv(name, dockerfile='Dockerfile', extra_args='') { - docker.withRegistry("https://${env.DOCKER_REGISTRY}", "ecr:eu-west-1:aws-ci-user") { - sh "sh ./workflow/docker_build_wrapper.sh $name . ${extra_args}" - } - return docker.image(name) -} - -def publishCoverageReport(String label) { - // Unfortunately, we cannot add a title or tag to individual coverage reports. - echo "Unstashing coverage-${label}" - unstash("coverage-${label}") - - step([ - $class: 'CoberturaPublisher', - autoUpdateHealth: false, - autoUpdateStability: false, - coberturaReportFile: "${label}.build/coverage.xml", - failNoReports: true, - failUnhealthy: false, - failUnstable: false, - maxNumberOfBuilds: 0, - onlyStable: false, - sourceEncoding: 'ASCII', - zoomCoverageChart: false - ]) -} - -if (env.BRANCH_NAME == 'master') { - env.DOCKER_PUSH = "1" -} - -def doDockerBuild(String flavor, Boolean enableSync, String sanitizerFlags = "") { - def sync = enableSync ? "sync" : "" - def label = "${flavor}${enableSync ? '-sync' : ''}" - - return { - node('docker') { - getSourceArchive() - def image = buildDockerEnv("ci/realm-object-store:${flavor}") - sshagent(['realm-ci-ssh']) { - image.inside("-v /etc/passwd:/etc/passwd:ro -v ${env.HOME}:${env.HOME} -v ${env.SSH_AUTH_SOCK}:${env.SSH_AUTH_SOCK} -e HOME=${env.HOME}") { - sh "./workflow/build.sh ${flavor} ${sync} ${sanitizerFlags}" - } - } - } - } -} - -def doAndroidDockerBuild() { - return { - node('docker') { - getSourceArchive() - wrap([$class: 'AnsiColorBuildWrapper']) { - def image = docker.build('realm-object-store:ndk21', '-f android.Dockerfile .') - docker.image('tracer0tong/android-emulator').withRun { emulator -> - image.inside("--link ${emulator.id}:emulator") { - sh """ - cmake -B build -DREALM_PLATFORM=Android -DANDROID_NDK=\${ANDROID_NDK} -GNinja -DCMAKE_MAKE_PROGRAM=ninja - cmake --build build - adb connect emulator - timeout 10m adb wait-for-device - adb push build/tests/tests /data/local/tmp - adb shell '/data/local/tmp/tests || echo __ADB_FAIL__' | tee adb.log - ! grep __ADB_FAIL__ adb.log - """ - } - } - } - } - } -} - -def doBuild(String nodeSpec, String flavor, Boolean enableSync, String version) { - def sync = enableSync ? "sync" : "false" - def label = "${flavor}${enableSync ? '-sync' : ''}" - return { - node(nodeSpec) { - getSourceArchive() - sshagent(['realm-ci-ssh']) { - sh "./workflow/test_coverage.sh ${sync} ${version} && mv coverage.build ${label}.build" - } - echo "Stashing coverage-${label}" - stash includes: "${label}.build/coverage.xml", name: "coverage-${label}" - } - } -} - -def doWindowsBuild() { - return { - node('windows') { - getSourceArchive() - - bat """ - "${tool 'cmake'}" . -DCMAKE_SYSTEM_VERSION="8.1" - "${tool 'cmake'}" --build . --config Release - tests\\Release\\tests.exe - """ - } - } -} - -def doWindowsUniversalBuild() { - return { - node('windows') { - getSourceArchive() - - bat """ - "${tool 'cmake'}" . -DCMAKE_SYSTEM_NAME="WindowsStore" -DCMAKE_SYSTEM_VERSION="10.0" - "${tool 'cmake'}" --build . --config Release --target realm-object-store - """ - } - } -} - -def setBuildName(newBuildName) { - currentBuild.displayName = "${currentBuild.displayName} - ${newBuildName}" -} - -stage('prepare') { - node('docker') { - checkout scm - sh 'git clean -ffdx -e .????????' - sshagent(['realm-ci-ssh']) { - sh 'git submodule update --init --recursive' - } - - gitTag = readGitTag() - gitSha = readGitSha() - echo "tag: ${gitTag}" - if (gitTag == "") { - echo "No tag given for this build" - setBuildName("${gitSha}") - } else { - echo "Building release: '${gitTag}'" - setBuildName("Tag ${gitTag}") - } - - stash includes: '**', name: 'source' - } -} - -stage('unit-tests') { - parallel( - linux: doDockerBuild('linux', false), - linux_sync: doDockerBuild('linux', true), - linux_asan: doDockerBuild('linux', true, '-DSANITIZE_ADDRESS=1'), - linux_tsan: doDockerBuild('linux', true, '-DSANITIZE_THREAD=1'), - android: doAndroidDockerBuild(), - macos: doBuild('osx', 'macOS', false, ''), - macos_sync: doBuild('osx', 'macOS', true, ''), - win32: doWindowsBuild(), - windows_universal: doWindowsUniversalBuild() - ) - currentBuild.result = 'SUCCESS' -} - -stage('publish') { - node('docker') { - // we need sources to allow the coverage report to display them - rlmCheckout(scm) - // coverage reports assume sources are in the parent directory - dir("build") { - publishCoverageReport('macOS-sync') - } - } -} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/LICENSE b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/LICENSE deleted file mode 100644 index 843725a69..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/LICENSE +++ /dev/null @@ -1,269 +0,0 @@ -TABLE OF CONTENTS - -1. Apache License version 2.0 -2. Realm Components -3. Export Compliance - -------------------------------------------------------------------------------- - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - 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. - - -REALM COMPONENTS - -This software contains components with separate copyright and license terms. -Your use of these components is subject to the terms and conditions of the -following licenses. - -For the Realm Core component - - Realm Core Binary License - - Copyright (c) 2011-2016 Realm Inc All rights reserved - - Redistribution and use in binary form, with or without modification, is - permitted provided that the following conditions are met: - - 1. You agree not to attempt to decompile, disassemble, reverse engineer or - otherwise discover the source code from which the binary code was derived. - You may, however, access and obtain a separate license for most of the - source code from which this Software was created, at - http://realm.io/pricing/. - - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from this - software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. - -EXPORT COMPLIANCE - -You understand that the Software may contain cryptographic functions that may be -subject to export restrictions, and you represent and warrant that you are not -located in a country that is subject to United States export restriction or embargo, -including Cuba, Iran, North Korea, Sudan, Syria or the Crimea region, and that you -are not on the Department of Commerce list of Denied Persons, Unverified Parties, -or affiliated with a Restricted Entity. - -You agree to comply with all export, re-export and import restrictions and -regulations of the Department of Commerce or other agency or authority of the -United States or other applicable countries. You also agree not to transfer, or -authorize the transfer of, directly or indirectly, the Software to any prohibited -country, including Cuba, Iran, North Korea, Sudan, Syria or the Crimea region, -or to any person or organization on or affiliated with the Department of -Commerce lists of Denied Persons, Unverified Parties or Restricted Entities, or -otherwise in violation of any such restrictions or regulations. diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/dependencies.list b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/dependencies.list deleted file mode 100644 index 9710b0a6d..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/dependencies.list +++ /dev/null @@ -1,4 +0,0 @@ -REALM_CORE_VERSION=6.0.10 -REALM_SYNC_VERSION=5.0.9 -REALM_CORE_PACKAGING=2 -OPENSSL_VERSION=1.1.1b diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/CMakeLists.txt b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/CMakeLists.txt deleted file mode 100644 index 30d703995..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/CMakeLists.txt +++ /dev/null @@ -1,142 +0,0 @@ -set(SOURCES - binding_callback_thread_observer.cpp - collection_notifications.cpp - index_set.cpp - list.cpp - object.cpp - object_changeset.cpp - object_schema.cpp - object_store.cpp - results.cpp - schema.cpp - shared_realm.cpp - thread_safe_reference.cpp - - impl/collection_change_builder.cpp - impl/collection_notifier.cpp - impl/list_notifier.cpp - impl/object_notifier.cpp - impl/realm_coordinator.cpp - impl/results_notifier.cpp - impl/transact_log_handler.cpp - impl/weak_realm_notifier.cpp - util/scheduler.cpp - util/uuid.cpp) - -set(HEADERS - binding_callback_thread_observer.hpp - collection_notifications.hpp - feature_checks.hpp - index_set.hpp - keypath_helpers.hpp - list.hpp - object.hpp - object_accessor.hpp - object_changeset.hpp - object_schema.hpp - object_store.hpp - property.hpp - results.hpp - schema.hpp - shared_realm.hpp - thread_safe_reference.hpp - - impl/apple/external_commit_helper.hpp - impl/apple/keychain_helper.hpp - impl/epoll/external_commit_helper.hpp - impl/generic/external_commit_helper.hpp - - impl/collection_change_builder.hpp - impl/collection_notifier.hpp - impl/external_commit_helper.hpp - impl/list_notifier.hpp - impl/notification_wrapper.hpp - impl/object_accessor_impl.hpp - impl/object_notifier.hpp - impl/realm_coordinator.hpp - impl/results_notifier.hpp - impl/transact_log_handler.hpp - impl/weak_realm_notifier.hpp - - util/android/scheduler.hpp - util/apple/scheduler.hpp - util/generic/scheduler.hpp - util/uv/scheduler.hpp - - util/aligned_union.hpp - util/atomic_shared_ptr.hpp - util/checked_mutex.hpp - util/copyable_atomic.hpp - util/event_loop_dispatcher.hpp - util/scheduler.hpp - util/tagged_bool.hpp - util/uuid.hpp) - -if(APPLE) - list(APPEND SOURCES impl/apple/external_commit_helper.cpp impl/apple/keychain_helper.cpp) -elseif(REALM_HAVE_EPOLL) - list(APPEND SOURCES impl/epoll/external_commit_helper.cpp) -elseif(CMAKE_SYSTEM_NAME MATCHES "^Windows") - list(APPEND SOURCES impl/windows/external_commit_helper.cpp) -else() - list(APPEND SOURCES impl/generic/external_commit_helper.cpp) -endif() - -set(INCLUDE_DIRS - ${UV_INCLUDE_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}) - -if(REALM_ENABLE_SYNC) - list(APPEND HEADERS - sync/async_open_task.hpp - sync/partial_sync.hpp - sync/subscription_state.hpp - sync/sync_config.hpp - sync/sync_manager.hpp - sync/sync_session.hpp - sync/sync_user.hpp - sync/impl/sync_client.hpp - sync/impl/sync_file.hpp - sync/impl/sync_metadata.hpp - sync/impl/work_queue.hpp) - list(APPEND SOURCES - sync/async_open_task.cpp - sync/partial_sync.cpp - sync/sync_config.cpp - sync/sync_manager.cpp - sync/sync_session.cpp - sync/sync_user.cpp - sync/impl/sync_file.cpp - sync/impl/sync_metadata.cpp - sync/impl/work_queue.cpp) - if(APPLE) - list(APPEND SOURCES - sync/impl/apple/network_reachability_observer.cpp - sync/impl/apple/system_configuration.cpp) - endif() - find_package(ZLIB REQUIRED) - list(APPEND INCLUDE_DIRS ${ZLIB_INCLUDE_DIRS}) -endif() - -if(REALM_ENABLE_SERVER) - list(APPEND HEADERS - server/adapter.hpp - server/admin_realm.hpp - server/global_notifier.hpp) - list(APPEND SOURCES - server/adapter.cpp - server/admin_realm.cpp - server/global_notifier.cpp) - list(APPEND INCLUDE_DIRS ../external/json) -endif() - -add_library(realm-object-store STATIC ${SOURCES} ${HEADERS}) -set_target_properties(realm-object-store PROPERTIES POSITION_INDEPENDENT_CODE 1) -target_compile_definitions(realm-object-store PRIVATE ${PLATFORM_DEFINES}) -target_include_directories(realm-object-store PUBLIC ${INCLUDE_DIRS}) -target_link_libraries(realm-object-store PUBLIC realm ${PLATFORM_LIBRARIES}) - -if(REALM_ENABLE_SYNC) - # Add the sync libraries separately to reduce merge conflicts. - target_link_libraries(realm-object-store PUBLIC realm-sync ${ZLIB_LIBRARIES}) -endif() diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/binding_callback_thread_observer.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/binding_callback_thread_observer.cpp deleted file mode 100644 index 0c388d068..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/binding_callback_thread_observer.cpp +++ /dev/null @@ -1,23 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 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 "binding_callback_thread_observer.hpp" - -namespace realm { -BindingCallbackThreadObserver* g_binding_callback_thread_observer = nullptr; -} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/binding_callback_thread_observer.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/binding_callback_thread_observer.hpp deleted file mode 100644 index e956349b2..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/binding_callback_thread_observer.hpp +++ /dev/null @@ -1,42 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 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_OS_BINDING_CALLBACK_THREAD_OBSERVER_HPP -#define REALM_OS_BINDING_CALLBACK_THREAD_OBSERVER_HPP - -#include <exception> - -namespace realm { -// Interface for bindings interested in registering callbacks before/after the ObjectStore thread runs. -// This is for example helpful to attach/detach the pthread to the JavaVM in order to be able to perform JNI calls. -class BindingCallbackThreadObserver { -public: - // This method is called just before the thread is started - virtual void did_create_thread() = 0; - - // This method is called just before the thread is being destroyed - virtual void will_destroy_thread() = 0; - - // This method is called with any exception throws by client.run(). - virtual void handle_error(std::exception const& e) = 0; -}; - -extern BindingCallbackThreadObserver* g_binding_callback_thread_observer; -} - -#endif // REALM_OS_BINDING_CALLBACK_THREAD_OBSERVER_HPP diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/binding_context.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/binding_context.hpp deleted file mode 100644 index de83966b1..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/binding_context.hpp +++ /dev/null @@ -1,180 +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 <realm/keys.hpp> - -#include <memory> -#include <tuple> -#include <unordered_map> -#include <vector> - -namespace realm { -// BindingContext is the extension point for adding binding-specific behavior to -// a SharedRealm. It can be used to store additional 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<std::function<void ()>>::iterator { -// token(std::list<std::function<void ()>>::iterator it) : std::list<std::function<void ()>>::iterator(it) { } -// friend class DelegateImplementation; -// }; -// -// token add_notification(std::function<void ()> 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<ObserverState> const&, std::vector<void*> const&, bool) 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<std::function<void ()>> m_registered_notifications; -// }; -class Realm; -class Schema; -class BindingContext { -public: - virtual ~BindingContext() = default; - - std::weak_ptr<Realm> realm; - - // Called when the Realm is about to send notifications about Realm, - // Collection or Object changes. This method will be called even if - // no notification callbacks have been registered. - virtual void will_send_notifications() { } - - // Called when the Realm is done sending all change notifications. This method - // will be called even if no notification callbacks have been registered. - virtual void did_send_notifications() { } - - // Called by the Realm when refresh called or a notification arrives which - // is triggered through write transaction committed by itself or a different - // Realm instance. - virtual void before_notify() { } - - // 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<ObserverState> 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<ObserverState> const& observers, - std::vector<void*> 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 transaction, although - // both vectors will be empty in that case. - virtual void did_change(std::vector<ObserverState> const& observers, - std::vector<void*> const& invalidated, - bool version_changed=true); - - // Called immediately after the corresponding Realm's schema is changed through - // update_schema()/set_schema_subset() or the schema is changed by another Realm - // instance. The parameter is a schema reference which is the same as the return - // value of Realm::schema(). - virtual void schema_did_change(Schema const&) {} - - // Change information for a single field of a row - struct ColumnInfo { - // What kind of change occurred? - // Always Set or None for everything but LinkList columns. - enum class Kind { - None, // No change - Set, // The value or 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 on - // LinkList columns. Not used for other types or for None or 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 { - // Table and row which is observed - realm::TableKey table_key; - int64_t obj_key; - - // 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::unordered_map<int64_t, ColumnInfo> changes; - - // Simple lexographic ordering - friend bool operator<(ObserverState const& lft, ObserverState const& rgt) - { - return std::tie(lft.table_key, lft.obj_key) < std::tie(rgt.table_key, rgt.obj_key); - } - }; -}; - -inline void BindingContext::will_change(std::vector<ObserverState> const&, std::vector<void*> const&) { } -inline void BindingContext::did_change(std::vector<ObserverState> const&, std::vector<void*> const&, bool) { } -} // namespace realm - -#endif /* BINDING_CONTEXT_HPP */ diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/collection_notifications.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/collection_notifications.cpp deleted file mode 100644 index 03fad73f1..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/collection_notifications.cpp +++ /dev/null @@ -1,61 +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, uint64_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; -} - -void NotificationToken::suppress_next() -{ - m_notifier.load()->suppress_next_notification(m_token); -} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/collection_notifications.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/collection_notifications.hpp deleted file mode 100644 index 3b4dca13f..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/collection_notifications.hpp +++ /dev/null @@ -1,183 +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 <exception> -#include <memory> -#include <type_traits> -#include <unordered_map> -#include <vector> - -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, uint64_t token); - ~NotificationToken(); - - NotificationToken(NotificationToken&&); - NotificationToken& operator=(NotificationToken&&); - - NotificationToken(NotificationToken const&) = delete; - NotificationToken& operator=(NotificationToken const&) = delete; - - void suppress_next(); - -private: - util::AtomicSharedPtr<_impl::CollectionNotifier> m_notifier; - uint64_t m_token; -}; - -struct CollectionChangeSet { - struct Move { - size_t from; - size_t to; - - bool operator==(Move m) const noexcept { return from == m.from && to == m.to; } - }; - - // Indices which were removed from the _old_ collection - IndexSet deletions; - - // Indices in the _new_ collection which are new insertions - IndexSet insertions; - - // Indices of objects in the _old_ collection which were modified - IndexSet modifications; - - // Indices in the _new_ collection which were modified. This will always - // have the same number of indices as `modifications` and conceptually - // represents the same entries, just in different versions of the collection. - // It exists for the sake of code which finds it easier to process - // modifications after processing deletions and insertions rather than before. - IndexSet modifications_new; - - // Rows in the collection which moved. - // - // Every `from` index will also be present in `deletions` and every `to` - // index will be present in `insertions`. - // - // This is currently not reliably calculated for all types of collections. A - // reported move will always actually be a move, but there may also be - // unreported moves which show up only as a delete/insert pair. - std::vector<Move> moves; - - // Per-column version of `modifications` - std::unordered_map<int64_t, IndexSet> columns; - - bool empty() const noexcept - { - return deletions.empty() && insertions.empty() && modifications.empty() - && modifications_new.empty() && moves.empty(); - } -}; - -// A type-erasing wrapper for the callback for collection notifications. Can be -// constructed with either any callable compatible with the signature -// `void (CollectionChangeSet, std::exception_ptr)`, an object with member -// functions `void before(CollectionChangeSet)`, `void after(CollectionChangeSet)`, -// `void error(std::exception_ptr)`, or a pointer to such an object. If a pointer -// is given, the caller is responsible for ensuring that the pointed-to object -// outlives the collection. -class CollectionChangeCallback { -public: - CollectionChangeCallback(std::nullptr_t={}) { } - - template<typename Callback> - CollectionChangeCallback(Callback cb) : m_impl(make_impl(std::move(cb))) { } - template<typename Callback> - CollectionChangeCallback& operator=(Callback cb) { m_impl = make_impl(std::move(cb)); return *this; } - - // Explicitly default the copy/move constructors as otherwise they'll use - // the above ones and add an extra layer of wrapping - CollectionChangeCallback(CollectionChangeCallback&&) = default; - CollectionChangeCallback(CollectionChangeCallback const&) = default; - CollectionChangeCallback& operator=(CollectionChangeCallback&&) = default; - CollectionChangeCallback& operator=(CollectionChangeCallback const&) = default; - - void before(CollectionChangeSet const& c) { m_impl->before(c); } - void after(CollectionChangeSet const& c) { m_impl->after(c); } - void error(std::exception_ptr e) { m_impl->error(e); } - - explicit operator bool() const { return !!m_impl; } - -private: - struct Base { - virtual ~Base() {} - virtual void before(CollectionChangeSet const&)=0; - virtual void after(CollectionChangeSet const&)=0; - virtual void error(std::exception_ptr)=0; - }; - - template<typename Callback, typename = decltype(std::declval<Callback>()(CollectionChangeSet(), std::exception_ptr()))> - std::shared_ptr<Base> make_impl(Callback cb) - { - return std::make_shared<Impl<Callback>>(std::move(cb)); - } - - template<typename Callback, typename = decltype(std::declval<Callback>().after(CollectionChangeSet())), typename = void> - std::shared_ptr<Base> make_impl(Callback cb) - { - return std::make_shared<Impl2<Callback>>(std::move(cb)); - } - - template<typename Callback, typename = decltype(std::declval<Callback>().after(CollectionChangeSet())), typename = void> - std::shared_ptr<Base> make_impl(Callback* cb) - { - return std::make_shared<Impl3<Callback>>(cb); - } - - template<typename T> - struct Impl : public Base { - T impl; - Impl(T impl) : impl(std::move(impl)) { } - void before(CollectionChangeSet const&) override { } - void after(CollectionChangeSet const& change) override { impl(change, {}); } - void error(std::exception_ptr error) override { impl({}, error); } - }; - template<typename T> - struct Impl2 : public Base { - T impl; - Impl2(T impl) : impl(std::move(impl)) { } - void before(CollectionChangeSet const& c) override { impl.before(c); } - void after(CollectionChangeSet const& c) override { impl.after(c); } - void error(std::exception_ptr error) override { impl.error(error); } - }; - template<typename T> - struct Impl3 : public Base { - T* impl; - Impl3(T* impl) : impl(impl) { } - void before(CollectionChangeSet const& c) override { impl->before(c); } - void after(CollectionChangeSet const& c) override { impl->after(c); } - void error(std::exception_ptr error) override { impl->error(error); } - }; - - std::shared_ptr<Base> m_impl; -}; -} // namespace realm - -#endif // REALM_COLLECTION_NOTIFICATIONS_HPP diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/feature_checks.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/feature_checks.hpp deleted file mode 100644 index a3fd8a51c..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/feature_checks.hpp +++ /dev/null @@ -1,30 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 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_OS_FEATURE_CHECKS_HPP -#define REALM_OS_FEATURE_CHECKS_HPP - -#include <realm/version.hpp> - -#if REALM_ENABLE_SYNC - -#include <realm/sync/version.hpp> - -#endif // REALM_ENABLE_SYNC - -#endif // REALM_OS_FEATURE_CHECKS_HPP diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/apple/external_commit_helper.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/apple/external_commit_helper.cpp deleted file mode 100644 index 1c4dab027..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/apple/external_commit_helper.cpp +++ /dev/null @@ -1,243 +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 <realm/db_options.hpp> -#include <realm/util/fifo_helper.hpp> - -#include <asl.h> -#include <assert.h> -#include <fcntl.h> -#include <sstream> -#include <sys/event.h> -#include <sys/stat.h> -#include <sys/time.h> -#include <system_error> -#include <unistd.h> - -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 - - - // Object Store needs to create a named pipe in order to coordinate notifications. - // This can be a problem on some file systems (e.g. FAT32) or due to security policies in SELinux. Most commonly - // it is a problem when saving Realms on external storage: https://stackoverflow.com/questions/2740321/how-to-create-named-pipe-mkfifo-in-android - // - // For this reason we attempt to create this file in a temporary location known to be safe to write these files. - // - // In order of priority we attempt to write the file in the following locations: - // 1) Next to the Realm file itself - // 2) A location defined by `Realm::Config::fifo_files_fallback_path` - // 3) A location defined by `DBOptions::set_sys_tmp_dir()` - // - // Core has a similar policy for its named pipes. - // - // Also see https://github.com/realm/realm-java/issues/3140 - // Note that hash collisions are okay here because they just result in doing extra work instead of resulting - // in correctness problems. - - std::string path; - std::string temp_dir = util::normalize_dir(parent.get_config().fifo_files_fallback_path); - std::string sys_temp_dir = util::normalize_dir(DBOptions::get_sys_tmp_dir()); - - path = parent.get_path() + ".note"; - bool fifo_created = realm::util::try_create_fifo(path); - if (!fifo_created && !temp_dir.empty()) { - path = util::format("%1realm_%2.note", temp_dir, std::hash<std::string>()(parent.get_path())); - fifo_created = realm::util::try_create_fifo(path); - } - if (!fifo_created && !sys_temp_dir.empty()) { - path = util::format("%1realm_%2.note", sys_temp_dir, std::hash<std::string>()(parent.get_path())); - realm::util::create_fifo(path); - } - - 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 - int 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::thread([=] { - try { - listen(); - } -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - 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; - } -#pragma clang diagnostic pop - }); -} - -ExternalCommitHelper::~ExternalCommitHelper() -{ - notify_fd(m_shutdown_write_fd, m_shutdown_read_fd); - m_thread.join(); // Wait for the thread to exit -} - -void ExternalCommitHelper::listen() -{ - pthread_setname_np("Realm 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); - if (ret == 0 || (ret < 0 && errno == EINTR)) { - // Spurious wakeup; just wait again - continue; - } - assert(ret > 0); - - // 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/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/apple/external_commit_helper.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/apple/external_commit_helper.hpp deleted file mode 100644 index 1fc99ed7a..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/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 <thread> - -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::thread 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/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/apple/keychain_helper.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/apple/keychain_helper.cpp deleted file mode 100644 index 92b5b1ea5..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/apple/keychain_helper.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/apple/keychain_helper.hpp" - - -#include <realm/util/cf_ptr.hpp> -#include <realm/util/optional.hpp> - -#include <Security/Security.h> - -#include <string> - -using realm::util::CFPtr; -using realm::util::adoptCF; -using realm::util::retainCF; - -namespace realm { -namespace keychain { - -KeychainAccessException::KeychainAccessException(int32_t error_code) -: std::runtime_error(util::format("Keychain returned unexpected status code: %1", error_code)) { } - -namespace { - -constexpr size_t key_size = 64; - -#if !TARGET_IPHONE_SIMULATOR -CFPtr<CFStringRef> convert_string(const std::string& string) -{ - auto result = adoptCF(CFStringCreateWithBytes(nullptr, reinterpret_cast<const UInt8*>(string.data()), - string.size(), kCFStringEncodingASCII, false)); - if (!result) { - throw std::bad_alloc(); - } - return result; -} -#endif - -CFPtr<CFMutableDictionaryRef> build_search_dictionary(CFStringRef account, CFStringRef service, - __unused util::Optional<std::string> group) -{ - auto d = adoptCF(CFDictionaryCreateMutable(nullptr, 0, &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks)); - if (!d) - throw std::bad_alloc(); - - CFDictionaryAddValue(d.get(), kSecClass, kSecClassGenericPassword); - CFDictionaryAddValue(d.get(), kSecReturnData, kCFBooleanTrue); - CFDictionaryAddValue(d.get(), kSecAttrAccount, account); - CFDictionaryAddValue(d.get(), kSecAttrService, service); -#if !TARGET_IPHONE_SIMULATOR - if (group) - CFDictionaryAddValue(d.get(), kSecAttrAccessGroup, convert_string(*group).get()); -#endif - return d; -} - -/// Get the encryption key for a given service, returning it only if it exists. -util::Optional<std::vector<char>> get_key(CFStringRef account, CFStringRef service) -{ - auto search_dictionary = build_search_dictionary(account, service, none); - CFDataRef retained_key_data; - if (OSStatus status = SecItemCopyMatching(search_dictionary.get(), (CFTypeRef *)&retained_key_data)) { - if (status != errSecItemNotFound) - throw KeychainAccessException(status); - - // Key was not found. - return none; - } - - // Key was previously stored. Extract it. - CFPtr<CFDataRef> key_data = adoptCF(retained_key_data); - if (key_size != CFDataGetLength(key_data.get())) - throw std::runtime_error("Password stored in keychain was not expected size."); - - auto key_bytes = reinterpret_cast<const char *>(CFDataGetBytePtr(key_data.get())); - return std::vector<char>(key_bytes, key_bytes + key_size); -} - -void set_key(const std::vector<char>& key, CFStringRef account, CFStringRef service) -{ - auto search_dictionary = build_search_dictionary(account, service, none); - CFDictionaryAddValue(search_dictionary.get(), kSecAttrAccessible, kSecAttrAccessibleAfterFirstUnlock); - auto key_data = adoptCF(CFDataCreate(nullptr, reinterpret_cast<const UInt8 *>(key.data()), key_size)); - if (!key_data) - throw std::bad_alloc(); - - CFDictionaryAddValue(search_dictionary.get(), kSecValueData, key_data.get()); - if (OSStatus status = SecItemAdd(search_dictionary.get(), nullptr)) - throw KeychainAccessException(status); -} - -} // anonymous namespace - -std::vector<char> metadata_realm_encryption_key(bool check_legacy_service) -{ - CFStringRef account = CFSTR("metadata"); - CFStringRef legacy_service = CFSTR("io.realm.sync.keychain"); - - CFPtr<CFStringRef> service; - if (CFStringRef bundle_id = CFBundleGetIdentifier(CFBundleGetMainBundle())) - service = adoptCF(CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ - Realm Sync Metadata Key"), bundle_id)); - else { - service = retainCF(legacy_service); - check_legacy_service = false; - } - - // Try retrieving the key. - if (auto existing_key = get_key(account, service.get())) { - return *existing_key; - } else if (check_legacy_service) { - // See if there's a key stored using the legacy shared keychain item. - if (auto existing_legacy_key = get_key(account, legacy_service)) { - // If so, copy it to the per-app keychain item before returning it. - set_key(*existing_legacy_key, account, service.get()); - return *existing_legacy_key; - } - } - // Make a completely new key. - std::vector<char> key(key_size); - arc4random_buf(key.data(), key_size); - set_key(key, account, service.get()); - return key; -} - -} // keychain -} // realm diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/apple/keychain_helper.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/apple/keychain_helper.hpp deleted file mode 100644 index 8915e78ee..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/apple/keychain_helper.hpp +++ /dev/null @@ -1,39 +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_OS_KEYCHAIN_HELPER_HPP -#define REALM_OS_KEYCHAIN_HELPER_HPP - -#include <cstdint> -#include <stdexcept> -#include <vector> - -namespace realm { -namespace keychain { - -std::vector<char> metadata_realm_encryption_key(bool check_legacy_service); - -class KeychainAccessException : public std::runtime_error { -public: - KeychainAccessException(int32_t error_code); -}; - -} -} - -#endif // REALM_OS_KEYCHAIN_HELPER_HPP diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/collection_change_builder.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/collection_change_builder.cpp deleted file mode 100644 index 5695b4b71..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/collection_change_builder.cpp +++ /dev/null @@ -1,673 +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 <realm/util/assert.hpp> - -#include <algorithm> - -using namespace realm; -using namespace realm::_impl; - -CollectionChangeBuilder::CollectionChangeBuilder(IndexSet deletions, - IndexSet insertions, - IndexSet modifications, - std::vector<Move> 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(); - - // FIXME: this is comically wasteful - std::unordered_set<int64_t> col_keys; - if (m_track_columns) { - for (auto& col : columns) - col_keys.insert(col.first); - for (auto& col : c.columns) - col_keys.insert(col.first); - } - - auto for_each_col = [&](auto&& f) { - f(modifications, c.modifications); - if (m_track_columns) { - for (auto col : col_keys) - f(columns[col], c.columns[col]); - } - }; - - // 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()) { - for_each_col([&](auto& col, auto& other) { - if (col.contains(it->from)) - other.add(it->to); - }); - old.to = it->to; - *it = c.moves.back(); - c.moves.pop_back(); - 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) { - for_each_col([&](auto& col, auto& other) { - if (col.contains(move.from)) - other.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(); - - for_each_col([&](auto& col, auto& other) { - col.erase_at(c.deletions); - col.shift_for_insert_at(c.insertions); - col.add(other); - }); - - 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::modify(size_t ndx, size_t col) -{ - modifications.add(ndx); - if (!m_track_columns || col == IndexSet::npos) - return; - columns[col].add(ndx); -} - -template<typename Func> -void CollectionChangeBuilder::for_each_col(Func&& f) -{ - f(modifications); - if (m_track_columns) { - for (auto& col : columns) - f(col.second); - } -} - -void CollectionChangeBuilder::insert(size_t index, size_t count, bool track_moves) -{ - REALM_ASSERT(count != 0); - - for_each_col([=](auto& col) { col.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 += count; - } -} - -void CollectionChangeBuilder::erase(size_t index) -{ - for_each_col([=](auto& col) { col.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) -{ - 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(); - columns.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}); - } - } - - for_each_col([=](auto& col) { - bool modified = col.contains(from); - col.erase_at(from); - - if (modified) - col.insert_at(to); - else - col.shift_for_insert_at(to); - }); -} - -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 { - int64_t key; - size_t prev_tv_index; - size_t tv_index; -}; - -#if 0 // FIXME: this is applicable to backlinks still -// Calculates the insertions/deletions required for a query on a table without -// a sort, where `removed` includes the rows which were modified to no longer -// match the query (but not outright deleted rows, which are filtered out long -// before any of this logic), and `move_candidates` tracks the rows which may -// be the result of a move. -// -// This function is not strictly required, as calculate_moves_sorted() will -// produce correct results even for the scenarios where this function is used. -// However, this function has asymptotically better worst-case performance and -// extremely cheap best-case performance, and is guaranteed to produce a minimal -// diff. -void calculate_moves_backlinks(std::vector<RowInfo>& new_rows, IndexSet& removed, - IndexSet const& move_candidates, - CollectionChangeSet& changeset) -{ - // Here we track which row we expect to see, which in the absence of swap() - // is always the row immediately after the last row which was not moved. - size_t expected = 0; - for (auto& row : new_rows) { - if (row.shifted_tv_index == expected) { - ++expected; - continue; - } - - // We didn't find the row we were expecting to find, which means that - // either a row was moved forward to here, the row we were expecting was - // removed, or the row we were expecting moved back. - - // First check if this row even could have moved. If it can't, just - // treat it as a match and move on, and we'll handle the row we were - // expecting when we hit it later. - if (!move_candidates.contains(row.key)) { - expected = row.shifted_tv_index + 1; - continue; - } - - // Next calculate where we expect this row to be based on the insertions - // and removals (i.e. rows changed to not match the query), as it could - // be that the row actually ends up in this spot due to the rows before - // it being removed. - 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 record it as 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); - } -} -#endif - -class LongestCommonSubsequenceCalculator { -public: - // A pair of an object key and an index in the table view - struct Row { - int64_t key; - 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<Match> m_longest_matches; - - LongestCommonSubsequenceCalculator(std::vector<Row>& a, std::vector<Row>& 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 key - std::vector<Row> &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<Length> prev; - // The length of the matching block for each `j` for the row currently being checked - std::vector<Length> 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) { - auto ai = a[i].key; - // 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.key < rgt; }); - REALM_ASSERT(it != end(b) && it->key == ai); - for (; it != end(b) && it->key == 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<RowInfo>& 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<LongestCommonSubsequenceCalculator::Row> a, b; - - a.reserve(rows.size()); - for (auto& row : rows) - a.push_back({row.key, row.prev_tv_index}); - std::sort(begin(a), end(a), [](auto lft, auto rgt) { - return std::tie(lft.tv_index, lft.key) < std::tie(rgt.tv_index, rgt.key); - }); - - // 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].key != rows[i].key) { - first_difference = i; - break; - } - } - if (first_difference == IndexSet::npos) - return; - - // Note that `b` is sorted by key, 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].key, i}); - std::sort(begin(b), end(b), [](auto lft, auto rgt) { - return std::tie(lft.key, lft.tv_index) < std::tie(rgt.key, 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; - } -} - -template<typename T> -void verify_changeset(std::vector<T> const& prev_rows, - std::vector<T> const& next_rows, - CollectionChangeBuilder const& changeset) -{ -#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(changeset.deletions.end()); - auto end = util::make_reverse_iterator(changeset.deletions.begin()); - for (; it != end; ++it) { - rows.erase(rows.begin() + it->first, rows.begin() + it->second); - } - - for (auto i : changeset.insertions.as_indexes()) { - rows.insert(rows.begin() + i, next_rows[i]); - } - - REALM_ASSERT(rows == next_rows); - } -#else - static_cast<void>(prev_rows); - static_cast<void>(next_rows); - static_cast<void>(changeset); -#endif -} - -void calculate(CollectionChangeBuilder& ret, - std::vector<RowInfo> old_rows, std::vector<RowInfo> new_rows, - std::function<bool (int64_t)> key_did_change, bool in_table_order) -{ - // Now that our old and new sets of rows are sorted by key, 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.key == new_index.key) { - new_index.prev_tv_index = old_rows[i].tv_index; - ++i; - ++j; - } - else if (old_index.key < new_index.key) { - ret.deletions.add(old_index.tv_index); - ++i; - } - else { - ret.insertions.add(new_index.tv_index); - ++j; - } - } - - for (; i < old_rows.size(); ++i) - ret.deletions.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 (key_did_change(row.key)) { - ret.modifications.add(row.tv_index); - } - } - - if (!in_table_order) - calculate_moves_sorted(new_rows, ret); -} - -} // Anonymous namespace - -CollectionChangeBuilder CollectionChangeBuilder::calculate(std::vector<int64_t> const& prev_rows, - std::vector<int64_t> const& next_rows, - std::function<bool (int64_t)> key_did_change, - bool in_table_order) -{ - - auto build_row_info = [](auto& rows) { - std::vector<RowInfo> info; - info.reserve(rows.size()); - for (size_t i = 0; i < rows.size(); ++i) - info.push_back({rows[i], IndexSet::npos, i}); - std::sort(begin(info), end(info), [](auto& lft, auto& rgt) { return lft.key < rgt.key; }); - return info; - }; - - CollectionChangeBuilder ret; - ::calculate(ret, build_row_info(prev_rows), build_row_info(next_rows), std::move(key_did_change), in_table_order); - ret.verify(); - verify_changeset(prev_rows, next_rows, ret); - return ret; -} - -CollectionChangeBuilder CollectionChangeBuilder::calculate(std::vector<size_t> const& prev_rows, - std::vector<size_t> const& next_rows, - std::function<bool (int64_t)> key_did_change) -{ - - auto build_row_info = [](auto& rows) { - std::vector<RowInfo> info; - info.reserve(rows.size()); - for (size_t i = 0; i < rows.size(); ++i) - info.push_back({static_cast<int64_t>(rows[i]), IndexSet::npos, i}); - std::sort(begin(info), end(info), [](auto& lft, auto& rgt) { return lft.key < rgt.key; }); - return info; - }; - - CollectionChangeBuilder ret; - ::calculate(ret, build_row_info(prev_rows), build_row_info(next_rows), std::move(key_did_change), false); - ret.verify(); - verify_changeset(prev_rows, next_rows, ret); - return ret; -} - -CollectionChangeSet CollectionChangeBuilder::finalize() && -{ - // Calculate which indices in the old collection were modified - auto modifications_in_old = modifications; - modifications_in_old.erase_at(insertions); - modifications_in_old.shift_for_insert_at(deletions); - - // During changeset calculation we allow marking a row as both inserted and - // modified in case changeset merging results in it no longer being an insert, - // but we don't want inserts in the final modification set - modifications.remove(insertions); - - return { - std::move(deletions), - std::move(insertions), - std::move(modifications_in_old), - std::move(modifications), - std::move(moves), - std::move(columns) - }; -} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/collection_change_builder.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/collection_change_builder.hpp deleted file mode 100644 index 93575e107..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/collection_change_builder.hpp +++ /dev/null @@ -1,81 +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 <realm/keys.hpp> - -#include <functional> -#include <unordered_set> -#include <vector> - -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<Move> 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<int64_t> const& old_rows, - std::vector<int64_t> const& new_rows, - std::function<bool (int64_t)> key_did_change, - bool in_table_order); - static CollectionChangeBuilder calculate(std::vector<size_t> const& old_rows, - std::vector<size_t> const& new_rows, - std::function<bool (int64_t)> key_did_change); - - // generic operations { - CollectionChangeSet finalize() &&; - void merge(CollectionChangeBuilder&&); - - void insert(size_t ndx, size_t count=1, bool track_moves=true); - void modify(size_t ndx, size_t col=-1); - void erase(size_t ndx); - void clear(size_t old_size); - // } - - // operations only implemented for LinkList semantics { - void clean_up_stale_moves(); - void move(size_t from, size_t to); - // } - -private: - bool m_track_columns = true; - - template<typename Func> - void for_each_col(Func&& f); - - void verify(); -}; -} // namespace _impl -} // namespace realm - -#endif // REALM_COLLECTION_CHANGE_BUILDER_HPP diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/collection_notifier.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/collection_notifier.cpp deleted file mode 100644 index 4da4aaf99..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/collection_notifier.cpp +++ /dev/null @@ -1,500 +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 <realm/db.hpp> - -using namespace realm; -using namespace realm::_impl; - -bool CollectionNotifier::all_related_tables_covered(const TableVersions& versions) -{ - if (m_related_tables.size() > versions.size()) { - return false; - } - auto first = versions.begin(); - auto last = versions.end(); - for (auto& it : m_related_tables) { - TableKey tk{it.table_key}; - auto match = std::find_if(first, last, [tk](auto& elem) { - return elem.first == tk; - }); - if (match == last) { - // tk not found in versions - return false; - } - } - return true; -} - -std::function<bool (ObjectChangeSet::ObjectKeyType)> -CollectionNotifier::get_modification_checker(TransactionChangeInfo const& info, - ConstTableRef root_table) -{ - if (info.schema_changed) - set_table(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) { - auto it = info.tables.find(tbl.table_key.value); - return it != info.tables.end() && !it->second.modifications_empty(); - }; - if (!any_of(begin(m_related_tables), end(m_related_tables), table_modified)) { - return [](ObjectChangeSet::ObjectKeyType) { return false; }; - } - if (m_related_tables.size() == 1) { - auto& object_set = info.tables.find(m_related_tables[0].table_key.value)->second; - return [&](ObjectChangeSet::ObjectKeyType object_key) { return object_set.modifications_contains(object_key); }; - } - - return DeepChangeChecker(info, *root_table, m_related_tables); -} - -void DeepChangeChecker::find_related_tables(std::vector<RelatedTable>& out, Table const& table) -{ - auto table_key = table.get_key(); - if (any_of(begin(out), end(out), [=](auto& tbl) { return tbl.table_key == table_key; })) - 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_key, {}}); - - for (auto col_key : table.get_column_keys()) { - auto type = table.get_column_type(col_key); - if (type == type_Link || type == type_LinkList) { - out[out_index].links.push_back({col_key.value, type == type_LinkList}); - find_related_tables(out, *table.get_link_target(col_key)); - } - } -} - -DeepChangeChecker::DeepChangeChecker(TransactionChangeInfo const& info, - Table const& root_table, - std::vector<RelatedTable> const& related_tables) -: m_info(info) -, m_root_table(root_table) -, m_root_table_key(root_table.get_key().value) -, m_root_object_changes([&] { - auto it = info.tables.find(m_root_table_key.value); - return it != info.tables.end() ? &it->second : nullptr; -}()) -, m_related_tables(related_tables) -{ -} - -bool DeepChangeChecker::check_outgoing_links(TableKey table_key, Table const& table, - int64_t obj_key, size_t depth) -{ - auto it = find_if(begin(m_related_tables), end(m_related_tables), - [&](auto&& tbl) { return tbl.table_key == table_key; }); - if (it == m_related_tables.end()) - return false; - if (it->links.empty()) - 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 = [&](int64_t col) { - auto end = m_current_path.begin() + depth; - auto match = std::find_if(m_current_path.begin(), end, [&](auto& p) { - return p.obj_key == obj_key && p.col_key == col; - }); - if (match != end) { - for (; match < end; ++match) match->depth_exceeded = true; - return true; - } - m_current_path[depth] = {obj_key, col, false}; - return false; - }; - - ConstObj obj = table.get_object(ObjKey(obj_key)); - auto linked_object_changed = [&](OutgoingLink const& link) { - if (already_checking(link.col_key)) - return false; - if (!link.is_list) { - if (obj.is_null(ColKey(link.col_key))) - return false; - auto dst = obj.get<ObjKey>(ColKey(link.col_key)).value; - return check_row(*table.get_link_target(ColKey(link.col_key)), dst, depth + 1); - } - - auto& target = *table.get_link_target(ColKey(link.col_key)); - auto lvr = obj.get_linklist(ColKey(link.col_key)); - return std::any_of(lvr.begin(), lvr.end(), - [&, this](auto key) { return this->check_row(target, key.value, depth + 1); }); - }; - - return std::any_of(begin(it->links), end(it->links), linked_object_changed); -} - -bool DeepChangeChecker::check_row(Table const& table, ObjKeyType key, 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 = 0; i < m_current_path.size(); ++i) - m_current_path[i].depth_exceeded = true; - return false; - } - - TableKey table_key = table.get_key(); - if (depth > 0) { - auto it = m_info.tables.find(table_key.value); - if (it != m_info.tables.end() && it->second.modifications_contains(key)) - return true; - } - auto& not_modified = m_not_modified[table_key.value]; - auto it = not_modified.find(key); - if (it != not_modified.end()) - return false; - - bool ret = check_outgoing_links(table_key, table, key, depth); - if (!ret && (depth == 0 || !m_current_path[depth - 1].depth_exceeded)) - not_modified.insert(key); - return ret; -} - -bool DeepChangeChecker::operator()(ObjKeyType key) -{ - if (m_root_object_changes && m_root_object_changes->modifications_contains(key)) - return true; - return check_row(m_root_table, key, 0); -} - -CollectionNotifier::CollectionNotifier(std::shared_ptr<Realm> realm) -: m_realm(std::move(realm)) -, m_sg_version(Realm::Internal::get_transaction(*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(); -} - -void CollectionNotifier::release_data() noexcept -{ - m_sg = nullptr; -} - -uint64_t CollectionNotifier::add_callback(CollectionChangeCallback callback) -{ - m_realm->verify_thread(); - - util::CheckedLockGuard lock(m_callback_mutex); - auto token = m_next_token++; - m_callbacks.push_back({std::move(callback), {}, {}, token, false, false}); - if (m_callback_index == npos) { // Don't need to wake up if we're already sending notifications - Realm::Internal::get_coordinator(*m_realm).wake_up_notifier_worker(); - m_have_callbacks = true; - } - return token; -} - -void CollectionNotifier::remove_callback(uint64_t token) -{ - // the callback needs to be destroyed after releasing the lock as destroying - // it could cause user code to be called - Callback old; - { - util::CheckedLockGuard lock(m_callback_mutex); - auto it = find_callback(token); - if (it == end(m_callbacks)) { - return; - } - - size_t idx = distance(begin(m_callbacks), it); - if (m_callback_index != npos) { - if (m_callback_index >= idx) - --m_callback_index; - } - --m_callback_count; - - old = std::move(*it); - m_callbacks.erase(it); - - m_have_callbacks = !m_callbacks.empty(); - } -} - -void CollectionNotifier::suppress_next_notification(uint64_t token) -{ - { - std::lock_guard<std::mutex> lock(m_realm_mutex); - REALM_ASSERT(m_realm); - m_realm->verify_thread(); - m_realm->verify_in_write(); - } - - util::CheckedLockGuard lock(m_callback_mutex); - auto it = find_callback(token); - if (it != end(m_callbacks)) { - it->skip_next = true; - } -} - -std::vector<CollectionNotifier::Callback>::iterator CollectionNotifier::find_callback(uint64_t token) -{ - 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)); - return it; -} - -void CollectionNotifier::unregister() noexcept -{ - std::lock_guard<std::mutex> lock(m_realm_mutex); - m_realm = nullptr; -} - -bool CollectionNotifier::is_alive() const noexcept -{ - std::lock_guard<std::mutex> lock(m_realm_mutex); - return m_realm != nullptr; -} - -std::unique_lock<std::mutex> CollectionNotifier::lock_target() -{ - return std::unique_lock<std::mutex>{m_realm_mutex}; -} - -void CollectionNotifier::set_table(ConstTableRef 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) || m_related_tables.empty()) { - return; - } - - info.tables.reserve(m_related_tables.size()); - for (auto& tbl : m_related_tables) - info.tables[tbl.table_key.value]; -} - -void CollectionNotifier::prepare_handover() -{ - REALM_ASSERT(m_sg); - m_sg_version = m_sg->get_version_of_current_transaction(); - do_prepare_handover(*m_sg); - add_changes(std::move(m_change)); - REALM_ASSERT(m_change.empty()); - m_has_run = true; - -#ifdef REALM_DEBUG - util::CheckedLockGuard lock(m_callback_mutex); - for (auto& callback : m_callbacks) - REALM_ASSERT(!callback.skip_next); -#endif -} - -void CollectionNotifier::before_advance() -{ - for_each_callback([&](auto& lock, auto& callback) { - if (callback.changes_to_deliver.empty()) { - return; - } - - auto changes = callback.changes_to_deliver; - // acquire a local reference to the callback so that removing the - // callback from within it can't result in a dangling pointer - auto cb = callback.fn; - lock.unlock_unchecked(); - cb.before(changes); - }); -} - -void CollectionNotifier::after_advance() -{ - for_each_callback([&](auto& lock, auto& callback) { - if (callback.initial_delivered && callback.changes_to_deliver.empty()) { - return; - } - callback.initial_delivered = true; - - auto changes = std::move(callback.changes_to_deliver); - // acquire a local reference to the callback so that removing the - // callback from within it can't result in a dangling pointer - auto cb = callback.fn; - lock.unlock_unchecked(); - cb.after(changes); - }); -} - -void CollectionNotifier::deliver_error(std::exception_ptr error) -{ - // Don't complain about double-unregistering callbacks - m_error = true; - - m_callback_count = m_callbacks.size(); - for_each_callback([this, &error](auto& lock, auto& callback) { - // acquire a local reference to the callback so that removing the - // callback from within it can't result in a dangling pointer - auto cb = std::move(callback.fn); - auto token = callback.token; - lock.unlock_unchecked(); - cb.error(error); - - // We never want to call the callback again after this, so just remove it - this->remove_callback(token); - }); -} - -bool CollectionNotifier::is_for_realm(Realm& realm) const noexcept -{ - std::lock_guard<std::mutex> lock(m_realm_mutex); - return m_realm.get() == &realm; -} - -bool CollectionNotifier::package_for_delivery() -{ - if (!prepare_to_deliver()) - return false; - util::CheckedLockGuard lock(m_callback_mutex); - for (auto& callback : m_callbacks) - callback.changes_to_deliver = std::move(callback.accumulated_changes).finalize(); - m_callback_count = m_callbacks.size(); - return true; -} - -template<typename Fn> -void CollectionNotifier::for_each_callback(Fn&& fn) -{ - util::CheckedUniqueLock callback_lock(m_callback_mutex); - REALM_ASSERT_DEBUG(m_callback_count <= m_callbacks.size()); - for (++m_callback_index; m_callback_index < m_callback_count; ++m_callback_index) { - fn(callback_lock, m_callbacks[m_callback_index]); - if (!callback_lock.owns_lock()) - callback_lock.lock_unchecked(); - } - - m_callback_index = npos; -} - -void CollectionNotifier::attach_to(std::shared_ptr<Transaction> sg) -{ - do_attach_to(*sg); - m_sg = std::move(sg); -} - -Transaction& CollectionNotifier::source_shared_group() -{ - return Realm::Internal::get_transaction(*m_realm); -} - -void CollectionNotifier::add_changes(CollectionChangeBuilder change) -{ - util::CheckedLockGuard lock(m_callback_mutex); - for (auto& callback : m_callbacks) { - if (callback.skip_next) { - REALM_ASSERT_DEBUG(callback.accumulated_changes.empty()); - callback.skip_next = false; - } - else { - if (&callback == &m_callbacks.back()) - callback.accumulated_changes.merge(std::move(change)); - else - callback.accumulated_changes.merge(CollectionChangeBuilder(change)); - } - } -} - -NotifierPackage::NotifierPackage(std::exception_ptr error, - std::vector<std::shared_ptr<CollectionNotifier>> notifiers, - RealmCoordinator* coordinator) -: m_notifiers(std::move(notifiers)) -, m_coordinator(coordinator) -, m_error(std::move(error)) -{ -} - -// Clang TSE seems to not like returning a unique_lock from a function -void NotifierPackage::package_and_wait(util::Optional<VersionID::version_type> target_version) NO_THREAD_SAFETY_ANALYSIS -{ - if (!m_coordinator || m_error || !*this) - return; - - auto lock = m_coordinator->wait_for_notifiers([&] { - if (!target_version) - return true; - return std::all_of(begin(m_notifiers), end(m_notifiers), [&](auto const& n) { - return !n->have_callbacks() || (n->has_run() && n->version().version >= *target_version); - }); - }); - - // Package the notifiers for delivery and remove any which don't have anything to deliver - auto package = [&](auto& notifier) { - if (notifier->has_run() && notifier->package_for_delivery()) { - m_version = notifier->version(); - return false; - } - return true; - }; - m_notifiers.erase(std::remove_if(begin(m_notifiers), end(m_notifiers), package), end(m_notifiers)); - if (m_version && target_version && m_version->version < *target_version) { - m_notifiers.clear(); - m_version = util::none; - } - REALM_ASSERT(m_version || m_notifiers.empty()); - - m_coordinator = nullptr; -} - -void NotifierPackage::before_advance() -{ - if (m_error) - return; - for (auto& notifier : m_notifiers) - notifier->before_advance(); -} - -void NotifierPackage::after_advance() -{ - if (m_error) { - for (auto& notifier : m_notifiers) - notifier->deliver_error(m_error); - return; - } - for (auto& notifier : m_notifiers) - notifier->after_advance(); -} - -void NotifierPackage::add_notifier(std::shared_ptr<CollectionNotifier> notifier) -{ - m_notifiers.push_back(notifier); - m_coordinator->register_notifier(notifier); -} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/collection_notifier.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/collection_notifier.hpp deleted file mode 100644 index 6c9da6078..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/collection_notifier.hpp +++ /dev/null @@ -1,353 +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 "object_changeset.hpp" -#include "impl/collection_change_builder.hpp" -#include "util/checked_mutex.hpp" - -#include <realm/util/assert.hpp> -#include <realm/version_id.hpp> -#include <realm/keys.hpp> -#include <realm/table_ref.hpp> - -#include <array> -#include <atomic> -#include <exception> -#include <functional> -#include <mutex> -#include <unordered_map> -#include <unordered_set> - -namespace realm { -class Realm; -class Transaction; - -namespace _impl { -class RealmCoordinator; - -struct ListChangeInfo { - TableKey table_key; - int64_t row_key; - int64_t col_key; - CollectionChangeBuilder* changes; -}; - -// FIXME: this should be in core -using TableKeyType = decltype(TableKey::value); -using ObjKeyType = decltype(ObjKey::value); - -struct TransactionChangeInfo { - std::vector<ListChangeInfo> lists; - std::unordered_map<TableKeyType, ObjectChangeSet> tables; - bool track_all; - bool schema_changed; -}; - -class DeepChangeChecker { -public: - struct OutgoingLink { - int64_t col_key; - bool is_list; - }; - struct RelatedTable { - TableKey table_key; - std::vector<OutgoingLink> links; - }; - - DeepChangeChecker(TransactionChangeInfo const& info, Table const& root_table, - std::vector<RelatedTable> const& related_tables); - - bool operator()(int64_t obj_key); - - // 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<RelatedTable>& out, Table const& table); - -private: - TransactionChangeInfo const& m_info; - Table const& m_root_table; - const TableKey m_root_table_key; - ObjectChangeSet const* const m_root_object_changes; - std::unordered_map<TableKeyType, std::unordered_set<ObjKeyType>> m_not_modified; - std::vector<RelatedTable> const& m_related_tables; - - struct Path { - int64_t obj_key; - int64_t col_key; - bool depth_exceeded; - }; - std::array<Path, 4> m_current_path; - - bool check_row(Table const& table, ObjKeyType obj_key, size_t depth = 0); - bool check_outgoing_links(TableKey table_key, Table const& table, - int64_t obj_key, 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<Realm>); - 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() - uint64_t add_callback(CollectionChangeCallback callback) REQUIRES(!m_callback_mutex); - // 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(uint64_t token) REQUIRES(!m_callback_mutex); - - void suppress_next_notification(uint64_t token) REQUIRES(!m_callback_mutex); - - // ------------------------------------------------------------------------ - // API for RealmCoordinator to manage running things and calling callbacks - - bool is_for_realm(Realm&) const noexcept; - Realm* get_realm() const noexcept { return m_realm.get(); } - - // Get the Transaction 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) - // precondition: RealmCoordinator::m_notifier_mutex is locked - 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; - - // Prepare to deliver the new collection and call callbacks. - // Returns whether or not it has anything to deliver. - // precondition: RealmCoordinator::m_notifier_mutex is locked - bool package_for_delivery() REQUIRES(!m_callback_mutex); - - // Pass the given error to all registered callbacks, then remove them - // precondition: RealmCoordinator::m_notifier_mutex is unlocked - void deliver_error(std::exception_ptr) REQUIRES(!m_callback_mutex); - - // Call each of the given callbacks with the changesets prepared by package_for_delivery() - // precondition: RealmCoordinator::m_notifier_mutex is unlocked - void before_advance() REQUIRES(!m_callback_mutex); - void after_advance() REQUIRES(!m_callback_mutex); - - bool is_alive() const noexcept; - - // precondition: RealmCoordinator::m_notifier_mutex is locked *or* is called on worker thread - bool has_run() const noexcept { return m_has_run; } - - // Attach the handed-over query to `sg`. Must not be already attached to a Transaction. - // precondition: RealmCoordinator::m_notifier_mutex is locked - void attach_to(std::shared_ptr<Transaction> sg); - - // Set `info` as the new ChangeInfo that will be populated by the next - // transaction advance, and register all required information in it - // precondition: RealmCoordinator::m_notifier_mutex is locked - void add_required_change_info(TransactionChangeInfo& info); - - // precondition: RealmCoordinator::m_notifier_mutex is unlocked - virtual void run() = 0; - - // precondition: RealmCoordinator::m_notifier_mutex is locked - void prepare_handover() REQUIRES(!m_callback_mutex); - - template <typename T> - class Handle; - - bool have_callbacks() const noexcept { return m_have_callbacks; } -protected: - void add_changes(CollectionChangeBuilder change) REQUIRES(!m_callback_mutex); - void set_table(ConstTableRef table); - std::unique_lock<std::mutex> lock_target(); - Transaction& source_shared_group(); - - bool all_related_tables_covered(const TableVersions& versions); - std::function<bool (ObjectChangeSet::ObjectKeyType)> get_modification_checker(TransactionChangeInfo const&, ConstTableRef); - - // The actual change, calculated in run() and delivered in prepare_handover() - CollectionChangeBuilder m_change; - -private: - virtual void do_attach_to(Transaction&) { } - virtual void do_prepare_handover(Transaction&) { } - virtual bool do_add_required_change_info(TransactionChangeInfo&) = 0; - virtual bool prepare_to_deliver() { return true; } - - mutable std::mutex m_realm_mutex; - std::shared_ptr<Realm> m_realm; - - VersionID m_sg_version; - std::shared_ptr<Transaction> m_sg; - - bool m_has_run = false; - bool m_error = false; - std::vector<DeepChangeChecker::RelatedTable> m_related_tables; - - struct Callback { - CollectionChangeCallback fn; - CollectionChangeBuilder accumulated_changes; - CollectionChangeSet changes_to_deliver; - uint64_t token; - bool initial_delivered; - bool skip_next; - }; - - // Currently registered callbacks and a mutex which must always be held - // while doing anything with them or m_callback_index - util::CheckedMutex m_callback_mutex; - std::vector<Callback> 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<bool> m_have_callbacks = {false}; - - // Iteration variable for looping over callbacks - // remove_callback() updates this when needed - size_t m_callback_index = -1; - // The number of callbacks which were present when the notifier was packaged - // for delivery which are still present. - // Updated by packaged_for_delivery and removd_callback(), and used in - // for_each_callback() to avoid calling callbacks registered during delivery. - size_t m_callback_count = -1; - - uint64_t m_next_token = 0; - - template<typename Fn> - void for_each_callback(Fn&& fn) REQUIRES(!m_callback_mutex); - - std::vector<Callback>::iterator find_callback(uint64_t token); -}; - -// A smart pointer to a CollectionNotifier that unregisters the notifier when -// the pointer is destroyed. Movable. Copying will produce a null Handle. -template <typename T> -class CollectionNotifier::Handle : public std::shared_ptr<T> { -public: - using std::shared_ptr<T>::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<T>::operator=(std::move(other)); - return *this; - } - - template<typename U> - Handle& operator=(std::shared_ptr<U>&& other) - { - reset(); - std::shared_ptr<T>::operator=(std::move(other)); - return *this; - } - - void reset() - { - if (*this) { - this->get()->unregister(); - std::shared_ptr<T>::reset(); - } - } -}; - -// A package of CollectionNotifiers for a single Realm instance which is passed -// around to the various places which need to actually trigger the notifications -class NotifierPackage { -public: - NotifierPackage() = default; - NotifierPackage(std::exception_ptr error, - std::vector<std::shared_ptr<CollectionNotifier>> notifiers, - RealmCoordinator* coordinator); - - explicit operator bool() { return !m_notifiers.empty(); } - - // Get the version which this package can deliver into, or VersionID{} if - // it has not yet been packaged - util::Optional<VersionID> version() { return m_version; } - - // Package the notifiers for delivery, blocking if they aren't ready for - // the given version. - // No-op if called multiple times - void package_and_wait(util::Optional<VersionID::version_type> target_version); - - // Send the before-change notifications - void before_advance(); - // Deliver the payload associated with the contained notifiers and/or the error - void deliver(Transaction& sg); - // Send the after-change notifications - void after_advance(); - - void add_notifier(std::shared_ptr<CollectionNotifier> notifier); - -private: - util::Optional<VersionID> m_version; - std::vector<std::shared_ptr<CollectionNotifier>> m_notifiers; - - RealmCoordinator* m_coordinator = nullptr; - std::exception_ptr m_error; -}; - -// Find which column of the row in the table contains the given container. -// -// LinkViews and Subtables know what row of their parent they're in, but not -// what column, so we have to just check each one. -template<typename Table, typename T, typename U> -size_t find_container_column(Table& table, size_t row_ndx, T const& expected, int type, U (Table::*getter)(size_t, size_t)) -{ - for (size_t i = 0, count = table.get_column_count(); i != count; ++i) { - if (table.get_column_type(i) == type && (table.*getter)(i, row_ndx) == expected) { - return i; - } - } - REALM_UNREACHABLE(); -} - - -} // namespace _impl -} // namespace realm - -#endif /* REALM_BACKGROUND_COLLECTION_HPP */ diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/epoll/external_commit_helper.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/epoll/external_commit_helper.cpp deleted file mode 100644 index c900a78b1..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/epoll/external_commit_helper.cpp +++ /dev/null @@ -1,299 +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 <realm/util/fifo_helper.hpp> - -#include <realm/util/assert.hpp> -#include <realm/db.hpp> - -#include <algorithm> -#include <errno.h> -#include <fcntl.h> -#include <sstream> -#include <stdlib.h> -#include <sys/epoll.h> -#include <sys/time.h> -#include <unistd.h> - -#ifdef __ANDROID__ -#include <android/log.h> -#define ANDROID_LOG __android_log_print -#else -#define ANDROID_LOG(...) -#endif - -using namespace realm; -using namespace realm::_impl; - -#define LOGE(...) do { \ - fprintf(stderr, __VA_ARGS__); \ - ANDROID_LOG(ANDROID_LOG_ERROR, "REALM", __VA_ARGS__); \ -} 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. - if (ret != 0) { - int err = errno; - if (err != EAGAIN) { - throw std::system_error(err, std::system_category()); - } - } - std::vector<uint8_t> buff(1024); - read(fd, buff.data(), buff.size()); - } -} -} // anonymous namespace - -class ExternalCommitHelper::DaemonThread { -public: - DaemonThread(); - ~DaemonThread(); - - void add_commit_helper(ExternalCommitHelper* helper); - // Return true if the m_helper_list is empty after removal. - void remove_commit_helper(ExternalCommitHelper* helper); - - static DaemonThread& shared(); -private: - void listen(); - - // To protect the accessing m_helpers on the daemon thread. - std::mutex m_mutex; - std::vector<ExternalCommitHelper*> m_helpers; - // The listener thread - std::thread m_thread; - // File descriptor for epoll - FdHolder m_epoll_fd; - // 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; - // Daemon thread id. For checking unexpected dead locks. - std::thread::id m_thread_id; -}; - - -void ExternalCommitHelper::FdHolder::close() -{ - if (m_fd != -1) { - ::close(m_fd); - } - m_fd = -1; -} - -ExternalCommitHelper::ExternalCommitHelper(RealmCoordinator& parent) -: m_parent(parent) -{ - std::string path; - std::string temp_dir = util::normalize_dir(parent.get_config().fifo_files_fallback_path); - std::string sys_temp_dir = util::normalize_dir(DBOptions::get_sys_tmp_dir()); - - // Object Store needs to create a named pipe in order to coordinate notifications. - // This can be a problem on some file systems (e.g. FAT32) or due to security policies in SELinux. Most commonly - // it is a problem when saving Realms on external storage: https://stackoverflow.com/questions/2740321/how-to-create-named-pipe-mkfifo-in-android - // - // For this reason we attempt to create this file in a temporary location known to be safe to write these files. - // - // In order of priority we attempt to write the file in the following locations: - // 1) Next to the Realm file itself - // 2) A location defined by `Realm::Config::fifo_files_fallback_path` - // 3) A location defined by `DBOptions::set_sys_tmp_dir()` - // - // Core has a similar policy for its named pipes. - // - // Also see https://github.com/realm/realm-java/issues/3140 - // Note that hash collisions are okay here because they just result in doing extra work instead of resulting - // in correctness problems. - - path = parent.get_path() + ".note"; - bool fifo_created = util::try_create_fifo(path); - if (!fifo_created && !temp_dir.empty()) { - path = util::format("%1realm_%2.note", temp_dir, std::hash<std::string>()(parent.get_path())); - fifo_created = util::try_create_fifo(path); - } - if (!fifo_created && !sys_temp_dir.empty()) { - path = util::format("%1realm_%2.note", sys_temp_dir, std::hash<std::string>()(parent.get_path())); - util::create_fifo(path); - } - - 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 - int ret = fcntl(m_notify_fd, F_SETFL, O_NONBLOCK); - if (ret == -1) { - throw std::system_error(errno, std::system_category()); - } - - // Lock is inside add_commit_helper. - DaemonThread::shared().add_commit_helper(this); -} - -ExternalCommitHelper::~ExternalCommitHelper() -{ - DaemonThread::shared().remove_commit_helper(this); -} - -ExternalCommitHelper::DaemonThread::DaemonThread() -{ - m_epoll_fd = epoll_create(1); - if (m_epoll_fd == -1) { - throw std::system_error(errno, std::system_category()); - } - - // Create the anonymous pipe - int pipe_fd[2]; - int 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]; - - epoll_event event{}; - event.events = EPOLLIN; - event.data.fd = m_shutdown_read_fd; - ret = epoll_ctl(m_epoll_fd, EPOLL_CTL_ADD, m_shutdown_read_fd, &event); - if (ret != 0) { - int err = errno; - throw std::system_error(err, std::system_category()); - } - - 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; - } - }); - m_thread_id = m_thread.get_id(); -} - -ExternalCommitHelper::DaemonThread::~DaemonThread() -{ - notify_fd(m_shutdown_write_fd); - m_thread.join(); // Wait for the thread to exit -} - -ExternalCommitHelper::DaemonThread& ExternalCommitHelper::DaemonThread::shared() -{ - static DaemonThread daemon_thread; - return daemon_thread; -} - -void ExternalCommitHelper::DaemonThread::add_commit_helper(ExternalCommitHelper* helper) -{ - // Called in the deamon thread loop, dead lock will happen. - REALM_ASSERT(std::this_thread::get_id() != m_thread_id); - - std::lock_guard<std::mutex> lock(m_mutex); - - m_helpers.push_back(helper); - - epoll_event event{}; - event.events = EPOLLIN | EPOLLET; - event.data.fd = helper->m_notify_fd; - int ret = epoll_ctl(m_epoll_fd, EPOLL_CTL_ADD, helper->m_notify_fd, &event); - if (ret != 0) { - int err = errno; - throw std::system_error(err, std::system_category()); - } -} - -void ExternalCommitHelper::DaemonThread::remove_commit_helper(ExternalCommitHelper* helper) -{ - // Called in the deamon thread loop, dead lock will happen. - REALM_ASSERT(std::this_thread::get_id() != m_thread_id); - - std::lock_guard<std::mutex> lock(m_mutex); - - m_helpers.erase(std::remove(m_helpers.begin(), m_helpers.end(), helper), m_helpers.end()); - - // In kernel versions before 2.6.9, the EPOLL_CTL_DEL operation required a non-NULL pointer in event, even - // though this argument is ignored. See man page of epoll_ctl. - epoll_event event{}; - epoll_ctl(m_epoll_fd, EPOLL_CTL_DEL, helper->m_notify_fd, &event); -} - -void ExternalCommitHelper::DaemonThread::listen() -{ - pthread_setname_np(pthread_self(), "Realm notification listener"); - - int ret; - - while (true) { - epoll_event ev{}; - ret = epoll_wait(m_epoll_fd, &ev, 1, -1); - - if (ret == -1 && errno == EINTR) { - // Interrupted system call, try again. - continue; - } - - if (ret == -1) { - int err = errno; - throw std::system_error(err, std::system_category()); - } - if (ret == 0) { - // Spurious wakeup; just wait again - continue; - } - - if (ev.data.u32 == (uint32_t)m_shutdown_read_fd) { - return; - } - - { - std::lock_guard<std::mutex> lock(m_mutex); - for (auto helper : m_helpers) { - if (ev.data.u32 == (uint32_t)helper->m_notify_fd) { - helper->m_parent.on_change(); - } - } - } - } -} - -void ExternalCommitHelper::notify_others() -{ - notify_fd(m_notify_fd); -} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/epoll/external_commit_helper.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/epoll/external_commit_helper.hpp deleted file mode 100644 index 7f9991373..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/epoll/external_commit_helper.hpp +++ /dev/null @@ -1,70 +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 <memory> -#include <mutex> -#include <thread> -#include <vector> - -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; - }; - - class DaemonThread; - - RealmCoordinator& m_parent; - - // 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; -}; - -} // namespace _impl -} // namespace realm - diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/external_commit_helper.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/external_commit_helper.hpp deleted file mode 100644 index 8c5f3a1bd..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/external_commit_helper.hpp +++ /dev/null @@ -1,40 +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 <realm/util/features.h> - -#if (defined(REALM_HAVE_EPOLL) && REALM_HAVE_EPOLL) || REALM_ANDROID || (defined(REALM_PLATFORM_NODE) && REALM_PLATFORM_NODE && !REALM_PLATFORM_APPLE && !defined(_WIN32)) -#define REALM_USE_EPOLL 1 -#else -#define REALM_USE_EPOLL 0 -#endif - -#if REALM_PLATFORM_APPLE -#include "impl/apple/external_commit_helper.hpp" -#elif REALM_USE_EPOLL -#include "impl/epoll/external_commit_helper.hpp" -#elif defined(_WIN32) -#include "impl/windows/external_commit_helper.hpp" -#else -#include "impl/generic/external_commit_helper.hpp" -#endif - -#endif // REALM_EXTERNAL_COMMIT_HELPER_HPP diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/generic/external_commit_helper.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/generic/external_commit_helper.cpp deleted file mode 100644 index dfebc6aed..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/generic/external_commit_helper.cpp +++ /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/external_commit_helper.hpp" - -#include "impl/realm_coordinator.hpp" - -#include <realm/history.hpp> -#include <realm/replication.hpp> - -using namespace realm; -using namespace realm::_impl; - -ExternalCommitHelper::ExternalCommitHelper(RealmCoordinator& parent) -: m_parent(parent) -, m_history(realm::make_in_realm_history(parent.get_path())) -, m_sg(*m_history, TransactionOptions(parent.is_in_memory() ? TransactionOptions::Durability::MemOnly - : TransactionOptions::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/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/generic/external_commit_helper.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/generic/external_commit_helper.hpp deleted file mode 100644 index f88e2e060..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/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 <realm/group_shared.hpp> - -#include <future> - -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<Replication> m_history; - Transaction m_sg; - - // The listener thread - std::future<void> m_thread; -}; - -} // namespace _impl -} // namespace realm - diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/list_notifier.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/list_notifier.cpp deleted file mode 100644 index ef55e7ffd..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/list_notifier.cpp +++ /dev/null @@ -1,104 +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 "list.hpp" - -#include <realm/db.hpp> -#include <realm/group.hpp> - -using namespace realm; -using namespace realm::_impl; - -ListNotifier::ListNotifier(std::shared_ptr<Realm> realm, LstBase const& list, - PropertyType type) -: CollectionNotifier(std::move(realm)) -, m_type(type) -, m_table(list.get_table()->get_key()) -, m_col(list.get_col_key()) -, m_obj(list.get_key()) -, m_prev_size(list.size()) -{ - if (m_type == PropertyType::Object) { - set_table(static_cast<const LnkLst&>(list).get_target_table()); - } -} - -void ListNotifier::release_data() noexcept -{ - m_list = {}; - CollectionNotifier::release_data(); -} - -void ListNotifier::do_attach_to(Transaction& sg) -{ - try { - auto obj = sg.get_table(m_table)->get_object(m_obj); - m_list = obj.get_listbase_ptr(m_col); - } - catch (const InvalidKey&) { - } -} - -bool ListNotifier::do_add_required_change_info(TransactionChangeInfo& info) -{ - if (!m_list->is_attached()) - return false; // origin row was deleted after the notification was added - - info.lists.push_back({m_table, m_obj.value, m_col.value, &m_change}); - - m_info = &info; - return true; -} - -void ListNotifier::run() -{ - if (!m_list->is_attached()) { - // List 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; - } - - m_prev_size = m_list->size(); - - if (m_type == PropertyType::Object) { - auto& list = static_cast<LnkLst&>(*m_list); - auto object_did_change = get_modification_checker(*m_info, list.get_target_table()); - for (size_t i = 0; i < list.size(); ++i) { - if (m_change.modifications.contains(i)) - continue; - if (object_did_change(list.get(i).value)) - m_change.modifications.add(i); - } - - for (auto const& move : m_change.moves) { - if (m_change.modifications.contains(move.to)) - continue; - if (object_did_change(list.get(move.to).value)) - m_change.modifications.add(move.to); - } - } -} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/list_notifier.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/list_notifier.hpp deleted file mode 100644 index abd4120e0..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/list_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. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef REALM_LIST_NOTIFIER_HPP -#define REALM_LIST_NOTIFIER_HPP - -#include "impl/collection_notifier.hpp" - -#include "property.hpp" - -#include <realm/list.hpp> - -namespace realm { -namespace _impl { -class ListNotifier : public CollectionNotifier { -public: - ListNotifier(std::shared_ptr<Realm> realm, LstBase const& list, PropertyType type); - -private: - PropertyType m_type; - std::unique_ptr<LstBase> m_list; - - TableKey m_table; - ColKey m_col; - ObjKey m_obj; - - // 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; - - TransactionChangeInfo* m_info; - - void run() override; - - void do_attach_to(Transaction& sg) override; - - void release_data() noexcept override; - bool do_add_required_change_info(TransactionChangeInfo& info) override; -}; -} -} - -#endif // REALM_LIST_NOTIFIER_HPP diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/notification_wrapper.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/notification_wrapper.hpp deleted file mode 100644 index f2e5a3b8f..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/notification_wrapper.hpp +++ /dev/null @@ -1,51 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 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_OS_NOTIFICATION_WRAPPER_HPP -#define REALM_OS_NOTIFICATION_WRAPPER_HPP - -#include "collection_notifications.hpp" - -namespace realm { -namespace _impl { - -// A wrapper that stores a value and an associated notification token. -// The point of this type is to keep the notification token alive -// until the value can be properly processed or handled. -template<typename T> -struct NotificationWrapper : public T { - using T::T; - - NotificationWrapper(T&& object) - : T(object) - { } - - template <typename F> - void add_notification_callback(F&& callback) - { - m_token = T::add_notification_callback(std::forward<F>(callback)); - } - -private: - NotificationToken m_token; -}; - -} // namespace _impl -} // namespace realm - -#endif // REALM_OS_NOTIFICATION_WRAPPER_HPP diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/object_accessor_impl.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/object_accessor_impl.hpp deleted file mode 100644 index 7cc309e1c..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/object_accessor_impl.hpp +++ /dev/null @@ -1,224 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 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_OS_OBJECT_ACCESSOR_IMPL_HPP -#define REALM_OS_OBJECT_ACCESSOR_IMPL_HPP - -#include "object_accessor.hpp" - -#include <realm/util/any.hpp> - -namespace realm { -using AnyDict = std::map<std::string, util::Any>; -using AnyVector = std::vector<util::Any>; - -// An object accessor context which can be used to create and access objects -// using util::Any as the type-erased value type. In addition, this serves as -// the reference implementation of an accessor context that must be implemented -// by each binding. -class CppContext { -public: - // This constructor is the only one used by the object accessor code, and is - // used when recurring into a link or array property during object creation - // (i.e. prop.type will always be Object or Array). - CppContext(CppContext& c, Property const& prop) - : realm(c.realm) - , object_schema(prop.type == PropertyType::Object ? &*realm->schema().find(prop.object_type) : c.object_schema) - { } - - CppContext() = default; - CppContext(std::shared_ptr<Realm> realm, const ObjectSchema* os=nullptr) - : realm(std::move(realm)), object_schema(os) { } - - // The use of util::Optional for the following two functions is not a hard - // requirement; only that it be some type which can be evaluated in a - // boolean context to determine if it contains a value, and if it does - // contain a value it must be dereferencable to obtain that value. - - // Get the value for a property in an input object, or `util::none` if no - // value present. The property is identified both by the name of the - // property and its index within the ObjectScehma's persisted_properties - // array. - util::Optional<util::Any> value_for_property(util::Any& dict, - const Property& prop, - size_t /* property_index */) const - { - auto const& v = any_cast<AnyDict&>(dict); - auto it = v.find(prop.name); - return it == v.end() ? util::none : util::make_optional(it->second); - } - - // Get the default value for the given property in the given object schema, - // or `util::none` if there is none (which is distinct from the default - // being `null`). - // - // This implementation does not support default values; see the default - // value tests for an example of one which does. - util::Optional<util::Any> - default_value_for_property(ObjectSchema const&, Property const&) const - { - return util::none; - } - - // Invoke `fn` with each of the values from an enumerable type - template<typename Func> - void enumerate_list(util::Any& value, Func&& fn) { - for (auto&& v : any_cast<AnyVector&>(value)) - fn(v); - } - - // Determine if `value` boxes the same List as `list` - bool is_same_list(List const& list, util::Any const& value) - { - if (auto list2 = any_cast<List>(&value)) - return list == *list2; - return false; - } - - // Convert from core types to the boxed type - util::Any box(BinaryData v) const { return std::string(v); } - util::Any box(List v) const { return v; } - util::Any box(Object v) const { return v; } - util::Any box(Results v) const { return v; } - util::Any box(StringData v) const { return std::string(v); } - util::Any box(Timestamp v) const { return v; } - util::Any box(bool v) const { return v; } - util::Any box(double v) const { return v; } - util::Any box(float v) const { return v; } - util::Any box(int64_t v) const { return v; } - util::Any box(util::Optional<bool> v) const { return v; } - util::Any box(util::Optional<double> v) const { return v; } - util::Any box(util::Optional<float> v) const { return v; } - util::Any box(util::Optional<int64_t> v) const { return v; } - util::Any box(Obj) const; - - // Any properties are only supported by the Cocoa binding to enable reading - // old Realm files that may have used them. Other bindings can safely not - // implement this. - util::Any box(Mixed) const { REALM_TERMINATE("not supported"); } - - // Convert from the boxed type to core types. This needs to be implemented - // for all of the types which `box()` can take, plus `Obj` and optional - // versions of the numeric types, minus `List` and `Results`. - // - // `create` and `update` are only applicable to `unbox<Obj>`. If - // `create` is false then when given something which is not a managed Realm - // object `unbox()` should simply return a detached obj, while if it's - // true then `unbox()` should create a new object in the context's Realm - // using the provided value. If `update` is true then upsert semantics - // should be used for this. - // If `update_only_diff` is true, only properties that are different from - // already existing properties should be updated. If `create` and `update_only_diff` - // is true, `current_row` may hold a reference to the object that should - // be compared against. - template<typename T> - T unbox(util::Any& v, CreatePolicy = CreatePolicy::Skip, ObjKey /*current_row*/ = ObjKey()) const { return any_cast<T>(v); } - - bool is_null(util::Any const& v) const noexcept { return !v.has_value(); } - util::Any null_value() const noexcept { return {}; } - util::Optional<util::Any> no_value() const noexcept { return {}; } - - // KVO hooks which will be called before and after modying a property from - // within Object::create(). - void will_change(Object const&, Property const&) {} - void did_change() {} - - // Get a string representation of the given value for use in error messages. - std::string print(util::Any const&) const { return "not implemented"; } - - // Cocoa allows supplying fewer values than there are properties when - // creating objects using an array of values. Other bindings should not - // mimick this behavior so just return false here. - bool allow_missing(util::Any const&) const { return false; } - -private: - std::shared_ptr<Realm> realm; - const ObjectSchema* object_schema = nullptr; - -}; - -inline util::Any CppContext::box(Obj obj) const -{ - REALM_ASSERT(object_schema); - return Object(realm, *object_schema, obj); -} - -template<> -inline StringData CppContext::unbox(util::Any& v, CreatePolicy, ObjKey) const -{ - if (!v.has_value()) - return StringData(); - auto& value = any_cast<std::string&>(v); - return StringData(value.c_str(), value.size()); -} - -template<> -inline BinaryData CppContext::unbox(util::Any& v, CreatePolicy, ObjKey) const -{ - if (!v.has_value()) - return BinaryData(); - auto& value = any_cast<std::string&>(v); - return BinaryData(value.c_str(), value.size()); -} - -template<> -inline Obj CppContext::unbox(util::Any& v, CreatePolicy policy, ObjKey current_obj) const -{ - if (auto object = any_cast<Object>(&v)) - return object->obj(); - if (auto obj = any_cast<Obj>(&v)) - return *obj; - if (policy == CreatePolicy::Skip) - return Obj(); - - REALM_ASSERT(object_schema); - return Object::create(const_cast<CppContext&>(*this), realm, *object_schema, v, policy, current_obj).obj(); -} - -template<> -inline util::Optional<bool> CppContext::unbox(util::Any& v, CreatePolicy, ObjKey) const -{ - return v.has_value() ? util::make_optional(unbox<bool>(v)) : util::none; -} - -template<> -inline util::Optional<int64_t> CppContext::unbox(util::Any& v, CreatePolicy, ObjKey) const -{ - return v.has_value() ? util::make_optional(unbox<int64_t>(v)) : util::none; -} - -template<> -inline util::Optional<double> CppContext::unbox(util::Any& v, CreatePolicy, ObjKey) const -{ - return v.has_value() ? util::make_optional(unbox<double>(v)) : util::none; -} - -template<> -inline util::Optional<float> CppContext::unbox(util::Any& v, CreatePolicy, ObjKey) const -{ - return v.has_value() ? util::make_optional(unbox<float>(v)) : util::none; -} - -template<> -inline Mixed CppContext::unbox(util::Any&, CreatePolicy, ObjKey) const -{ - throw std::logic_error("'Any' type is unsupported"); -} -} - -#endif // REALM_OS_OBJECT_ACCESSOR_IMPL_HPP diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/object_notifier.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/object_notifier.cpp deleted file mode 100644 index f266199d9..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/object_notifier.cpp +++ /dev/null @@ -1,64 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 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/object_notifier.hpp" - -#include "shared_realm.hpp" - -using namespace realm; -using namespace realm::_impl; - -ObjectNotifier::ObjectNotifier(std::shared_ptr<Realm> realm, TableKey table, ObjKey obj) -: CollectionNotifier(std::move(realm)) -, m_table(table) -, m_obj(obj) -{ -} - -bool ObjectNotifier::do_add_required_change_info(TransactionChangeInfo& info) -{ - m_info = &info; - info.tables[m_table.value]; - return false; -} - -void ObjectNotifier::run() -{ - if (!m_table) - return; - - auto it = m_info->tables.find(m_table.value); - if (it == m_info->tables.end()) - return; - auto& change = it->second; - - if (change.deletions_contains(m_obj.value)) { - m_change.deletions.add(0); - m_table = {}; - m_obj = {}; - return; - } - - auto column_modifications = change.get_columns_modified(m_obj.value); - if (!column_modifications) - return; - m_change.modifications.add(0); - for (auto col : *column_modifications) { - m_change.columns[col].add(0); - } -} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/object_notifier.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/object_notifier.hpp deleted file mode 100644 index 4f9411a60..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/object_notifier.hpp +++ /dev/null @@ -1,45 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 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_OS_OBJECT_NOTIFIER_HPP -#define REALM_OS_OBJECT_NOTIFIER_HPP - -#include "impl/collection_notifier.hpp" - -#include <realm/keys.hpp> - -namespace realm { - -namespace _impl { -class ObjectNotifier : public CollectionNotifier { -public: - ObjectNotifier(std::shared_ptr<Realm> realm, TableKey table, ObjKey obj); - -private: - TableKey m_table; - ObjKey m_obj; - TransactionChangeInfo* m_info; - - void run() override; - - bool do_add_required_change_info(TransactionChangeInfo& info) override; -}; -} -} - -#endif // REALM_OS_OBJECT_NOTIFIER_HPP diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/realm_coordinator.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/realm_coordinator.cpp deleted file mode 100644 index c9ee74b4d..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/realm_coordinator.cpp +++ /dev/null @@ -1,1162 +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 "binding_context.hpp" -#include "object_schema.hpp" -#include "object_store.hpp" -#include "property.hpp" -#include "schema.hpp" -#include "thread_safe_reference.hpp" -#include "util/scheduler.hpp" - -#if REALM_ENABLE_SYNC -#include "sync/impl/work_queue.hpp" -#include "sync/impl/sync_file.hpp" -#include "sync/async_open_task.hpp" -#include "sync/partial_sync.hpp" -#include "sync/sync_config.hpp" -#include "sync/sync_manager.hpp" -#include "sync/sync_session.hpp" -#endif - -#include <realm/db.hpp> -#include <realm/history.hpp> -#include <realm/string_data.hpp> -#include <realm/util/fifo_helper.hpp> - -#include <algorithm> -#include <unordered_map> - -using namespace realm; -using namespace realm::_impl; - -static auto& s_coordinator_mutex = *new std::mutex; -static auto& s_coordinators_per_path = *new std::unordered_map<std::string, std::weak_ptr<RealmCoordinator>>; - -std::shared_ptr<RealmCoordinator> RealmCoordinator::get_coordinator(StringData path) -{ - std::lock_guard<std::mutex> 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<RealmCoordinator>(); - weak_coordinator = coordinator; - return coordinator; -} - -std::shared_ptr<RealmCoordinator> RealmCoordinator::get_coordinator(const Realm::Config& config) NO_THREAD_SAFETY_ANALYSIS -{ - auto coordinator = get_coordinator(config.path); - util::CheckedLockGuard lock(coordinator->m_realm_mutex); - coordinator->set_config(config); - return coordinator; -} - -std::shared_ptr<RealmCoordinator> RealmCoordinator::get_existing_coordinator(StringData path) -{ - std::lock_guard<std::mutex> lock(s_coordinator_mutex); - - auto& weak_coordinator = s_coordinators_per_path[path]; - if (auto coordinator = weak_coordinator.lock()) { - return coordinator; - } - - return {}; -} - -void RealmCoordinator::create_sync_session(bool force_client_resync) -{ -#if REALM_ENABLE_SYNC - if (m_sync_session) - return; - - if (!m_config.encryption_key.empty() && !m_config.sync_config->realm_encryption_key) { - throw std::logic_error("A realm encryption key was specified in Realm::Config but not in SyncConfig"); - } else if (m_config.sync_config->realm_encryption_key && m_config.encryption_key.empty()) { - throw std::logic_error("A realm encryption key was specified in SyncConfig but not in Realm::Config"); - } else if (m_config.sync_config->realm_encryption_key && - !std::equal(m_config.sync_config->realm_encryption_key->begin(), m_config.sync_config->realm_encryption_key->end(), - m_config.encryption_key.begin(), m_config.encryption_key.end())) { - throw std::logic_error("The realm encryption key specified in SyncConfig does not match the one in Realm::Config"); - } - - m_sync_session = SyncManager::shared().get_session(m_config.path, *m_config.sync_config, force_client_resync); - - std::weak_ptr<RealmCoordinator> weak_self = shared_from_this(); - SyncSession::Internal::set_sync_transact_callback(*m_sync_session, - [weak_self](VersionID old_version, VersionID new_version) { - if (auto self = weak_self.lock()) { - util::CheckedUniqueLock lock(self->m_transaction_callback_mutex); - if (auto transaction_callback = self->m_transaction_callback) { - lock.unlock(); - transaction_callback(old_version, new_version); - } - if (self->m_notifier) - self->m_notifier->notify_others(); - } - }); -#else - static_cast<void>(force_client_resync); -#endif -} - -void RealmCoordinator::set_config(const Realm::Config& config) -{ - if (config.encryption_key.data() && config.encryption_key.size() != 64) - throw InvalidEncryptionKeyException(); - if (config.schema_mode == SchemaMode::Immutable && config.sync_config) - throw std::logic_error("Synchronized Realms cannot be opened in immutable mode"); - if (config.schema_mode == SchemaMode::Additive && config.migration_function) - throw std::logic_error("Realms opened in Additive-only schema mode do not use a migration function"); - if (config.schema_mode == SchemaMode::Immutable && config.migration_function) - throw std::logic_error("Realms opened in immutable mode do not use a migration function"); - if (config.schema_mode == SchemaMode::ReadOnlyAlternative && config.migration_function) - throw std::logic_error("Realms opened in read-only mode do not use a migration function"); - if (config.schema_mode == SchemaMode::Immutable && config.initialization_function) - throw std::logic_error("Realms opened in immutable mode do not use an initialization function"); - if (config.schema_mode == SchemaMode::ReadOnlyAlternative && config.initialization_function) - throw std::logic_error("Realms opened in read-only mode do not use an initialization function"); - if (config.schema && config.schema_version == ObjectStore::NotVersioned) - throw std::logic_error("A schema version must be specified when the schema is specified"); - if (!config.realm_data.is_null() && (!config.immutable() || !config.in_memory)) - throw std::logic_error("In-memory realms initialized from memory buffers can only be opened in read-only mode"); - if (!config.realm_data.is_null() && !config.path.empty()) - throw std::logic_error("Specifying both memory buffer and path is invalid"); - if (!config.realm_data.is_null() && !config.encryption_key.empty()) - throw std::logic_error("Memory buffers do not support encryption"); - // ResetFile also won't use the migration function, but specifying one is - // allowed to simplify temporarily switching modes during development - - bool no_existing_realm = std::all_of(begin(m_weak_realm_notifiers), end(m_weak_realm_notifiers), - [](auto& notifier) { return notifier.expired(); }); - if (no_existing_realm) { - m_config = config; - m_config.scheduler = nullptr; - } - else { - if (m_config.immutable() != config.immutable()) { - 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_mode != config.schema_mode) { - throw MismatchedConfigException("Realm at path '%1' already opened with a different schema mode.", config.path); - } - util::CheckedLockGuard lock(m_schema_cache_mutex); - if (config.schema && m_schema_version != ObjectStore::NotVersioned && m_schema_version != config.schema_version) { - throw MismatchedConfigException("Realm at path '%1' already opened with different schema version.", config.path); - } - -#if REALM_ENABLE_SYNC - if (bool(m_config.sync_config) != bool(config.sync_config)) { - throw MismatchedConfigException("Realm at path '%1' already opened with different sync configurations.", config.path); - } - - if (config.sync_config) { - if (m_config.sync_config->user != config.sync_config->user) { - throw MismatchedConfigException("Realm at path '%1' already opened with different sync user.", config.path); - } - if (m_config.sync_config->realm_url() != config.sync_config->realm_url()) { - throw MismatchedConfigException("Realm at path '%1' already opened with different sync server URL.", config.path); - } - if (m_config.sync_config->transformer != config.sync_config->transformer) { - throw MismatchedConfigException("Realm at path '%1' already opened with different transformer.", config.path); - } - if (m_config.sync_config->realm_encryption_key != config.sync_config->realm_encryption_key) { - throw MismatchedConfigException("Realm at path '%1' already opened with sync session encryption key.", config.path); - } - } -#endif - - // Realm::update_schema() handles complaining about schema mismatches - } -} - -std::shared_ptr<Realm> RealmCoordinator::get_realm(Realm::Config config, util::Optional<VersionID> version) -{ - if (!config.scheduler) - config.scheduler = version ? util::Scheduler::get_frozen() : util::Scheduler::make_default(); - // realm must be declared before lock so that the mutex is released before - // we release the strong reference to realm, as Realm's destructor may want - // to acquire the same lock - std::shared_ptr<Realm> realm; - util::CheckedUniqueLock lock(m_realm_mutex); - set_config(config); - do_get_realm(std::move(config), realm, version, lock); - return realm; -} - -std::shared_ptr<Realm> RealmCoordinator::get_realm(std::shared_ptr<util::Scheduler> scheduler) -{ - std::shared_ptr<Realm> realm; - util::CheckedUniqueLock lock(m_realm_mutex); - auto config = m_config; - config.scheduler = scheduler ? scheduler : util::Scheduler::make_default(); - do_get_realm(std::move(config), realm, none, lock); - return realm; -} - -ThreadSafeReference RealmCoordinator::get_unbound_realm() -{ - std::shared_ptr<Realm> realm; - util::CheckedUniqueLock lock(m_realm_mutex); - do_get_realm(m_config, realm, none, lock); - return ThreadSafeReference(realm); -} - -void RealmCoordinator::do_get_realm(Realm::Config config, std::shared_ptr<Realm>& realm, - util::Optional<VersionID> version, - CheckedUniqueLock& realm_lock) -{ - open_db(); - - auto schema = std::move(config.schema); - auto migration_function = std::move(config.migration_function); - auto initialization_function = std::move(config.initialization_function); - auto audit_factory = std::move(config.audit_factory); - config.schema = {}; - - realm = Realm::make_shared_realm(std::move(config), version, shared_from_this()); - if (!m_notifier && !m_config.immutable() && m_config.automatic_change_notifications) { - try { - m_notifier = std::make_unique<ExternalCommitHelper>(*this); - } - catch (std::system_error const& ex) { - throw RealmFileException(RealmFileException::Kind::AccessError, - get_path(), ex.code().message(), ""); - } - } - m_weak_realm_notifiers.emplace_back(realm); - - if (realm->config().sync_config) - create_sync_session(false); - - if (!m_audit_context && audit_factory) - m_audit_context = audit_factory(); - - realm_lock.unlock_unchecked(); - if (schema) { -#if REALM_ENABLE_SYNC && REALM_PLATFORM_JAVA - // Workaround for https://github.com/realm/realm-java/issues/6619 - // Between Realm Java 5.10.0 and 5.13.0 created_at/updated_at was optional - // when created from Java, even though the Object Store code specified them as - // required. Due to how the Realm was initialized, this wasn't a problem before - // 5.13.0, but after that the Object Store initializer code was changed causing - // problems when Java clients upgraded. In order to prevent older clients from - // breaking with a schema mismatch when upgrading we thus fix the schema in transit. - // This means that schema reported back from Realm will be different than the one - // specified in the Java model class, but this seemed like the approach with the - // least amount of disadvantages. - if (realm->is_partial()) { - auto& new_schema = schema.value(); - auto current_schema = realm->schema(); - auto current_resultsets_schema_obj = current_schema.find("__ResultSets"); - if (current_resultsets_schema_obj != current_schema.end()) { - Property* p = current_resultsets_schema_obj->property_for_public_name("created_at"); - if (is_nullable(p->type)) { - auto it = new_schema.find("__ResultSets"); - if (it != new_schema.end()) { - auto created_at_property = it->property_for_public_name("created_at"); - auto updated_at_property = it->property_for_public_name("updated_at"); - if (created_at_property && updated_at_property) { - created_at_property->type = created_at_property->type | PropertyType::Nullable; - updated_at_property->type = updated_at_property->type | PropertyType::Nullable; - } - } - } - } - } -#endif - realm->update_schema(std::move(*schema), config.schema_version, std::move(migration_function), - std::move(initialization_function)); - } -#if REALM_ENABLE_SYNC - else if (realm->is_partial()) - _impl::ensure_partial_sync_schema_initialized(*realm); -#endif -} - -void RealmCoordinator::bind_to_context(Realm& realm) -{ - util::CheckedLockGuard lock(m_realm_mutex); - for (auto& cached_realm : m_weak_realm_notifiers) { - if (!cached_realm.is_for_realm(&realm)) - continue; - cached_realm.bind_to_scheduler(); - return; - } - REALM_TERMINATE("Invalid Realm passed to bind_to_context()"); -} - -#if REALM_ENABLE_SYNC -std::shared_ptr<AsyncOpenTask> RealmCoordinator::get_synchronized_realm(Realm::Config config) -{ - if (!config.sync_config) - throw std::logic_error("This method is only available for fully synchronized Realms."); - - util::CheckedLockGuard lock(m_realm_mutex); - set_config(config); - bool exists = File::exists(m_config.path); - create_sync_session(!config.sync_config->is_partial && !exists); - return std::make_shared<AsyncOpenTask>(shared_from_this(), m_sync_session); -} - -void RealmCoordinator::create_session(const Realm::Config& config) -{ - REALM_ASSERT(config.sync_config); - util::CheckedLockGuard lock(m_realm_mutex); - set_config(config); - bool exists = File::exists(m_config.path); - create_sync_session(!config.sync_config->is_partial && !exists); -} - -void RealmCoordinator::open_with_config(Realm::Config config) -{ - CheckedLockGuard lock(m_realm_mutex); - set_config(config); - open_db(); -} - -#endif - -namespace realm { -namespace _impl { -REALM_NOINLINE void translate_file_exception(StringData path, bool immutable) -{ - 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(), immutable ? "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 (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()); - } - 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(); - RealmFileException::Kind error_kind = RealmFileException::Kind::AccessError; - // FIXME: Replace this with a proper specific exception type once Core adds support for it. - if (underlying == "Bad or incompatible history type") - error_kind = RealmFileException::Kind::BadHistoryError; - 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(error_kind, 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 (UnsupportedFileFormatVersion const& ex) { - throw RealmFileException(RealmFileException::Kind::FormatUpgradeRequired, path, - util::format("Opening Realm files of format version %1 is not supported by this version of Realm", ex.source_version), - ex.what()); - } -} -} // namespace _impl -} // namespace realm - -void RealmCoordinator::open_db() -{ - if (m_db || m_read_only_group) - return; - - bool server_synchronization_mode = m_config.sync_config || m_config.force_sync_history; - try { - if (m_config.immutable()) { - if (m_config.realm_data.is_null()) { - m_read_only_group = std::make_shared<Group>(m_config.path, - m_config.encryption_key.data(), - Group::mode_ReadOnly); - } - else { - // Create in-memory read-only realm from existing buffer (without taking ownership of the buffer) - m_read_only_group = std::make_unique<Group>(m_config.realm_data, false); - } - return; - } - - if (server_synchronization_mode) { -#if REALM_ENABLE_SYNC - m_history = sync::make_client_replication(m_config.path); -#else - REALM_TERMINATE("Realm was not built with sync enabled"); -#endif - } - else { - m_history = make_in_realm_history(m_config.path); - } - - DBOptions options; - options.durability = m_config.in_memory - ? DBOptions::Durability::MemOnly - : DBOptions::Durability::Full; - - if (!m_config.fifo_files_fallback_path.empty()) { - options.temp_dir = util::normalize_dir(m_config.fifo_files_fallback_path); - } - options.encryption_key = m_config.encryption_key.data(); - options.allow_file_format_upgrade = !m_config.disable_format_upgrade - && m_config.schema_mode != SchemaMode::ResetFile; - m_db = DB::create(*m_history, options); - } - catch (realm::FileFormatUpgradeRequired const&) { - if (m_config.schema_mode != SchemaMode::ResetFile) { - translate_file_exception(m_config.path, m_config.immutable()); - } - util::File::remove(m_config.path); - return open_db(); - } - catch (UnsupportedFileFormatVersion const&) { - if (m_config.schema_mode != SchemaMode::ResetFile) { - translate_file_exception(m_config.path, m_config.immutable()); - } - util::File::remove(m_config.path); - return open_db(); - } -#if REALM_ENABLE_SYNC - catch (IncompatibleHistories const& ex) { - translate_file_exception(m_config.path, m_config.immutable()); // Throws - } -#endif // REALM_ENABLE_SYNC - catch (...) { - translate_file_exception(m_config.path, m_config.immutable()); - } - - if (!m_config.should_compact_on_launch_function) - return; - - size_t free_space = 0; - size_t used_space = 0; - if (auto tr = m_db->start_write(true)) { - tr->commit(); - m_db->get_stats(free_space, used_space); - } - if (free_space > 0 && m_config.should_compact_on_launch_function(free_space + used_space, used_space)) - m_db->compact(); -} - -void RealmCoordinator::close() -{ - m_db->close(); - m_db = nullptr; -} - -std::shared_ptr<Group> RealmCoordinator::begin_read(VersionID version, bool frozen_transaction) -{ - open_db(); - if (m_read_only_group) - return m_read_only_group; - return (frozen_transaction) ? m_db->start_frozen(version) : m_db->start_read(version); -} - -uint64_t RealmCoordinator::get_schema_version() const noexcept -{ - util::CheckedLockGuard lock(m_schema_cache_mutex); - return m_schema_version; -} - -bool RealmCoordinator::get_cached_schema(Schema& schema, uint64_t& schema_version, - uint64_t& transaction) const noexcept -{ - util::CheckedLockGuard lock(m_schema_cache_mutex); - if (!m_cached_schema) - return false; - schema = *m_cached_schema; - schema_version = m_schema_version; - transaction = m_schema_transaction_version_max; - return true; -} - -void RealmCoordinator::cache_schema(Schema const& new_schema, uint64_t new_schema_version, - uint64_t transaction_version) -{ - util::CheckedLockGuard lock(m_schema_cache_mutex); - if (transaction_version < m_schema_transaction_version_max) - return; - if (new_schema.empty() || new_schema_version == ObjectStore::NotVersioned) - return; - - m_cached_schema = new_schema; - m_schema_version = new_schema_version; - m_schema_transaction_version_min = transaction_version; - m_schema_transaction_version_max = transaction_version; -} - -void RealmCoordinator::clear_schema_cache_and_set_schema_version(uint64_t new_schema_version) -{ - util::CheckedLockGuard lock(m_schema_cache_mutex); - m_cached_schema = util::none; - m_schema_version = new_schema_version; -} - -void RealmCoordinator::advance_schema_cache(uint64_t previous, uint64_t next) -{ - util::CheckedLockGuard lock(m_schema_cache_mutex); - if (!m_cached_schema) - return; - REALM_ASSERT(previous <= m_schema_transaction_version_max); - if (next < m_schema_transaction_version_min) - return; - m_schema_transaction_version_min = std::min(previous, m_schema_transaction_version_min); - m_schema_transaction_version_max = std::max(next, m_schema_transaction_version_max); -} - -RealmCoordinator::RealmCoordinator() -#if REALM_ENABLE_SYNC -: m_partial_sync_work_queue(std::make_unique<_impl::partial_sync::WorkQueue>()) -#endif -{ -} - -RealmCoordinator::~RealmCoordinator() -{ - { - std::lock_guard<std::mutex> 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; - } - } - } - // Waits for the worker thread to join - m_notifier = nullptr; - - // Ensure the notifiers aren't holding on to Transactions after we destroy - // the History object the DB depends on - // No locking needed here because the worker thread is gone - for (auto& notifier : m_new_notifiers) - notifier->release_data(); - for (auto& notifier : m_notifiers) - notifier->release_data(); -} - -void RealmCoordinator::unregister_realm(Realm* realm) -{ - // Normally results notifiers are cleaned up by the background worker thread - // but if that's disabled we need to ensure that any notifiers from this - // Realm get cleaned up - if (!m_config.automatic_change_notifications) { - util::CheckedLockGuard lock(m_notifier_mutex); - clean_up_dead_notifiers(); - } - { - util::CheckedLockGuard 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)); - } -} - -// Thread-safety analsys doesn't reasonably handle calling functions on different -// instances of this type -void RealmCoordinator::clear_cache() NO_THREAD_SAFETY_ANALYSIS -{ - std::vector<std::shared_ptr<Realm>> realms_to_close; - std::vector<std::shared_ptr<RealmCoordinator>> coordinators; - { - std::lock_guard<std::mutex> lock(s_coordinator_mutex); - - for (auto& weak_coordinator : s_coordinators_per_path) { - auto coordinator = weak_coordinator.second.lock(); - if (!coordinator) { - continue; - } - coordinators.push_back(coordinator); - - coordinator->m_notifier = nullptr; - - // Gather a list of all of the realms which will be removed - CheckedLockGuard lock(coordinator->m_realm_mutex); - 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(); - } - coordinators.clear(); - - // Close all of the previously cached Realms. This can't be done while - // s_coordinator_mutex is held as it may try to re-lock it. - for (auto& realm : realms_to_close) - realm->close(); -} - -void RealmCoordinator::clear_all_caches() -{ - std::vector<std::weak_ptr<RealmCoordinator>> to_clear; - { - std::lock_guard<std::mutex> 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::assert_no_open_realms() noexcept -{ -#ifdef REALM_DEBUG - std::lock_guard<std::mutex> lock(s_coordinator_mutex); - REALM_ASSERT(s_coordinators_per_path.empty()); -#endif -} - -void RealmCoordinator::wake_up_notifier_worker() -{ - if (m_notifier) { - // FIXME: this wakes up the notification workers for all processes and - // not just us. This might be worth optimizing in the future. - m_notifier->notify_others(); - } -} - -void RealmCoordinator::commit_write(Realm& realm) -{ - REALM_ASSERT(!m_config.immutable()); - REALM_ASSERT(realm.is_in_transaction()); - - Transaction& tr = Realm::Internal::get_transaction(realm); - { - // Need to acquire this lock before committing or another process could - // perform a write and notify us before we get the chance to set the - // skip version - util::CheckedLockGuard l(m_notifier_mutex); - - tr.commit_and_continue_as_read(); - - // Don't need to check m_new_notifiers because those don't skip versions - bool have_notifiers = std::any_of(m_notifiers.begin(), m_notifiers.end(), - [&](auto&& notifier) { return notifier->is_for_realm(realm); }); - if (have_notifiers) { - m_notifier_skip_version = Realm::Internal::get_transaction(realm).get_version_of_current_transaction(); - } - } - -#if REALM_ENABLE_SYNC - // Realm could be closed in did_change. So send sync notification first before did_change. - if (m_sync_session) { - auto version = tr.get_version(); - SyncSession::Internal::nonsync_transact_notify(*m_sync_session, version); - } -#endif - if (realm.m_binding_context) { - realm.m_binding_context->did_change({}, {}); - } - - if (m_notifier) { - m_notifier->notify_others(); - } -} - -void RealmCoordinator::enable_wait_for_change() -{ - m_db->enable_wait_for_change(); -} - -bool RealmCoordinator::wait_for_change(std::shared_ptr<Transaction> tr) -{ - return m_db->wait_for_change(tr); -} - -void RealmCoordinator::wait_for_change_release() -{ - m_db->wait_for_change_release(); -} - -void RealmCoordinator::pin_version(VersionID versionid) -{ - if (m_async_error) - return; - if (!m_advancer_sg || versionid < m_advancer_sg->get_version_of_current_transaction()) - m_advancer_sg = m_db->start_read(versionid); -} - -// Thread-safety analsys doesn't reasonably handle calling functions on different -// instances of this type -void RealmCoordinator::register_notifier(std::shared_ptr<CollectionNotifier> notifier) NO_THREAD_SAFETY_ANALYSIS -{ - auto version = notifier->version(); - auto& self = Realm::Internal::get_coordinator(*notifier->get_realm()); - { - util::CheckedLockGuard lock(self.m_notifier_mutex); - self.pin_version(version); - 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) && m_notifiers.empty()) { - m_notifier_sg = nullptr; - m_notifier_skip_version = {0, 0}; - } - if (swap_remove(m_new_notifiers) && m_new_notifiers.empty()) { - m_advancer_sg = nullptr; - } -} - -void RealmCoordinator::on_change() -{ - run_async_notifiers(); - - util::CheckedLockGuard lock(m_realm_mutex); - for (auto& realm : m_weak_realm_notifiers) { - realm.notify(); - } -} - -namespace { -class IncrementalChangeInfo { -public: - IncrementalChangeInfo(Transaction& sg, - std::vector<std::shared_ptr<_impl::CollectionNotifier>>& 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(VersionID version) - { - if (version != m_sg.get_version_of_current_transaction()) { - transaction::advance(m_sg, *m_current, version); - m_info.push_back({std::move(m_current->lists)}); - auto next = &m_info.back(); - for (auto& table : m_current->tables) - next->tables[table.first]; - m_current = next; - return true; - } - return false; - } - - void advance_to_final(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 (auto& ct : cur.tables) { - auto& pt = prev.tables[ct.first]; - if (pt.empty()) - pt = ct.second; - else - pt.merge(ObjectChangeSet{ct.second}); - } - } - - // Copy the list change info if there are multiple LinkViews for the same LinkList - auto id = [](auto const& list) { return std::tie(list.table_key, list.col_key, list.row_key); }; - 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<TransactionChangeInfo> m_info; - TransactionChangeInfo* m_current = nullptr; - Transaction& m_sg; -}; -} // anonymous namespace - -void RealmCoordinator::run_async_notifiers() -{ - util::CheckedUniqueLock lock(m_notifier_mutex); - - clean_up_dead_notifiers(); - - if (m_notifiers.empty() && m_new_notifiers.empty()) { - m_notifier_cv.notify_all(); - return; - } - - if (!m_notifier_sg) { - m_notifier_sg = m_db->start_read(); - } - - if (m_async_error) { - std::move(m_new_notifiers.begin(), m_new_notifiers.end(), std::back_inserter(m_notifiers)); - m_new_notifiers.clear(); - m_notifier_cv.notify_all(); - return; - } - - 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); - auto advancer_sg = std::move(m_advancer_sg); - - if (!new_notifiers.empty()) { - REALM_ASSERT(advancer_sg); - REALM_ASSERT_3(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(*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(advancer_sg); - notifier->add_required_change_info(new_notifier_change_info.current()); - } - new_notifier_change_info.advance_to_final(VersionID{}); - - // We want to advance the non-new notifiers to the same version as the - // new notifiers to avoid having to merge changes from any new - // transaction that happen immediately after this into the new notifier - // changes - version = advancer_sg->get_version_of_current_transaction(); - } - else { - // If we have no new notifiers we want to just advance to the latest - // version, but we have to pick a "latest" version while holding the - // notifier lock to avoid advancing over a transaction which should be - // skipped - // FIXME: this is comically slow - version = m_db->start_read()->get_version_of_current_transaction(); - } - - auto skip_version = m_notifier_skip_version; - m_notifier_skip_version = {0, 0}; - - // 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; - m_notifiers.insert(m_notifiers.end(), new_notifiers.begin(), new_notifiers.end()); - lock.unlock(); - - if (skip_version.version) { - REALM_ASSERT(!notifiers.empty()); - REALM_ASSERT(version >= skip_version); - IncrementalChangeInfo change_info(*m_notifier_sg, notifiers); - for (auto& notifier : notifiers) - notifier->add_required_change_info(change_info.current()); - change_info.advance_to_final(skip_version); - - for (auto& notifier : notifiers) - notifier->run(); - - util::CheckedLockGuard lock(m_notifier_mutex); - for (auto& notifier : notifiers) - notifier->prepare_handover(); - } - - // 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); - notifier->run(); - } - - // 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 - util::CheckedLockGuard lock2(m_notifier_mutex); - for (auto& notifier : new_notifiers) { - notifier->prepare_handover(); - } - for (auto& notifier : notifiers) { - notifier->prepare_handover(); - } - clean_up_dead_notifiers(); - m_notifier_cv.notify_all(); -} - -bool RealmCoordinator::can_advance(Realm& realm) -{ - bool changes = realm.last_seen_transaction_version() != m_db->get_version_of_latest_snapshot(); - return changes; -} - -void RealmCoordinator::advance_to_ready(Realm& realm) -{ - util::CheckedUniqueLock lock(m_notifier_mutex); - _impl::NotifierPackage notifiers(m_async_error, notifiers_for_realm(realm), this); - lock.unlock(); - notifiers.package_and_wait(util::none); - - // FIXME: we probably won't actually want a strong pointer here - auto sg = Realm::Internal::get_transaction_ref(realm); - if (notifiers) { - auto version = notifiers.version(); - if (version) { - auto current_version = sg->get_version_of_current_transaction(); - // Notifications are out of date, so just discard - // This should only happen if begin_read() was used to change the - // read version outside of our control - if (*version < current_version) - return; - // While there is a newer version, notifications are for the current - // version so just deliver them without advancing - if (*version == current_version) { - if (realm.m_binding_context) - realm.m_binding_context->will_send_notifications(); - notifiers.after_advance(); - if (realm.m_binding_context) - realm.m_binding_context->did_send_notifications(); - return; - } - } - } - - transaction::advance(sg, realm.m_binding_context.get(), notifiers); -} - -std::vector<std::shared_ptr<_impl::CollectionNotifier>> RealmCoordinator::notifiers_for_realm(Realm& realm) -{ - std::vector<std::shared_ptr<_impl::CollectionNotifier>> ret; - for (auto& notifier : m_new_notifiers) { - if (notifier->is_for_realm(realm)) - ret.push_back(notifier); - } - for (auto& notifier : m_notifiers) { - if (notifier->is_for_realm(realm)) - ret.push_back(notifier); - } - return ret; -} - -bool RealmCoordinator::advance_to_latest(Realm& realm) -{ - // FIXME: we probably won't actually want a strong pointer here - auto self = shared_from_this(); - auto sg = Realm::Internal::get_transaction_ref(realm); - util::CheckedUniqueLock lock(m_notifier_mutex); - _impl::NotifierPackage notifiers(m_async_error, notifiers_for_realm(realm), this); - lock.unlock(); - notifiers.package_and_wait(sg->get_version_of_latest_snapshot()); - - auto version = sg->get_version_of_current_transaction(); - transaction::advance(sg, realm.m_binding_context.get(), notifiers); - - // Realm could be closed in the callbacks. - if (realm.is_closed()) - return false; - - return version != sg->get_version_of_current_transaction(); -} - -void RealmCoordinator::promote_to_write(Realm& realm) -{ - REALM_ASSERT(!realm.is_in_transaction()); - - util::CheckedUniqueLock lock(m_notifier_mutex); - _impl::NotifierPackage notifiers(m_async_error, notifiers_for_realm(realm), this); - lock.unlock(); - - // FIXME: we probably won't actually want a strong pointer here - auto tr = Realm::Internal::get_transaction_ref(realm); - transaction::begin(tr, realm.m_binding_context.get(), notifiers); -} - -void RealmCoordinator::process_available_async(Realm& realm) -{ - REALM_ASSERT(!realm.is_in_transaction()); - - util::CheckedUniqueLock lock(m_notifier_mutex); - auto notifiers = notifiers_for_realm(realm); - if (notifiers.empty()) - return; - - if (auto error = m_async_error) { - lock.unlock(); - if (realm.m_binding_context) - realm.m_binding_context->will_send_notifications(); - for (auto& notifier : notifiers) - notifier->deliver_error(m_async_error); - if (realm.m_binding_context) - realm.m_binding_context->did_send_notifications(); - return; - } - - bool in_read = realm.is_in_read_transaction(); - auto& sg = Realm::Internal::get_transaction(realm); - auto version = sg.get_version_of_current_transaction(); - auto package = [&](auto& notifier) { - return !(notifier->has_run() && (!in_read || notifier->version() == version) && notifier->package_for_delivery()); - }; - notifiers.erase(std::remove_if(begin(notifiers), end(notifiers), package), end(notifiers)); - if (notifiers.empty()) - return; - lock.unlock(); - - // no before advance because the Realm is already at the given version, - // because we're either sending initial notifications or the write was - // done on this Realm instance - - if (realm.m_binding_context) { - realm.m_binding_context->will_send_notifications(); - if (realm.is_closed()) // i.e. the Realm was closed in the callback above - return; - } - - for (auto& notifier : notifiers) - notifier->after_advance(); - - if (realm.m_binding_context) - realm.m_binding_context->did_send_notifications(); -} - -void RealmCoordinator::set_transaction_callback(std::function<void(VersionID, VersionID)> fn) -{ - create_sync_session(false); - util::CheckedLockGuard lock(m_transaction_callback_mutex); - m_transaction_callback = std::move(fn); -} - -bool RealmCoordinator::compact() -{ - return m_db->compact(); -} - -#if REALM_ENABLE_SYNC -_impl::partial_sync::WorkQueue& RealmCoordinator::partial_sync_work_queue() -{ - return *m_partial_sync_work_queue; -} -#endif diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/realm_coordinator.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/realm_coordinator.hpp deleted file mode 100644 index 23100b60a..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/realm_coordinator.hpp +++ /dev/null @@ -1,274 +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 "util/checked_mutex.hpp" - -#include <realm/version_id.hpp> - -#include <condition_variable> -#include <mutex> - -namespace realm { -class DB; -class Replication; -class Schema; -class StringData; -class SyncSession; -class Transaction; - -namespace _impl { -class CollectionNotifier; -class ExternalCommitHelper; -class WeakRealmNotifier; - -namespace partial_sync { -class WorkQueue; -} - -// 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<RealmCoordinator> { -public: - // Get the coordinator for the given path, creating it if neccesary - static std::shared_ptr<RealmCoordinator> get_coordinator(StringData path); - // Get the coordinator for the given config, creating it if neccesary - static std::shared_ptr<RealmCoordinator> get_coordinator(const Realm::Config&); - // Get the coordinator for the given path, or null if there is none - static std::shared_ptr<RealmCoordinator> get_existing_coordinator(StringData path); - - // Get a shared Realm with the given configuration - // If the Realm is already opened on another thread, validate that the given - // configuration is compatible with the existing one. - // If no version is provided a live thread-confined Realm is returned. - // Otherwise, a frozen Realm at the given version is returned. This - // can be read from any thread. - std::shared_ptr<Realm> get_realm(Realm::Config config, util::Optional<VersionID> version) REQUIRES(!m_realm_mutex, !m_schema_cache_mutex); - std::shared_ptr<Realm> get_realm(std::shared_ptr<util::Scheduler> = nullptr) REQUIRES(!m_realm_mutex, !m_schema_cache_mutex); -#if REALM_ENABLE_SYNC - // Get a thread-local shared Realm with the given configuration - // If the Realm is not already present, it will be fully downloaded before being returned. - // If the Realm is already on disk, it will be fully synchronized before being returned. - // Timeouts and interruptions are not handled by this method and must be handled by upper layers. - std::shared_ptr<AsyncOpenTask> get_synchronized_realm(Realm::Config config) - REQUIRES(!m_realm_mutex, !m_schema_cache_mutex); - // Used by GlobalNotifier to bypass the normal initialization path - void open_with_config(Realm::Config config) REQUIRES(!m_realm_mutex, !m_schema_cache_mutex); - - // Creates the underlying sync session if it doesn't already exists. - // This is also created as part of opening a Realm, so only use this - // method if the session needs to exist before the Realm does. - void create_session(const Realm::Config& config) REQUIRES(!m_realm_mutex, !m_schema_cache_mutex); -#endif - - // Get a Realm which is not bound to the current execution context - ThreadSafeReference get_unbound_realm() REQUIRES(!m_realm_mutex); - - // Bind an unbound Realm to a specific execution context. The Realm must - // be managed by this coordinator. - void bind_to_context(Realm& realm) REQUIRES(!m_realm_mutex); - - Realm::Config get_config() const { return m_config; } - - uint64_t get_schema_version() const noexcept REQUIRES(!m_schema_cache_mutex); - const std::string& get_path() const noexcept { return m_config.path; } - const std::vector<char>& get_encryption_key() const noexcept { return m_config.encryption_key; } - bool is_in_memory() const noexcept { return m_config.in_memory; } - // Returns the number of versions in the Realm file. - uint_fast64_t get_number_of_versions() const { return m_db->get_number_of_versions(); }; - - // To avoid having to re-read and validate the file's schema every time a - // new read transaction is begun, RealmCoordinator maintains a cache of the - // most recently seen file schema and the range of transaction versions - // which it applies to. Note that this schema may not be identical to that - // of any Realm instances managed by this coordinator, as individual Realms - // may only be using a subset of it. - - // Get the latest cached schema and the transaction version which it applies - // to. Returns false if there is no cached schema. - bool get_cached_schema(Schema& schema, uint64_t& schema_version, uint64_t& transaction) const noexcept REQUIRES(!m_schema_cache_mutex); - - // Cache the state of the schema at the given transaction version - void cache_schema(Schema const& new_schema, uint64_t new_schema_version, - uint64_t transaction_version) REQUIRES(!m_schema_cache_mutex); - // If there is a schema cached for transaction version `previous`, report - // that it is still valid at transaction version `next` - void advance_schema_cache(uint64_t previous, uint64_t next) REQUIRES(!m_schema_cache_mutex); - void clear_schema_cache_and_set_schema_version(uint64_t new_schema_version) REQUIRES(!m_schema_cache_mutex); - - - // Asynchronously call notify() on every Realm instance for this coordinator's - // path, including those in other processes - void send_commit_notifications(Realm&); - void wake_up_notifier_worker(); - - // 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(); - - // Verify that there are no Realms open for any paths - static void assert_no_open_realms() noexcept; - - // 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) REQUIRES(!m_realm_mutex, !m_notifier_mutex); - - // Called by m_notifier when there's a new commit to send notifications for - void on_change() REQUIRES(!m_realm_mutex, !m_notifier_mutex); - - static void register_notifier(std::shared_ptr<CollectionNotifier> notifier); - - std::shared_ptr<Group> begin_read(VersionID version={}, bool frozen_transaction = false); - - // Check if advance_to_ready() would actually advance the Realm's read version - bool can_advance(Realm& realm); - - // Advance the Realm to the most recent transaction version which all async - // work is complete for - void advance_to_ready(Realm& realm) REQUIRES(!m_notifier_mutex); - - // Advance the Realm to the most recent transaction version, blocking if - // async notifiers are not yet ready for that version - // returns whether it actually changed the version - bool advance_to_latest(Realm& realm) REQUIRES(!m_notifier_mutex); - - // Deliver any notifications which are ready for the Realm's version - void process_available_async(Realm& realm) REQUIRES(!m_notifier_mutex); - - // Register a function which is called whenever sync makes a write to the Realm - void set_transaction_callback(std::function<void(VersionID, VersionID)>) REQUIRES(!m_transaction_callback_mutex); - - // Deliver notifications for the Realm, blocking if some aren't ready yet - // The calling Realm must be in a write transaction - void promote_to_write(Realm& realm) REQUIRES(!m_notifier_mutex); - - // Commit a Realm's current write transaction and send notifications to all - // other Realm instances for that path, including in other processes - void commit_write(Realm& realm) REQUIRES(!m_notifier_mutex); - - void enable_wait_for_change(); - bool wait_for_change(std::shared_ptr<Transaction> tr); - void wait_for_change_release(); - - void close(); - bool compact(); - - template<typename Pred> - util::CheckedUniqueLock wait_for_notifiers(Pred&& wait_predicate) REQUIRES(!m_notifier_mutex); - -#if REALM_ENABLE_SYNC - // A work queue that can be used to perform background work related to partial sync. - _impl::partial_sync::WorkQueue& partial_sync_work_queue(); -#endif - - AuditInterface* audit_context() const noexcept { return m_audit_context.get(); } - -private: - friend Realm::Internal; - Realm::Config m_config; - std::unique_ptr<Replication> m_history; - std::shared_ptr<DB> m_db; - std::shared_ptr<Group> m_read_only_group; - - mutable util::CheckedMutex m_schema_cache_mutex; - util::Optional<Schema> m_cached_schema GUARDED_BY(m_schema_cache_mutex); - uint64_t m_schema_version GUARDED_BY(m_schema_cache_mutex) = -1; - uint64_t m_schema_transaction_version_min GUARDED_BY(m_schema_cache_mutex) = 0; - uint64_t m_schema_transaction_version_max GUARDED_BY(m_schema_cache_mutex) = 0; - - util::CheckedMutex m_realm_mutex; - std::vector<WeakRealmNotifier> m_weak_realm_notifiers GUARDED_BY(m_realm_mutex); - - util::CheckedMutex m_notifier_mutex; - std::condition_variable m_notifier_cv; - std::vector<std::shared_ptr<_impl::CollectionNotifier>> m_new_notifiers GUARDED_BY(m_notifier_mutex); - std::vector<std::shared_ptr<_impl::CollectionNotifier>> m_notifiers GUARDED_BY(m_notifier_mutex); - VersionID m_notifier_skip_version GUARDED_BY(m_notifier_mutex) = {0, 0}; - - // Transaction used for actually running async notifiers - // Will have a read transaction iff m_notifiers is non-empty - std::shared_ptr<Transaction> m_notifier_sg; - - // Transaction 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::shared_ptr<Transaction> m_advancer_sg; - std::exception_ptr m_async_error; - - std::unique_ptr<_impl::ExternalCommitHelper> m_notifier; - util::CheckedMutex m_transaction_callback_mutex; - std::function<void(VersionID, VersionID)> m_transaction_callback GUARDED_BY(m_transaction_callback_mutex); - -#if REALM_ENABLE_SYNC - std::shared_ptr<SyncSession> m_sync_session; - std::unique_ptr<partial_sync::WorkQueue> m_partial_sync_work_queue; -#endif - - std::shared_ptr<AuditInterface> m_audit_context; - - void open_db(); - - void pin_version(VersionID version) REQUIRES(m_notifier_mutex); - - void set_config(const Realm::Config&) REQUIRES(m_realm_mutex, !m_schema_cache_mutex); - void create_sync_session(bool force_client_resync); - void do_get_realm(Realm::Config config, std::shared_ptr<Realm>& realm, - util::Optional<VersionID> version, - util::CheckedUniqueLock& realm_lock) REQUIRES(m_realm_mutex); - void run_async_notifiers() REQUIRES(!m_notifier_mutex); - void advance_helper_shared_group_to_latest(); - void clean_up_dead_notifiers() REQUIRES(m_notifier_mutex); - - std::vector<std::shared_ptr<_impl::CollectionNotifier>> notifiers_for_realm(Realm&) REQUIRES(m_notifier_mutex); -}; - -void translate_file_exception(StringData path, bool immutable=false); - -template<typename Pred> -util::CheckedUniqueLock RealmCoordinator::wait_for_notifiers(Pred&& wait_predicate) -{ - util::CheckedUniqueLock lock(m_notifier_mutex); - bool first = true; - m_notifier_cv.wait(lock.native_handle(), [&] { - if (wait_predicate()) - return true; - if (first) { - wake_up_notifier_worker(); - first = false; - } - return false; - }); - return lock; -} - -} // namespace _impl -} // namespace realm - -#endif /* REALM_COORDINATOR_HPP */ diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/results_notifier.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/results_notifier.cpp deleted file mode 100644 index 633ded68d..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/results_notifier.cpp +++ /dev/null @@ -1,362 +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" - -#include "shared_realm.hpp" - -#include <numeric> - -using namespace realm; -using namespace realm::_impl; - -// Some of the inter-thread synchronization for this class is handled externally -// by RealmCoordinator using the "notifier lock" which also guards registering -// and unregistering notifiers. This can make it somewhat difficult to tell what -// can safely be accessed where. -// -// The data flow is: -// - ResultsNotifier is created on target thread. -// - On background worker thread: -// * do_attach_to() called with notifier lock held -// - Writes to m_query -// * do_add_required_change_info() called with notifier lock held -// - Writes to m_info -// * run() called with no locks held -// - Reads m_query -// - Reads m_info -// - Reads m_need_to_run <-- FIXME: data race? -// - Writes m_run_tv -// * do_prepare_handover() called with notifier lock held -// - Reads m_run_tv -// - Writes m_handover_transaction -// - Writes m_handover_tv -// - On target thread: -// * prepare_to_deliver() called with notifier lock held -// - Reads m_handover_transaction -// - Reads m_handover_tv -// - Writes m_deliver_transaction -// - Writes m_deliver_handover -// * get_tableview() called with no locks held -// - Reads m_deliver_transaction -// - Reads m_deliver_handover -// - Reads m_results_were_used - -ResultsNotifier::ResultsNotifier(Results& target) -: ResultsNotifierBase(target.get_realm()) -, m_query(std::make_unique<Query>(target.get_query())) -, m_descriptor_ordering(target.get_descriptor_ordering()) -, m_target_is_in_table_order(target.is_in_table_order()) -{ - auto table = m_query->get_table(); - if (table) { - set_table(table); - } -} - -void ResultsNotifier::release_data() noexcept -{ - m_query = {}; - m_run_tv = {}; - m_handover_tv = {}; - m_handover_transaction = {}; - m_delivered_tv = {}; - m_delivered_transaction = {}; - CollectionNotifier::release_data(); -} - -bool ResultsNotifier::get_tableview(TableView& out) -{ - if (!m_delivered_tv) - return false; - auto& transaction = source_shared_group(); - if (m_delivered_transaction->get_version_of_current_transaction() != transaction.get_version_of_current_transaction()) - return false; - - out = std::move(*transaction.import_copy_of(*m_delivered_tv, PayloadPolicy::Move)); - m_delivered_tv.reset(); - return true; -} - -bool ResultsNotifier::do_add_required_change_info(TransactionChangeInfo& info) -{ - m_info = &info; - return m_query->get_table() && has_run() && have_callbacks(); -} - -bool ResultsNotifier::need_to_run() -{ - REALM_ASSERT(m_info); - - { - 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_results_were_used)) - return false; - } - - // If we've run previously, check if we need to rerun - if (has_run() && m_query->sync_view_if_needed() == m_last_seen_version) { - // Does m_last_seen_version match m_related_tables - if (all_related_tables_covered(m_last_seen_version)) { - return false; - } - } - return true; -} - -void ResultsNotifier::calculate_changes() -{ - if (has_run() && have_callbacks()) { - std::vector<int64_t> next_rows; - next_rows.reserve(m_run_tv.size()); - for (size_t i = 0; i < m_run_tv.size(); ++i) - next_rows.push_back(m_run_tv.get_key(i).value); - - m_change = CollectionChangeBuilder::calculate(m_previous_rows, next_rows, - get_modification_checker(*m_info, m_query->get_table()), - m_target_is_in_table_order); - - m_previous_rows = std::move(next_rows); - } - else { - m_previous_rows.resize(m_run_tv.size()); - for (size_t i = 0; i < m_run_tv.size(); ++i) - m_previous_rows[i] = m_run_tv.get_key(i).value; - } -} - -void ResultsNotifier::run() -{ - // Table's been deleted, so report all rows as deleted - if (!m_query->get_table()) { - m_change = {}; - m_change.deletions.set(m_previous_rows.size()); - m_previous_rows.clear(); - return; - } - - if (!need_to_run()) - return; - - m_query->sync_view_if_needed(); - m_run_tv = m_query->find_all(); - m_run_tv.apply_descriptor_ordering(m_descriptor_ordering); - m_run_tv.sync_if_needed(); - m_last_seen_version = m_run_tv.ObjList::get_dependency_versions(); - - calculate_changes(); -} - -void ResultsNotifier::do_prepare_handover(Transaction& sg) -{ - m_handover_tv.reset(); - if (m_handover_transaction) - m_handover_transaction->advance_read(sg.get_version_of_current_transaction()); - - if (m_run_tv.is_attached()) { - REALM_ASSERT(m_run_tv.is_in_sync()); - if (!m_handover_transaction) - m_handover_transaction = sg.duplicate(); - m_handover_tv = m_run_tv.clone_for_handover(m_handover_transaction.get(), PayloadPolicy::Move); - m_run_tv = {}; - } -} - -bool ResultsNotifier::prepare_to_deliver() -{ - auto lock = lock_target(); - auto realm = get_realm(); - if (!realm) { - m_handover_tv.reset(); - m_delivered_tv.reset(); - return false; - } - if (!m_handover_tv) { - bool transaction_is_stale = m_delivered_transaction && - (!realm->is_in_read_transaction() || realm->read_transaction_version() > m_delivered_transaction->get_version_of_current_transaction()); - if (transaction_is_stale) { - m_delivered_tv.reset(); - m_delivered_transaction.reset(); - } - return true; - } - - m_results_were_used = !m_delivered_tv; - m_delivered_tv.reset(); - if (m_delivered_transaction) - m_delivered_transaction->advance_read(m_handover_transaction->get_version_of_current_transaction()); - else - m_delivered_transaction = m_handover_transaction->duplicate(); - m_delivered_tv = m_delivered_transaction->import_copy_of(*m_handover_tv, PayloadPolicy::Move); - m_handover_tv.reset(); - - return true; -} - -void ResultsNotifier::do_attach_to(Transaction& sg) -{ - if (m_query->get_table()) - m_query = sg.import_copy_of(*m_query, PayloadPolicy::Move); -} - -ListResultsNotifier::ListResultsNotifier(Results& target) -: ResultsNotifierBase(target.get_realm()) -, m_list(target.get_list()) -{ - auto& ordering = target.get_descriptor_ordering(); - for (size_t i = 0, sz = ordering.size(); i < sz; i++) { - auto descr = ordering[i]; - if (descr->get_type() == DescriptorType::Sort) - m_sort_order = static_cast<const SortDescriptor*>(descr)->is_ascending(0); - if (descr->get_type() == DescriptorType::Distinct) - m_distinct = true; - } - -} - -void ListResultsNotifier::release_data() noexcept -{ - m_list = {}; - CollectionNotifier::release_data(); -} - -bool ListResultsNotifier::get_list_indices(ListIndices& out) -{ - if (!m_delivered_indices) - return false; - auto& transaction = source_shared_group(); - if (m_delivered_transaction_version != transaction.get_version_of_current_transaction()) - return false; - - out = std::move(m_delivered_indices); - m_delivered_indices = util::none; - return true; -} - -bool ListResultsNotifier::do_add_required_change_info(TransactionChangeInfo& info) -{ - if (!m_list->is_attached()) - return false; // origin row was deleted after the notification was added - - info.lists.push_back({m_list->get_table()->get_key(), m_list->get_key().value, - m_list->get_col_key().value, &m_change}); - - m_info = &info; - return true; -} - -bool ListResultsNotifier::need_to_run() -{ - REALM_ASSERT(m_info); - - { - 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_results_were_used)) - return false; - } - - return !has_run() || m_list->has_changed(); -} - -void ListResultsNotifier::calculate_changes() -{ - // Unsorted lists can just forward the changeset directly from the - // transaction log parsing, but sorted lists need to perform diffing - if (has_run() && have_callbacks() && (m_sort_order || m_distinct)) { - // Update each of the row indices in m_previous_indices to the equivalent - // new index in the new list - if (!m_change.insertions.empty() || !m_change.deletions.empty()) { - for (auto& row : m_previous_indices) { - if (m_change.deletions.contains(row)) - row = npos; - else - row = m_change.insertions.shift(m_change.deletions.unshift(row)); - } - } - - m_change = CollectionChangeBuilder::calculate(m_previous_indices, *m_run_indices, - [=](int64_t key) { - return m_change.modifications_new.contains(static_cast<size_t>(key)); - }); - } - - m_previous_indices = *m_run_indices; -} - -void ListResultsNotifier::run() -{ - if (!m_list->is_attached()) { - // List was deleted, so report all of the rows being removed - m_change = {}; - m_change.deletions.set(m_previous_indices.size()); - m_previous_indices.clear(); - return; - } - - if (!need_to_run()) - return; - - m_run_indices = std::vector<size_t>(); - if (m_distinct) - m_list->distinct(*m_run_indices, m_sort_order); - else if (m_sort_order) - m_list->sort(*m_run_indices, *m_sort_order); - else { - m_run_indices->resize(m_list->size()); - std::iota(m_run_indices->begin(), m_run_indices->end(), 0); - } - - calculate_changes(); -} - -void ListResultsNotifier::do_prepare_handover(Transaction& sg) -{ - if (m_run_indices) { - m_handover_indices = std::move(m_run_indices); - m_run_indices = {}; - } - else { - m_handover_indices = {}; - } - m_handover_transaction_version = sg.get_version_of_current_transaction(); -} - -bool ListResultsNotifier::prepare_to_deliver() -{ - auto lock = lock_target(); - if (!get_realm()) { - return false; - } - if (!m_handover_indices) - return true; - - m_results_were_used = !m_delivered_indices; - m_delivered_indices = std::move(m_handover_indices); - m_delivered_transaction_version = m_handover_transaction_version; - m_handover_indices = {}; - - return true; -} - -void ListResultsNotifier::do_attach_to(Transaction& sg) -{ - if (m_list->is_attached()) - m_list = sg.import_copy_of(*m_list); -} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/results_notifier.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/results_notifier.hpp deleted file mode 100644 index e48d92850..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/results_notifier.hpp +++ /dev/null @@ -1,117 +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 <realm/db.hpp> - -namespace realm { -namespace _impl { -class ResultsNotifierBase : public CollectionNotifier { -public: - using ListIndices = util::Optional<std::vector<size_t>>; - using CollectionNotifier::CollectionNotifier; - - virtual bool get_tableview(TableView&) { return false; } - virtual bool get_list_indices(ListIndices&) { return false; } -}; - -class ResultsNotifier : public ResultsNotifierBase { -public: - ResultsNotifier(Results& target); - bool get_tableview(TableView& out) override; - -private: - std::unique_ptr<Query> m_query; - DescriptorOrdering m_descriptor_ordering; - bool m_target_is_in_table_order; - - // 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_run_tv; - - TransactionRef m_handover_transaction; - std::unique_ptr<TableView> m_handover_tv; - TransactionRef m_delivered_transaction; - std::unique_ptr<TableView> m_delivered_tv; - - // 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. - TableVersions m_last_seen_version; - - // The rows from the previous run of the query, for calculating diffs - std::vector<int64_t> m_previous_rows; - - TransactionChangeInfo* m_info = nullptr; - bool m_results_were_used = true; - - bool need_to_run(); - void calculate_changes(); - - void run() override; - void do_prepare_handover(Transaction&) override; - bool do_add_required_change_info(TransactionChangeInfo& info) override; - bool prepare_to_deliver() override; - - void release_data() noexcept override; - void do_attach_to(Transaction& sg) override; -}; - -class ListResultsNotifier : public ResultsNotifierBase { -public: - ListResultsNotifier(Results& target); - bool get_list_indices(ListIndices& out) override; - -private: - std::shared_ptr<LstBase> m_list; - util::Optional<bool> m_sort_order; - bool m_distinct = false; - - ListIndices m_run_indices; - - VersionID m_handover_transaction_version; - ListIndices m_handover_indices; - VersionID m_delivered_transaction_version; - ListIndices m_delivered_indices; - - // The rows from the previous run of the query, for calculating diffs - std::vector<size_t> m_previous_indices; - - TransactionChangeInfo* m_info = nullptr; - bool m_results_were_used = true; - - bool need_to_run(); - void calculate_changes(); - - void run() override; - void do_prepare_handover(Transaction&) override; - bool do_add_required_change_info(TransactionChangeInfo& info) override; - bool prepare_to_deliver() override; - - void release_data() noexcept override; - void do_attach_to(Transaction& sg) override; -}; - -} // namespace _impl -} // namespace realm - -#endif /* REALM_RESULTS_NOTIFIER_HPP */ diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/transact_log_handler.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/transact_log_handler.cpp deleted file mode 100644 index 918326fe8..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/transact_log_handler.cpp +++ /dev/null @@ -1,564 +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 "shared_realm.hpp" - -#include <realm/db.hpp> - -#include <algorithm> -#include <numeric> - -using namespace realm; - -namespace { - -class KVOAdapter : public _impl::TransactionChangeInfo { -public: - KVOAdapter(std::vector<BindingContext::ObserverState>& observers, BindingContext* context); - - void before(Transaction& sg); - void after(Transaction& sg); - -private: - BindingContext* m_context; - std::vector<BindingContext::ObserverState>& m_observers; - std::vector<void *> m_invalidated; - - struct ListInfo { - BindingContext::ObserverState* observer; - _impl::CollectionChangeBuilder builder; - ColKey col; - }; - std::vector<ListInfo> m_lists; - VersionID m_version; -}; - -KVOAdapter::KVOAdapter(std::vector<BindingContext::ObserverState>& observers, BindingContext* context) -: _impl::TransactionChangeInfo{} -, m_context(context) -, m_observers(observers) -{ - if (m_observers.empty()) - return; - - std::vector<TableKey> tables_needed; - for (auto& observer : observers) { - tables_needed.push_back(observer.table_key); - } - std::sort(begin(tables_needed), end(tables_needed)); - tables_needed.erase(std::unique(begin(tables_needed), end(tables_needed)), - end(tables_needed)); - - auto realm = context->realm.lock(); - auto& group = realm->read_group(); - for (auto& observer : observers) { - auto table = group.get_table(TableKey(observer.table_key)); - for (auto key : table->get_column_keys()) { - if (table->get_column_attr(key).test(col_attr_List)) - m_lists.push_back({&observer, {}, key}); - } - } - - tables.reserve(tables_needed.size()); - for (auto& tbl : tables_needed) - tables[tbl.value] = {}; - for (auto& list : m_lists) - lists.push_back({list.observer->table_key, - list.observer->obj_key, list.col.value, &list.builder}); -} - -void KVOAdapter::before(Transaction& sg) -{ - if (!m_context) - return; - - m_version = sg.get_version_of_current_transaction(); - if (tables.empty()) - return; - - for (auto& observer : m_observers) { - auto it = tables.find(observer.table_key.value); - if (it == tables.end()) - continue; - - auto const& table = it->second; - auto key = observer.obj_key; - if (table.deletions_contains(key)) { - m_invalidated.push_back(observer.info); - continue; - } - auto column_modifications = table.get_columns_modified(key); - if (column_modifications) { - for (auto col : *column_modifications) { - observer.changes[col].kind = BindingContext::ColumnInfo::Kind::Set; - } - } - } - - for (auto& list : m_lists) { - if (list.builder.empty()) { - // We may have pre-emptively marked the column as modified if the - // LinkList was selected but the actual changes made ended up being - // a no-op - list.observer->changes.erase(list.col.value); - continue; - } - // If the containing row was deleted then changes will be empty - if (list.observer->changes.empty()) { - REALM_ASSERT_DEBUG(tables[list.observer->table_key.value].deletions_contains(list.observer->obj_key)); - continue; - } - // otherwise the column should have been marked as modified - auto it = list.observer->changes.find(list.col.value); - REALM_ASSERT(it != list.observer->changes.end()); - auto& builder = list.builder; - auto& changes = it->second; - - builder.modifications.remove(builder.insertions); - - // KVO can't express moves (becaue NSArray doesn't have them), so - // transform them into a series of sets on each affected index when possible - if (!builder.moves.empty() && builder.insertions.count() == builder.moves.size() && builder.deletions.count() == builder.moves.size()) { - changes.kind = BindingContext::ColumnInfo::Kind::Set; - changes.indices = builder.modifications; - changes.indices.add(builder.deletions); - - // Iterate over each of the rows which may have been shifted by - // the moves and check if it actually has been, or if it's ended - // up in the same place as it started (either because the moves were - // actually a swap that doesn't effect the rows in between, or the - // combination of moves happen to leave some intermediate rows in - // the same place) - auto in_range = [](auto& it, auto end, size_t i) { - if (it != end && i >= it->second) - ++it; - return it != end && i >= it->first && i < it->second; - }; - - auto del_it = builder.deletions.begin(), del_end = builder.deletions.end(); - auto ins_it = builder.insertions.begin(), ins_end = builder.insertions.end(); - size_t start = std::min(ins_it->first, del_it->first); - size_t end = std::max(std::prev(ins_end)->second, std::prev(del_end)->second); - ptrdiff_t shift = 0; - for (size_t i = start; i < end; ++i) { - if (in_range(del_it, del_end, i)) - --shift; - else if (in_range(ins_it, ins_end, i + shift)) - ++shift; - if (shift != 0) - changes.indices.add(i); - } - } - // KVO can't express multiple types of changes at once - else if (builder.insertions.empty() + builder.modifications.empty() + builder.deletions.empty() < 2) { - changes.kind = BindingContext::ColumnInfo::Kind::SetAll; - } - else if (!builder.insertions.empty()) { - changes.kind = BindingContext::ColumnInfo::Kind::Insert; - changes.indices = builder.insertions; - } - else if (!builder.modifications.empty()) { - changes.kind = BindingContext::ColumnInfo::Kind::Set; - changes.indices = builder.modifications; - } - else { - REALM_ASSERT(!builder.deletions.empty()); - changes.kind = BindingContext::ColumnInfo::Kind::Remove; - changes.indices = builder.deletions; - } - } - m_context->will_change(m_observers, m_invalidated); -} - -void KVOAdapter::after(Transaction& sg) -{ - if (!m_context) - return; - m_context->did_change(m_observers, m_invalidated, - m_version != VersionID{} && - m_version != sg.get_version_of_current_transaction()); -} - -class TransactLogValidationMixin { - // The currently selected table - TableKey m_current_table; - - REALM_NORETURN - REALM_NOINLINE - void schema_error() - { - throw _impl::UnsupportedSchemaChange(); - } - -protected: - TableKey current_table() const noexcept { return m_current_table; } - -public: - - bool select_table(TableKey key) noexcept - { - m_current_table = key; - return true; - } - - // Removing or renaming things while a Realm is open is never supported - bool erase_group_level_table(TableKey) { schema_error(); } - bool rename_group_level_table(TableKey) { schema_error(); } - bool erase_column(ColKey) { schema_error(); } - bool rename_column(ColKey) { schema_error(); } - - // Additive changes and reorderings are supported - bool insert_group_level_table(TableKey) { return true; } - bool insert_column(ColKey) { return true; } - bool set_link_type(ColKey) { return true; } - - // Non-schema changes are all allowed - void parse_complete() { } - bool create_object(ObjKey) { return true; } - bool remove_object(ObjKey) { return true; } - bool clear_table(size_t=0) noexcept { return true; } - bool list_set(size_t) { return true; } - bool list_insert(size_t) { return true; } - bool list_erase(size_t) { return true; } - bool list_clear(size_t) { return true; } - bool list_move(size_t, size_t) { return true; } - bool list_swap(size_t, size_t) { return true; } -}; - - -// A transaction log handler that just validates that all operations made are -// ones supported by the object store -struct TransactLogValidator : public TransactLogValidationMixin { - bool modify_object(ColKey, ObjKey) { return true; } - bool select_list(ColKey, ObjKey) { return true; } -}; - -// Extends TransactLogValidator to track changes made to LinkViews -class TransactLogObserver : public TransactLogValidationMixin { - _impl::TransactionChangeInfo& m_info; - _impl::CollectionChangeBuilder* m_active_list = nullptr; - ObjectChangeSet* m_active_table = nullptr; - - _impl::CollectionChangeBuilder* find_list(ObjKey obj, ColKey col) - { - // 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 - auto table = current_table(); - for (auto it = m_info.lists.rbegin(), end = m_info.lists.rend(); it != end; ++it) { - if (it->table_key == table && it->row_key == obj.value && it->col_key == col.value) - return it->changes; - } - return nullptr; - } - -public: - TransactLogObserver(_impl::TransactionChangeInfo& info) - : m_info(info) { } - - void parse_complete() - { - for (auto& list : m_info.lists) - list.changes->clean_up_stale_moves(); - for (auto it = m_info.tables.begin(); it != m_info.tables.end(); ) { - if (it->second.empty()) - it = m_info.tables.erase(it); - else - ++it; - } - } - - bool select_table(TableKey key) noexcept - { - TransactLogValidationMixin::select_table(key); - - TableKey table_key = current_table(); - if (m_info.track_all) - m_active_table = &m_info.tables[table_key.value]; - else { - auto it = m_info.tables.find(table_key.value); - if (it == m_info.tables.end()) - m_active_table = nullptr; - else - m_active_table = &it->second; - } - return true; - } - - bool select_list(ColKey col, ObjKey obj) - { - modify_object(col, obj); - m_active_list = find_list(obj, col); - return true; - } - - bool list_set(size_t index) - { - if (m_active_list) - m_active_list->modify(index); - return true; - } - - bool list_insert(size_t index) - { - if (m_active_list) - m_active_list->insert(index); - return true; - } - - bool list_erase(size_t index) - { - if (m_active_list) - m_active_list->erase(index); - return true; - } - - bool list_swap(size_t index1, size_t index2) - { - if (m_active_list) { - if (index1 > index2) - std::swap(index1, index2); - m_active_list->move(index1, index2); - if (index1 + 1 != index2) - m_active_list->move(index2 - 1, index1); - } - return true; - } - - bool list_clear(size_t old_size) - { - if (m_active_list) - m_active_list->clear(old_size); - return true; - } - - bool list_move(size_t from, size_t to) - { - if (m_active_list) - m_active_list->move(from, to); - return true; - } - - bool create_object(ObjKey key) - { - if (m_active_table) - m_active_table->insertions_add(key.value); - return true; - } - - bool remove_object(ObjKey key) - { - if (!m_active_table) - return true; - if (!m_active_table->insertions_remove(key.value)) - m_active_table->deletions_add(key.value); - m_active_table->modifications_remove(key.value); - - for (size_t i = 0; i < m_info.lists.size(); ++i) { - auto& list = m_info.lists[i]; - if (list.table_key != current_table()) - continue; - if (list.row_key == key.value) { - if (i + 1 < m_info.lists.size()) - m_info.lists[i] = std::move(m_info.lists.back()); - m_info.lists.pop_back(); - continue; - } - } - - return true; - } - - bool modify_object(ColKey col, ObjKey key) - { - if (m_active_table) - m_active_table->modifications_add(key.value, col.value); - return true; - } - - bool clear_table(size_t old_size) - { - auto cur_table = current_table(); - if (m_active_table) - m_active_table->clear(old_size); - auto it = remove_if(begin(m_info.lists), end(m_info.lists), - [&](auto const& lv) { return lv.table_key == cur_table; }); - m_info.lists.erase(it, end(m_info.lists)); - return true; - } - - bool insert_column(ColKey) - { - m_info.schema_changed = true; - return true; - } - - bool insert_group_level_table(TableKey) - { - m_info.schema_changed = true; - return true; - } -}; - -class KVOTransactLogObserver : public TransactLogObserver { - KVOAdapter m_adapter; - _impl::NotifierPackage& m_notifiers; - Transaction& m_sg; - -public: - KVOTransactLogObserver(std::vector<BindingContext::ObserverState>& observers, - BindingContext* context, - _impl::NotifierPackage& notifiers, - Transaction& sg) - : TransactLogObserver(m_adapter) - , m_adapter(observers, context) - , m_notifiers(notifiers) - , m_sg(sg) - { - } - - ~KVOTransactLogObserver() - { - m_adapter.after(m_sg); - } - - void parse_complete() - { - TransactLogObserver::parse_complete(); - m_adapter.before(m_sg); - - m_notifiers.package_and_wait(m_sg.get_version_of_latest_snapshot()); - m_notifiers.before_advance(); - } -}; - -template<typename Func> -void advance_with_notifications(BindingContext* context, - const std::shared_ptr<Transaction>& sg, - Func&& func, _impl::NotifierPackage& notifiers) -{ - auto old_version = sg->get_version_of_current_transaction(); - std::vector<BindingContext::ObserverState> observers; - if (context) { - observers = context->get_observed_rows(); - } - - // Advancing to the latest version with notifiers requires using the full - // transaction log observer so that we have a point where we know what - // version we're going to before we actually advance to that version - if (observers.empty() && (!notifiers || notifiers.version())) { - notifiers.before_advance(); - TransactLogValidator validator; - func(&validator); - auto new_version = sg->get_version_of_current_transaction(); - if (context && old_version != new_version) - context->did_change({}, {}); - // did_change() could close the Realm. Just return if it does. - if (sg->get_transact_stage() == DB::transact_Ready) - return; - if (context) - context->will_send_notifications(); - // will_send_notifications() could close the Realm. Just return if it does. - if (sg->get_transact_stage() == DB::transact_Ready) - return; - notifiers.after_advance(); - if (sg->get_transact_stage() == DB::transact_Ready) - return; - if (context) - context->did_send_notifications(); - return; - } - - if (context) - context->will_send_notifications(); - { - KVOTransactLogObserver observer(observers, context, notifiers, *sg); - func(&observer); - } - notifiers.package_and_wait(sg->get_version_of_current_transaction().version); // is a no-op if parse_complete() was called - notifiers.after_advance(); - if (context) - context->did_send_notifications(); -} - -} // anonymous namespace - -namespace realm { -namespace _impl { - -UnsupportedSchemaChange::UnsupportedSchemaChange() -: std::logic_error("Schema mismatch detected: another process has modified the Realm file's schema in an incompatible way") -{ -} - -namespace transaction { -void advance(Transaction& tr, BindingContext*, VersionID version) -{ - TransactLogValidator validator; - tr.advance_read(&validator, version); -} - -void advance(const std::shared_ptr<Transaction>& tr, BindingContext* context, NotifierPackage& notifiers) -{ - advance_with_notifications(context, tr, [&](auto&&... args) { - tr->advance_read(std::move(args)..., notifiers.version().value_or(VersionID{})); - }, notifiers); -} - -void begin(const std::shared_ptr<Transaction>& tr, BindingContext* context, NotifierPackage& notifiers) -{ - advance_with_notifications(context, tr, [&](auto&&... args) { - tr->promote_to_write(std::move(args)...); - }, notifiers); -} - -void cancel(Transaction& tr, BindingContext* context) -{ - std::vector<BindingContext::ObserverState> observers; - if (context) { - observers = context->get_observed_rows(); - } - if (observers.empty()) { - tr.rollback_and_continue_as_read(); - return; - } - - _impl::NotifierPackage notifiers; - KVOTransactLogObserver o(observers, context, notifiers, tr); - tr.rollback_and_continue_as_read(&o); -} - -void advance(Transaction& tr, TransactionChangeInfo& info, VersionID version) -{ - if (!info.track_all && info.tables.empty() && info.lists.empty()) { - tr.advance_read(version); - } - else { - TransactLogObserver o(info); - tr.advance_read(&o, version); - } -} - -} // namespace transaction -} // namespace _impl -} // namespace realm diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/transact_log_handler.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/transact_log_handler.hpp deleted file mode 100644 index 389ee58de..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/transact_log_handler.hpp +++ /dev/null @@ -1,62 +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 <cstdint> -#include <stdexcept> -#include <memory> - -#include <realm/version_id.hpp> - -namespace realm { -class BindingContext; -class Transaction; - -namespace _impl { -class NotifierPackage; -struct TransactionChangeInfo; - -struct UnsupportedSchemaChange : std::logic_error { - UnsupportedSchemaChange(); -}; - -namespace transaction { -// Advance the read transaction version, with change notifications sent to delegate -// Must not be called from within a write transaction. -void advance(const std::shared_ptr<Transaction>& sg, BindingContext* binding_context, NotifierPackage&); -void advance(Transaction& sg, BindingContext* binding_context, 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(const std::shared_ptr<Transaction>& sg, - BindingContext* binding_context, NotifierPackage&); - -// Cancel a write transaction and roll back all changes, with change notifications -// for reverting to the old values sent to delegate -void cancel(Transaction& sg, BindingContext* binding_context); - -// Advance the read transaction version, with change information gathered in info -void advance(Transaction& sg, TransactionChangeInfo& info, VersionID version=VersionID{}); -} // namespace transaction -} // namespace _impl -} // namespace realm - -#endif /* REALM_TRANSACT_LOG_HANDLER_HPP */ diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/weak_realm_notifier.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/weak_realm_notifier.cpp deleted file mode 100644 index 013b22812..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/weak_realm_notifier.cpp +++ /dev/null @@ -1,54 +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 "util/scheduler.hpp" - -using namespace realm; -using namespace realm::_impl; - - -WeakRealmNotifier::WeakRealmNotifier(const std::shared_ptr<Realm>& realm) -: m_realm(realm) -, m_realm_key(realm.get()) -{ - bind_to_scheduler(); -} - -WeakRealmNotifier::~WeakRealmNotifier() = default; - -void WeakRealmNotifier::notify() -{ - if (m_scheduler) - m_scheduler->notify(); -} - -void WeakRealmNotifier::bind_to_scheduler() -{ - REALM_ASSERT(!m_scheduler); - m_scheduler = realm()->scheduler(); - if (m_scheduler) { - m_scheduler->set_notify_callback([weak_realm = m_realm] { - if (auto realm = weak_realm.lock()) { - realm->notify(); - } - }); - } -} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/weak_realm_notifier.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/weak_realm_notifier.hpp deleted file mode 100644 index 1cd0c266a..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/weak_realm_notifier.hpp +++ /dev/null @@ -1,67 +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_HPP -#define REALM_WEAK_REALM_NOTIFIER_HPP - -#include <memory> -#include <thread> - -namespace realm { -class Realm; - -namespace util { -class Scheduler; -} - -namespace _impl { -// WeakRealmNotifier 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 WeakRealmNotifier { -public: - WeakRealmNotifier(const std::shared_ptr<Realm>& realm); - ~WeakRealmNotifier(); - - // Get a strong reference to the cached realm - std::shared_ptr<Realm> realm() const { return m_realm.lock(); } - - // Has the Realm instance been destroyed? - bool expired() const { return m_realm.expired(); } - - // Is this a WeakRealmNotifier for the given Realm instance? - bool is_for_realm(Realm* realm) const { return realm == m_realm_key; } - - // Invoke m_realm.notify() on the Realm's thread via the scheduler. - void notify(); - - // Bind this notifier to the Realm's scheduler. - void bind_to_scheduler(); - -private: - std::weak_ptr<Realm> m_realm; - void* m_realm_key; - std::shared_ptr<util::Scheduler> m_scheduler; -}; - -} // namespace _impl -} // namespace realm - -#endif // REALM_WEAK_REALM_NOTIFIER_HPP diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/windows/external_commit_helper.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/windows/external_commit_helper.cpp deleted file mode 100644 index 8f3de75f8..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/windows/external_commit_helper.cpp +++ /dev/null @@ -1,69 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 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 <algorithm> - -using namespace realm; -using namespace realm::_impl; - -static std::wstring create_condvar_sharedmemory_name(std::string realm_path) { - std::replace(realm_path.begin(), realm_path.end(), '\\', '/'); - return L"Local\\Realm_ObjectStore_ExternalCommitHelper_SharedCondVar_" + std::wstring(realm_path.begin(), realm_path.end()); -} - -ExternalCommitHelper::ExternalCommitHelper(RealmCoordinator& parent) -: m_parent(parent) -, m_condvar_shared(create_condvar_sharedmemory_name(parent.get_path()).c_str()) -{ - m_mutex.set_shared_part(InterprocessMutex::SharedPart(), parent.get_path(), "ExternalCommitHelper_ControlMutex"); - m_commit_available.set_shared_part(m_condvar_shared.get(), parent.get_path(), - "ExternalCommitHelper_CommitCondVar", - std::filesystem::temp_directory_path().u8string()); - m_thread = std::async(std::launch::async, [this]() { listen(); }); -} - -ExternalCommitHelper::~ExternalCommitHelper() -{ - { - std::lock_guard<InterprocessMutex> lock(m_mutex); - m_keep_listening = false; - m_commit_available.notify_all(); - } - m_thread.wait(); - - m_commit_available.release_shared_part(); -} - -void ExternalCommitHelper::notify_others() -{ - m_commit_available.notify_all(); -} - -void ExternalCommitHelper::listen() -{ - std::lock_guard<InterprocessMutex> lock(m_mutex); - while (m_keep_listening) { - m_commit_available.wait(m_mutex, nullptr); - if (m_keep_listening) { - m_parent.on_change(); - } - } -} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/windows/external_commit_helper.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/windows/external_commit_helper.hpp deleted file mode 100644 index 3d4f3c6e3..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/windows/external_commit_helper.hpp +++ /dev/null @@ -1,97 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 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 <realm/db.hpp> - -#include <future> -#include <windows.h> - -namespace realm { -namespace _impl { -class RealmCoordinator; - -namespace win32 { - template <class T, void (*Initializer)(T&)> - class SharedMemory { - public: - SharedMemory(LPCWSTR name) { -#if REALM_WINDOWS - HANDLE mapping = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, sizeof(T), name); - auto error = GetLastError(); - if (mapping != NULL) - m_memory = reinterpret_cast<T*>(MapViewOfFile(mapping, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(T))); -#elif REALM_UWP - HANDLE mapping = CreateFileMappingFromApp(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, sizeof(T), name); - auto error = GetLastError(); - if (mapping != NULL) - m_memory = reinterpret_cast<T*>(MapViewOfFileFromApp(mapping, FILE_MAP_ALL_ACCESS, 0, sizeof(T))); -#endif - - if (mapping) { - // we can close the handle we own because the view has now referenced it - CloseHandle(mapping); - } - - try { - if (error == 0) { - Initializer(get()); - } - else if (error != ERROR_ALREADY_EXISTS) { - throw std::system_error(error, std::system_category()); - } - } - catch (...) { - UnmapViewOfFile(m_memory); - throw; - } - } - - T& get() const noexcept { return *m_memory; } - - ~SharedMemory() { - UnmapViewOfFile(m_memory); - } - private: - T* m_memory = nullptr; - }; -} - -class ExternalCommitHelper { -public: - ExternalCommitHelper(RealmCoordinator& parent); - ~ExternalCommitHelper(); - - void notify_others(); - -private: - void listen(); - - RealmCoordinator& m_parent; - - // The listener thread - std::future<void> m_thread; - - win32::SharedMemory<InterprocessCondVar::SharedPart, InterprocessCondVar::init_shared_part> m_condvar_shared; - - InterprocessCondVar m_commit_available; - InterprocessMutex m_mutex; - bool m_keep_listening = true; -}; - -} // namespace _impl -} // namespace realm diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/index_set.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/index_set.cpp deleted file mode 100644 index e1a3b1071..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/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 <realm/util/assert.hpp> - -#include <algorithm> - -using namespace realm; -using namespace realm::_impl; - -const size_t IndexSet::npos; - -template<typename T> -void MutableChunkedRangeVectorIterator<T>::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<typename T> -void MutableChunkedRangeVectorIterator<T>::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<typename T> -void MutableChunkedRangeVectorIterator<T>::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({{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) noexcept -{ - 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<size_t, size_t>; - - ChunkedRangeVectorBuilder(ChunkedRangeVector const& expected); - void push_back(size_t index); - void push_back(std::pair<size_t, size_t> range); - std::vector<ChunkedRangeVector::Chunk> finalize(); -private: - std::vector<ChunkedRangeVector::Chunk> 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<size_t, size_t> 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<ChunkedRangeVector::Chunk> 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<size_t> values) -{ - for (size_t v : values) - add(v); -} - -bool IndexSet::contains(size_t index) const noexcept -{ - auto it = const_cast<IndexSet*>(this)->find(index); - return it != end() && it->first <= index; -} - -size_t IndexSet::count(size_t start_index, size_t end_index) const noexcept -{ - auto it = const_cast<IndexSet*>(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) noexcept -{ - return find(index, begin()); -} - -IndexSet::iterator IndexSet::find(size_t index, iterator begin) noexcept -{ - 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 noexcept -{ - // 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 noexcept -{ - REALM_ASSERT_DEBUG(!contains(index)); - return index - count(0, index); -} - -void IndexSet::clear() noexcept -{ - 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/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/index_set.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/index_set.hpp deleted file mode 100644 index 55bedcc15..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/index_set.hpp +++ /dev/null @@ -1,325 +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 <cstddef> -#include <initializer_list> -#include <iterator> -#include <type_traits> -#include <utility> -#include <vector> - -namespace realm { -namespace _impl { -template<typename OuterIterator> -class MutableChunkedRangeVectorIterator; - -// An iterator for ChunkedRangeVector, templated on the vector iterator/const_iterator -template<typename OuterIterator> -class ChunkedRangeVectorIterator { -public: - using iterator_category = std::bidirectional_iterator_tag; - using value_type = typename std::remove_reference<decltype(*OuterIterator()->data.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 noexcept { return *m_inner; } - pointer operator->() const noexcept { return m_inner; } - - template<typename Other> bool operator==(Other const& it) const noexcept; - template<typename Other> bool operator!=(Other const& it) const noexcept; - - ChunkedRangeVectorIterator& operator++() noexcept; - ChunkedRangeVectorIterator operator++(int) noexcept; - - ChunkedRangeVectorIterator& operator--() noexcept; - ChunkedRangeVectorIterator operator--(int) noexcept; - - // Advance directly to the next outer block - void next_chunk() noexcept; - - OuterIterator outer() const noexcept { return m_outer; } - size_t offset() const noexcept { return m_inner - &m_outer->data[0]; } - -private: - OuterIterator m_outer; - OuterIterator m_end; - value_type* m_inner; - friend struct ChunkedRangeVector; - friend class MutableChunkedRangeVectorIterator<OuterIterator>; -}; - -// A mutable iterator that adds some invariant-preserving mutation methods -template<typename OuterIterator> -class MutableChunkedRangeVectorIterator : public ChunkedRangeVectorIterator<OuterIterator> { -public: - using ChunkedRangeVectorIterator<OuterIterator>::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<std::pair<size_t, size_t>> data; - size_t begin; - size_t end; - size_t count; - }; - std::vector<Chunk> m_data; - - using value_type = std::pair<size_t, size_t>; - using iterator = MutableChunkedRangeVectorIterator<typename decltype(m_data)::iterator>; - using const_iterator = ChunkedRangeVectorIterator<typename decltype(m_data)::const_iterator>; - -#ifdef REALM_DEBUG - static const size_t max_size = 4; -#else - static const size_t max_size = 4096 / sizeof(std::pair<size_t, size_t>); -#endif - - iterator begin() noexcept { return empty() ? end() : iterator(m_data.begin(), m_data.end(), &m_data[0].data[0]); } - iterator end() noexcept { return iterator(m_data.end(), m_data.end(), nullptr); } - const_iterator begin() const noexcept { return cbegin(); } - const_iterator end() const noexcept { return cend(); } - const_iterator cbegin() const noexcept { return empty() ? cend() : const_iterator(m_data.cbegin(), m_data.end(), &m_data[0].data[0]); } - const_iterator cend() const noexcept { 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) noexcept; - 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<size_t>); - - // Check if the index set contains the given index - bool contains(size_t index) const noexcept; - - // 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 noexcept; - - // 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 noexcept; - // Shift an index by deleting each of the indexes in this set - size_t unshift(size_t index) const noexcept; - - // Remove all indexes from the set - void clear() noexcept; - - // An iterator over the individual indices in the set rather than the ranges - class IndexIterator : public std::iterator<std::forward_iterator_tag, size_t> { - public: - IndexIterator(IndexSet::const_iterator it) : m_iterator(it) { } - size_t operator*() const noexcept { return m_iterator->first + m_offset; } - bool operator==(IndexIterator const& it) const noexcept { return m_iterator == it.m_iterator; } - bool operator!=(IndexIterator const& it) const noexcept { return m_iterator != it.m_iterator; } - - IndexIterator& operator++() noexcept - { - ++m_offset; - if (m_iterator->first + m_offset == m_iterator->second) { - ++m_iterator; - m_offset = 0; - } - return *this; - } - - IndexIterator operator++(int) noexcept - { - 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 noexcept { return m_index_set.begin(); } - const_iterator end() const noexcept { return m_index_set.end(); } - - IndexIteratableAdaptor(IndexSet const& is) : m_index_set(is) { } - private: - IndexSet const& m_index_set; - }; - - IndexIteratableAdaptor as_indexes() const noexcept { return *this; } - -private: - // Find the range which contains the index, or the first one after it if - // none do - iterator find(size_t index) noexcept; - iterator find(size_t index, iterator it) noexcept; - // 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<typename Iterator> -std::reverse_iterator<Iterator> make_reverse_iterator(Iterator it) noexcept -{ - return std::reverse_iterator<Iterator>(it); -} -} // namespace util - - -namespace _impl { -template<typename T> -template<typename OtherIterator> -inline bool ChunkedRangeVectorIterator<T>::operator==(OtherIterator const& it) const noexcept -{ - return m_outer == it.outer() && m_inner == it.operator->(); -} - -template<typename T> -template<typename OtherIterator> -inline bool ChunkedRangeVectorIterator<T>::operator!=(OtherIterator const& it) const noexcept -{ - return !(*this == it); -} - -template<typename T> -inline ChunkedRangeVectorIterator<T>& ChunkedRangeVectorIterator<T>::operator++() noexcept -{ - ++m_inner; - if (offset() == m_outer->data.size()) - next_chunk(); - return *this; -} - -template<typename T> -inline ChunkedRangeVectorIterator<T> ChunkedRangeVectorIterator<T>::operator++(int) noexcept -{ - auto value = *this; - ++*this; - return value; -} - -template<typename T> -inline ChunkedRangeVectorIterator<T>& ChunkedRangeVectorIterator<T>::operator--() noexcept -{ - if (!m_inner || m_inner == &m_outer->data.front()) { - --m_outer; - m_inner = &m_outer->data.back(); - } - else { - --m_inner; - } - return *this; -} - -template<typename T> -inline ChunkedRangeVectorIterator<T> ChunkedRangeVectorIterator<T>::operator--(int) noexcept -{ - auto value = *this; - --*this; - return value; -} - -template<typename T> -inline void ChunkedRangeVectorIterator<T>::next_chunk() noexcept -{ - ++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/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/list.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/list.cpp deleted file mode 100644 index b6382bbd4..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/list.cpp +++ /dev/null @@ -1,507 +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_schema.hpp" -#include "object_store.hpp" -#include "results.hpp" -#include "schema.hpp" -#include "shared_realm.hpp" - -namespace { -using namespace realm; - -template<typename T> -struct ListType { - using type = Lst<T>; -}; - -template<> -struct ListType<Obj> { - using type = LnkLst; -}; - -} - -namespace realm { -using namespace _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<Realm> r, const Obj& parent_obj, ColKey col) -: m_realm(std::move(r)) -, m_type(ObjectSchema::from_core_type(*parent_obj.get_table(), col) & ~PropertyType::Array) -, m_list_base(parent_obj.get_listbase_ptr(col)) -{ -} - -List::List(std::shared_ptr<Realm> r, const LstBase& list) -: m_realm(std::move(r)) -, m_type(ObjectSchema::from_core_type(*list.get_table(), list.get_col_key()) & ~PropertyType::Array) -, m_list_base(list.clone()) -{ -} - -static StringData object_name(Table const& table) -{ - return ObjectStore::object_type_for_table_name(table.get_name()); -} - -ObjectSchema const& List::get_object_schema() const -{ - verify_attached(); - - REALM_ASSERT(get_type() == PropertyType::Object); - auto object_schema = m_object_schema.load(); - if (!object_schema) { - auto object_type = object_name(*static_cast<LnkLst&>(*m_list_base).get_target_table()); - auto it = m_realm->schema().find(object_type); - REALM_ASSERT(it != m_realm->schema().end()); - m_object_schema = object_schema = &*it; - } - return *object_schema; -} - -Query List::get_query() const -{ - verify_attached(); - if (m_type == PropertyType::Object) - return static_cast<LnkLst&>(*m_list_base).get_target_table()->where(as<Obj>()); - throw std::runtime_error("not implemented"); -} - -ObjKey List::get_parent_object_key() const -{ - verify_attached(); - return m_list_base->get_key(); -} - -ColKey List::get_parent_column_key() const -{ - verify_attached(); - return m_list_base->get_col_key(); -} - -TableKey List::get_parent_table_key() const -{ - verify_attached(); - return m_list_base->get_table()->get_key(); -} - -void List::verify_valid_row(size_t row_ndx, bool insertion) const -{ - size_t s = size(); - if (row_ndx > s || (!insertion && row_ndx == s)) { - throw OutOfBoundsIndexException{row_ndx, s + insertion}; - } -} - -void List::validate(const Obj& obj) const -{ - if (!obj.is_valid()) - throw std::invalid_argument("Object has been deleted or invalidated"); - auto target = static_cast<LnkLst&>(*m_list_base).get_target_table(); - if (obj.get_table() != target) - throw std::invalid_argument(util::format("Object of type (%1) does not match List type (%2)", - object_name(*obj.get_table()), - object_name(*target))); -} - -bool List::is_valid() const -{ - if (!m_realm) - return false; - m_realm->verify_thread(); - if (!m_realm->is_in_read_transaction()) - return false; - return m_list_base->is_attached(); -} - -void List::verify_attached() const -{ - if (!is_valid()) { - throw InvalidatedException(); - } -} - -void List::verify_in_transaction() const -{ - verify_attached(); - m_realm->verify_in_write(); -} - -size_t List::size() const -{ - verify_attached(); - return m_list_base->size(); -} - -template<typename T> -T List::get(size_t row_ndx) const -{ - verify_valid_row(row_ndx); - return as<T>().get(row_ndx); -} - -template<> -Obj List::get(size_t row_ndx) const -{ - verify_valid_row(row_ndx); - auto& list = as<Obj>(); - return list.get_target_table()->get_object(list.get(row_ndx)); -} - -template<typename T> -size_t List::find(T const& value) const -{ - verify_attached(); - return as<T>().find_first(value); -} - -template<> -size_t List::find(Obj const& o) const -{ - verify_attached(); - if (!o.is_valid()) - return not_found; - validate(o); - - return as<Obj>().ConstLstIf<ObjKey>::find_first(o.get_key()); -} - -size_t List::find(Query&& q) const -{ - verify_attached(); - if (m_type == PropertyType::Object) { - ObjKey key = get_query().and_query(std::move(q)).find(); - return key ? as<Obj>().ConstLstIf<ObjKey>::find_first(key) : not_found; - } - throw std::runtime_error("not implemented"); -} - -template<typename T> -void List::add(T value) -{ - verify_in_transaction(); - as<T>().add(value); -} - -template<> -void List::add(Obj o) -{ - verify_in_transaction(); - validate(o); - as<Obj>().add(o.get_key()); -} - -template<typename T> -void List::insert(size_t row_ndx, T value) -{ - verify_in_transaction(); - verify_valid_row(row_ndx, true); - as<T>().insert(row_ndx, value); -} - -template<> -void List::insert(size_t row_ndx, Obj o) -{ - verify_in_transaction(); - verify_valid_row(row_ndx, true); - validate(o); - as<Obj>().insert(row_ndx, o.get_key()); -} - -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 - if (source_ndx == dest_ndx) - return; - - m_list_base->move(source_ndx, dest_ndx); -} - -void List::remove(size_t row_ndx) -{ - verify_in_transaction(); - verify_valid_row(row_ndx); - m_list_base->remove(row_ndx, row_ndx + 1); -} - -void List::remove_all() -{ - verify_in_transaction(); - m_list_base->clear(); -} - -template<typename T> -void List::set(size_t row_ndx, T value) -{ - verify_in_transaction(); - verify_valid_row(row_ndx); -// validate(row); - as<T>().set(row_ndx, value); -} - -template<> -void List::set(size_t row_ndx, Obj o) -{ - verify_in_transaction(); - verify_valid_row(row_ndx); - validate(o); - as<Obj>().set(row_ndx, o.get_key()); -} - -void List::swap(size_t ndx1, size_t ndx2) -{ - verify_in_transaction(); - verify_valid_row(ndx1); - verify_valid_row(ndx2); - m_list_base->swap(ndx1, ndx2); -} - -void List::delete_at(size_t row_ndx) -{ - verify_in_transaction(); - verify_valid_row(row_ndx); - if (m_type == PropertyType::Object) - as<Obj>().remove_target_row(row_ndx); - else - m_list_base->remove(row_ndx, row_ndx + 1); -} - -void List::delete_all() -{ - verify_in_transaction(); - if (m_type == PropertyType::Object) - as<Obj>().remove_all_target_rows(); - else - m_list_base->clear(); -} - -Results List::sort(SortDescriptor order) const -{ - verify_attached(); - if ((m_type == PropertyType::Object)) { - return Results(m_realm, std::dynamic_pointer_cast<LnkLst>(m_list_base), util::none, std::move(order)); - } - else { - DescriptorOrdering o; - o.append_sort(order); - return Results(m_realm, m_list_base, std::move(o)); - } -} - -Results List::sort(std::vector<std::pair<std::string, bool>> const& keypaths) const -{ - return as_results().sort(keypaths); -} - -Results List::filter(Query q) const -{ - verify_attached(); - return Results(m_realm, std::dynamic_pointer_cast<LnkLst>(m_list_base), get_query().and_query(std::move(q))); -} - -Results List::as_results() const -{ - verify_attached(); - return m_type == PropertyType::Object - ? Results(m_realm, std::static_pointer_cast<LnkLst>(m_list_base)) - : Results(m_realm, m_list_base); -} - -Results List::snapshot() const -{ - return as_results().snapshot(); -} - -// The simpler definition of void_t below does not work in gcc 4.9 due to a bug -// in that version of gcc (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64395) - -// template<class...> using VoidT = void; -namespace _impl { - template<class... > struct MakeVoid { using type = void; }; -} -template<class... T> using VoidT = typename _impl::MakeVoid<T...>::type; - -template<class, class = VoidT<>> -struct HasMinmaxType : std::false_type { }; -template<class T> -struct HasMinmaxType<T, VoidT<typename ColumnTypeTraits<T>::minmax_type>> : std::true_type { }; - -template<class, class = VoidT<>> -struct HasSumType : std::false_type { }; -template<class T> -struct HasSumType<T, VoidT<typename ColumnTypeTraits<T>::sum_type>> : std::true_type { }; - -template<bool cond> -struct If; - -template<> -struct If<true> { - template<typename T, typename Then, typename Else> - static auto call(T self, Then&& fn, Else&&) { return fn(self); } -}; -template<> -struct If<false> { - template<typename T, typename Then, typename Else> - static auto call(T, Then&&, Else&& fn) { return fn(); } -}; - -util::Optional<Mixed> List::max(ColKey col) const -{ - if (get_type() == PropertyType::Object) - return as_results().max(col); - size_t out_ndx = not_found; - auto result = m_list_base->max(&out_ndx); - if (result.is_null()) { - throw realm::Results::UnsupportedColumnTypeException(m_list_base->get_col_key(), m_list_base->get_table(), "max"); - } - return out_ndx == not_found ? none : make_optional(result); -} - -util::Optional<Mixed> List::min(ColKey col) const -{ - if (get_type() == PropertyType::Object) - return as_results().min(col); - - size_t out_ndx = not_found; - auto result = m_list_base->min(&out_ndx); - if (result.is_null()) { - throw realm::Results::UnsupportedColumnTypeException(m_list_base->get_col_key(), m_list_base->get_table(), "min"); - } - return out_ndx == not_found ? none : make_optional(result); -} - -Mixed List::sum(ColKey col) const -{ - if (get_type() == PropertyType::Object) - return *as_results().sum(col); - - auto result = m_list_base->sum(); - if (result.is_null()) { - throw realm::Results::UnsupportedColumnTypeException(m_list_base->get_col_key(), m_list_base->get_table(), "sum"); - } - return result; -} - -util::Optional<double> List::average(ColKey col) const -{ - if (get_type() == PropertyType::Object) - return as_results().average(col); - size_t count = 0; - auto result = m_list_base->avg(&count); - if (result.is_null()) { - throw realm::Results::UnsupportedColumnTypeException(m_list_base->get_col_key(), m_list_base->get_table(), "average"); - } - return count == 0 ? none : make_optional(result.get_double()); -} - -bool List::operator==(List const& rgt) const noexcept -{ - return m_list_base->get_table() == rgt.m_list_base->get_table() - && m_list_base->get_key() == rgt.m_list_base->get_key() - && m_list_base->get_col_key() == rgt.m_list_base->get_col_key(); -} - -NotificationToken List::add_notification_callback(CollectionChangeCallback cb) & -{ - verify_attached(); - m_realm->verify_notifications_available(); - // Adding a new callback to a notifier which had all of its callbacks - // removed does not properly reinitialize the notifier. Work around this by - // recreating it instead. - // FIXME: The notifier lifecycle here is dumb (when all callbacks are removed - // from a notifier a zombie is left sitting around uselessly) and should be - // cleaned up. - if (m_notifier && !m_notifier->have_callbacks()) - m_notifier.reset(); - if (!m_notifier) { - m_notifier = std::make_shared<ListNotifier>(m_realm, *m_list_base, m_type); - RealmCoordinator::register_notifier(m_notifier); - } - return {m_notifier, m_notifier->add_callback(std::move(cb))}; -} - -List List::freeze(std::shared_ptr<Realm> const& frozen_realm) const -{ - return List(frozen_realm, *frozen_realm->import_copy_of(*m_list_base)); -} - -bool List::is_frozen() const noexcept -{ - return m_realm->is_frozen(); -} - -List::OutOfBoundsIndexException::OutOfBoundsIndexException(size_t r, size_t c) -: std::out_of_range(util::format("Requested index %1 greater than max %2", r, c - 1)) -, requested(r), valid_count(c) {} - -#define REALM_PRIMITIVE_LIST_TYPE(T) \ - template T List::get<T>(size_t) const; \ - template size_t List::find<T>(T const&) const; \ - template void List::add<T>(T); \ - template void List::insert<T>(size_t, T); \ - template void List::set<T>(size_t, T); - -REALM_PRIMITIVE_LIST_TYPE(bool) -REALM_PRIMITIVE_LIST_TYPE(int64_t) -REALM_PRIMITIVE_LIST_TYPE(float) -REALM_PRIMITIVE_LIST_TYPE(double) -REALM_PRIMITIVE_LIST_TYPE(StringData) -REALM_PRIMITIVE_LIST_TYPE(BinaryData) -REALM_PRIMITIVE_LIST_TYPE(Timestamp) -REALM_PRIMITIVE_LIST_TYPE(ObjKey) -REALM_PRIMITIVE_LIST_TYPE(util::Optional<bool>) -REALM_PRIMITIVE_LIST_TYPE(util::Optional<int64_t>) -REALM_PRIMITIVE_LIST_TYPE(util::Optional<float>) -REALM_PRIMITIVE_LIST_TYPE(util::Optional<double>) - -#undef REALM_PRIMITIVE_LIST_TYPE -} // namespace realm - -namespace { -size_t hash_combine() { return 0; } -template<typename T, typename... Rest> -size_t hash_combine(const T& v, Rest... rest) -{ - size_t h = hash_combine(rest...); - h ^= std::hash<T>()(v) + 0x9e3779b9 + (h<<6) + (h>>2); - return h; -} -} - -namespace std { -size_t hash<List>::operator()(List const& list) const -{ - auto& impl = *list.m_list_base; - return hash_combine(impl.get_key().value, impl.get_table()->get_key().value, - impl.get_col_key().value); -} -} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/list.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/list.hpp deleted file mode 100644 index fb7a751c2..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/list.hpp +++ /dev/null @@ -1,319 +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_OS_LIST_HPP -#define REALM_OS_LIST_HPP - -#include "collection_notifications.hpp" -#include "impl/collection_notifier.hpp" -#include "object.hpp" -#include "property.hpp" -#include "util/copyable_atomic.hpp" - -#include <realm/mixed.hpp> -#include <realm/list.hpp> - -#include <functional> -#include <memory> - -namespace realm { -class Obj; -class ObjectSchema; -class Query; -class Realm; -class Results; -class SortDescriptor; -class ThreadSafeReference; -struct ColKey; -struct ObjKey; - -namespace _impl { -class ListNotifier; -} - -class List { -public: - List() noexcept; - List(std::shared_ptr<Realm> r, const Obj& parent_obj, ColKey col); - List(std::shared_ptr<Realm> r, const LstBase& list); - ~List(); - - List(const List&); - List& operator=(const List&); - List(List&&); - List& operator=(List&&); - - const std::shared_ptr<Realm>& get_realm() const { return m_realm; } - Query get_query() const; - - ColKey get_parent_column_key() const; - ObjKey get_parent_object_key() const; - TableKey get_parent_table_key() const; - - // Get the type of the values contained in this List - PropertyType get_type() const { return m_type; } - - // Get the ObjectSchema of the values in this List - // Only valid if get_type() returns PropertyType::Object - ObjectSchema const& get_object_schema() const; - - bool is_valid() const; - void verify_attached() const; - void verify_in_transaction() const; - - size_t size() const; - - void move(size_t source_ndx, size_t dest_ndx); - void remove(size_t list_ndx); - void remove_all(); - void swap(size_t ndx1, size_t ndx2); - void delete_at(size_t list_ndx); - void delete_all(); - - template<typename T = Obj> - T get(size_t row_ndx) const; - template<typename T> - size_t find(T const& value) const; - - // Find the index in the List of the first row matching the query - size_t find(Query&& query) const; - - template<typename T> - void add(T value); - template<typename T> - void insert(size_t list_ndx, T value); - template<typename T> - void set(size_t row_ndx, T value); - - Results sort(SortDescriptor order) const; - Results sort(std::vector<std::pair<std::string, bool>> const& keypaths) const; - Results filter(Query q) const; - - // Return a Results representing a live view of this List. - Results as_results() const; - - // Return a Results representing a snapshot of this List. - Results snapshot() const; - - // Returns a frozen copy of this result - List freeze(std::shared_ptr<Realm> const& realm) const; - - // Returns whether or not this List is frozen. - bool is_frozen() const noexcept; - - // Get the min/max/average/sum of the given column - // All but sum() returns none when there are zero matching rows - // sum() returns 0, - // Throws UnsupportedColumnTypeException for sum/average on timestamp or non-numeric column - // Throws OutOfBoundsIndexException for an out-of-bounds column - util::Optional<Mixed> max(ColKey column={}) const; - util::Optional<Mixed> min(ColKey column={}) const; - util::Optional<double> average(ColKey column={}) const; - Mixed sum(ColKey column={}) const; - - bool operator==(List const& rgt) const noexcept; - - NotificationToken add_notification_callback(CollectionChangeCallback cb) &; - - template<typename Context> - auto get(Context&, size_t row_ndx) const; - template<typename T, typename Context> - size_t find(Context&, T&& value) const; - - template<typename T, typename Context> - void add(Context&, T&& value, CreatePolicy=CreatePolicy::ForceCreate); - template<typename T, typename Context> - void insert(Context&, size_t list_ndx, T&& value, CreatePolicy=CreatePolicy::ForceCreate); - template<typename T, typename Context> - void set(Context&, size_t row_ndx, T&& value, CreatePolicy=CreatePolicy::ForceCreate); - - // Replace the values in this list with the values from an enumerable object - template<typename T, typename Context> - void assign(Context&, T&& value, CreatePolicy=CreatePolicy::ForceCreate); - - // 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<Realm> m_realm; - PropertyType m_type; - mutable util::CopyableAtomic<const ObjectSchema*> m_object_schema = nullptr; - _impl::CollectionNotifier::Handle<_impl::ListNotifier> m_notifier; - std::shared_ptr<LstBase> m_list_base; - - void verify_valid_row(size_t row_ndx, bool insertion = false) const; - void validate(const Obj&) const; - - template<typename Fn> - auto dispatch(Fn&&) const; - template<typename T> - auto& as() const; - - template<typename T, typename Context> - void set_if_different(Context&, size_t row_ndx, T&& value, CreatePolicy); - - friend struct std::hash<List>; -}; - -template<typename T> -auto& List::as() const -{ - return static_cast<Lst<T>&>(*m_list_base); -} - -template<> -inline auto& List::as<Obj>() const -{ - return static_cast<LnkLst&>(*m_list_base); -} - -template<typename Fn> -auto List::dispatch(Fn&& fn) const -{ - verify_attached(); - return switch_on_type(get_type(), std::forward<Fn>(fn)); -} - -template<typename Context> -auto List::get(Context& ctx, size_t row_ndx) const -{ - return dispatch([&](auto t) { return ctx.box(this->get<std::decay_t<decltype(*t)>>(row_ndx)); }); -} - -template<typename T, typename Context> -size_t List::find(Context& ctx, T&& value) const -{ - return dispatch([&](auto t) { return this->find(ctx.template unbox<std::decay_t<decltype(*t)>>(value, CreatePolicy::Skip)); }); -} - -template<typename T, typename Context> -void List::add(Context& ctx, T&& value, CreatePolicy policy) -{ - dispatch([&](auto t) { this->add(ctx.template unbox<std::decay_t<decltype(*t)>>(value, policy)); }); -} - -template<typename T, typename Context> -void List::insert(Context& ctx, size_t list_ndx, T&& value, CreatePolicy policy) -{ - dispatch([&](auto t) { this->insert(list_ndx, ctx.template unbox<std::decay_t<decltype(*t)>>(value, policy)); }); -} - -template<typename T, typename Context> -void List::set(Context& ctx, size_t row_ndx, T&& value, CreatePolicy policy) -{ - dispatch([&](auto t) { this->set(row_ndx, ctx.template unbox<std::decay_t<decltype(*t)>>(value, policy)); }); -} - -namespace _impl { -template <class T> -inline ObjKey help_get_current_row(const T&) -{ - return ObjKey(); -} - -template <> -inline ObjKey help_get_current_row(const ConstObj& v) -{ - return v.get_key(); -} - -template <> -inline ObjKey help_get_current_row(const Obj& v) -{ - return v.get_key(); -} - -template <class T> -inline bool help_compare_values(const T& v1, const T& v2) -{ - return v1 != v2; -} -template <> -inline bool help_compare_values(const Obj& v1, const Obj& v2) -{ - return v1.get_table() != v2.get_table() || v1.get_key() != v2.get_key(); -} -} - -template<typename T, typename Context> -void List::set_if_different(Context& ctx, size_t row_ndx, T&& value, CreatePolicy policy) -{ - dispatch([&](auto t) { - using U = std::decay_t<decltype(*t)>; - auto old_value = this->get<U>(row_ndx); - auto new_value = ctx.template unbox<U>(value, policy, _impl::help_get_current_row(old_value)); - if (_impl::help_compare_values(old_value, new_value)) - this->set(row_ndx, new_value); - }); -} - - -template<typename T, typename Context> -void List::assign(Context& ctx, T&& values, CreatePolicy policy) -{ - if (ctx.is_same_list(*this, values)) - return; - - if (ctx.is_null(values)) { - remove_all(); - return; - } - - if (policy == CreatePolicy::UpdateModified) { - size_t sz = size(); - size_t index = 0; - ctx.enumerate_list(values, [&](auto&& element) { - if (index < sz) { - this->set_if_different(ctx, index, element, policy); - } - else { - this->add(ctx, element, policy); - } - index++; - }); - while (index < sz) { - remove(--sz); - } - } - else { - remove_all(); - ctx.enumerate_list(values, [&](auto&& element) { - this->add(ctx, element, policy); - }); - } -} -} // namespace realm - -namespace std { -template<> struct hash<realm::List> { - size_t operator()(realm::List const&) const; -}; -} - -#endif // REALM_OS_LIST_HPP diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object.cpp deleted file mode 100644 index 318da0c80..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object.cpp +++ /dev/null @@ -1,163 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 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.hpp" - -#include "impl/object_notifier.hpp" -#include "impl/realm_coordinator.hpp" -#include "object_schema.hpp" -#include "object_store.hpp" - -#include <realm/table.hpp> - -using namespace realm; - -Object Object::freeze(std::shared_ptr<Realm> frozen_realm) const -{ - return Object(frozen_realm, frozen_realm->import_copy_of(m_obj)); -} - -bool Object::is_frozen() const noexcept -{ - return m_realm->is_frozen(); -} - -InvalidatedObjectException::InvalidatedObjectException(const std::string& object_type) -: std::logic_error("Accessing object of type " + object_type + " which has been invalidated or deleted") -, object_type(object_type) -{} - -InvalidPropertyException::InvalidPropertyException(const std::string& object_type, const std::string& property_name) -: std::logic_error(util::format("Property '%1.%2' does not exist", object_type, property_name)) -, object_type(object_type), property_name(property_name) -{} - -MissingPropertyValueException::MissingPropertyValueException(const std::string& object_type, const std::string& property_name) -: std::logic_error(util::format("Missing value for property '%1.%2'", object_type, property_name)) -, object_type(object_type), property_name(property_name) -{} - -MissingPrimaryKeyException::MissingPrimaryKeyException(const std::string& object_type) -: std::logic_error(util::format("'%1' does not have a primary key defined", object_type)) -, object_type(object_type) -{} - -ReadOnlyPropertyException::ReadOnlyPropertyException(const std::string& object_type, const std::string& property_name) -: std::logic_error(util::format("Cannot modify read-only property '%1.%2'", object_type, property_name)) -, object_type(object_type), property_name(property_name) {} - -ModifyPrimaryKeyException::ModifyPrimaryKeyException(const std::string& object_type, const std::string& property_name) -: std::logic_error(util::format("Cannot modify primary key after creation: '%1.%2'", object_type, property_name)) -, object_type(object_type), property_name(property_name) {} - -Object::Object(SharedRealm r, ObjectSchema const& s, Obj const& o) -: m_realm(std::move(r)), m_object_schema(&s), m_obj(o) { } - -Object::Object(SharedRealm r, Obj const& o) -: m_realm(std::move(r)) -, m_object_schema(&*m_realm->schema().find(ObjectStore::object_type_for_table_name(o.get_table()->get_name()))) -, m_obj(o) -{ -} - -Object::Object(SharedRealm r, StringData object_type, ObjKey key) -: m_realm(std::move(r)) -, m_object_schema(&*m_realm->schema().find(object_type)) -, m_obj(ObjectStore::table_for_object_type(m_realm->read_group(), object_type)->get_object(key)) -{ -} - -Object::Object(SharedRealm r, StringData object_type, size_t index) -: m_realm(std::move(r)) -, m_object_schema(&*m_realm->schema().find(object_type)) -, m_obj(ObjectStore::table_for_object_type(m_realm->read_group(), object_type)->get_object(index)) -{ -} - -Object::Object() = default; -Object::~Object() = default; -Object::Object(Object const&) = default; -Object::Object(Object&&) = default; -Object& Object::operator=(Object const&) = default; -Object& Object::operator=(Object&&) = default; - -NotificationToken Object::add_notification_callback(CollectionChangeCallback callback) & -{ - verify_attached(); - m_realm->verify_notifications_available(); - if (!m_notifier) { - m_notifier = std::make_shared<_impl::ObjectNotifier>(m_realm, m_obj.get_table()->get_key(), m_obj.get_key()); - _impl::RealmCoordinator::register_notifier(m_notifier); - } - return {m_notifier, m_notifier->add_callback(std::move(callback))}; -} - -void Object::verify_attached() const -{ - m_realm->verify_thread(); - if (!m_obj.is_valid()) { - throw InvalidatedObjectException(m_object_schema->name); - } -} - -Property const& Object::property_for_name(StringData prop_name) const -{ - auto prop = m_object_schema->property_for_name(prop_name); - if (!prop) { - throw InvalidPropertyException(m_object_schema->name, prop_name); - } - return *prop; -} - -void Object::validate_property_for_setter(Property const& property) const -{ - verify_attached(); - m_realm->verify_in_write(); - - // Modifying primary keys is allowed in migrations to make it possible to - // add a new primary key to a type (or change the property type), but it - // is otherwise considered the immutable identity of the row - if (property.is_primary) { - if (!m_realm->is_in_migration()) - throw ModifyPrimaryKeyException(m_object_schema->name, property.name); - // Modifying the PK property while it's the PK will corrupt the table, - // so remove it and then restore it at the end of the migration (which will rebuild the table) - m_obj.get_table()->set_primary_key_column({}); - } -} - -#if REALM_ENABLE_SYNC -void Object::ensure_user_in_everyone_role() -{ - if (auto role_table = m_realm->read_group().get_table("class___Role")) { - if (ObjKey ndx = role_table->find_first_string(role_table->get_column_key("name"), "everyone")) { - auto role = role_table->get_object(ndx); - auto users = role.get_linklist(role_table->get_column_key("members")); - if (users.find_first(m_obj.get_key()) == realm::npos) { - users.add(m_obj.get_key()); - } - } - } -} - -void Object::ensure_private_role_exists_for_user() -{ - auto user_id = m_obj.get<StringData>("id"); - ObjectStore::ensure_private_role_exists_for_user(static_cast<Transaction&>(m_realm->read_group()), user_id); -} -#endif diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object.hpp deleted file mode 100644 index 1f2751a2f..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object.hpp +++ /dev/null @@ -1,188 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 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_OS_OBJECT_HPP -#define REALM_OS_OBJECT_HPP - -#include "impl/collection_notifier.hpp" - -#include <realm/obj.hpp> - -namespace realm { -class ObjectSchema; -struct Property; - -namespace _impl { - class ObjectNotifier; -} - -enum class CreatePolicy : int8_t { - // Do not create objects if given something that could be converted to a - // Realm object but isn't. Used for things like find(). - Skip, - // Throw an exception if an object with the same PK already exists. - ForceCreate, - // If an object with the same PK already exists, set all fields in the input. - UpdateAll, - // If an object with the same PK already exists, only set fields which have changed. - UpdateModified -}; - -class Object { -public: - Object(); - Object(std::shared_ptr<Realm> r, Obj const& o); - Object(std::shared_ptr<Realm> r, ObjectSchema const& s, Obj const& o); - Object(std::shared_ptr<Realm> r, StringData object_type, ObjKey key); - Object(std::shared_ptr<Realm> r, StringData object_type, size_t index); - - Object(Object const&); - Object(Object&&); - Object& operator=(Object const&); - Object& operator=(Object&&); - - ~Object(); - - std::shared_ptr<Realm> const& realm() const { return m_realm; } - std::shared_ptr<Realm> const& get_realm() const { return m_realm; } - ObjectSchema const& get_object_schema() const { return *m_object_schema; } - Obj obj() const { return m_obj; } - - bool is_valid() const { return m_obj.is_valid(); } - - // Returns a frozen copy of this object. - Object freeze(std::shared_ptr<Realm> frozen_realm) const; - - // Returns whether or not this Object is frozen. - bool is_frozen() const noexcept; - - NotificationToken add_notification_callback(CollectionChangeCallback callback) &; - - void ensure_user_in_everyone_role(); - void ensure_private_role_exists_for_user(); - - template<typename ValueType> - void set_column_value(StringData prop_name, ValueType&& value) { m_obj.set(prop_name, value); } - - template<typename ValueType> - ValueType get_column_value(StringData prop_name) const { return m_obj.get<ValueType>(prop_name); } - - // The following functions require an accessor context which converts from - // the binding's native data types to the core data types. See CppContext - // for a reference implementation of such a context. - // - // The actual definitions of these templated functions is in object_accessor.hpp - - // property getter/setter - template<typename ValueType, typename ContextType> - void set_property_value(ContextType& ctx, StringData prop_name, - ValueType value, CreatePolicy policy = CreatePolicy::ForceCreate); - - template<typename ValueType, typename ContextType> - ValueType get_property_value(ContextType& ctx, StringData prop_name) const; - - template<typename ValueType, typename ContextType> - ValueType get_property_value(ContextType& ctx, const Property& property) const; - - // create an Object from a native representation - template<typename ValueType, typename ContextType> - static Object create(ContextType& ctx, std::shared_ptr<Realm> const& realm, - const ObjectSchema &object_schema, ValueType value, - CreatePolicy policy = CreatePolicy::ForceCreate, - ObjKey current_obj = ObjKey(), Obj* = nullptr); - - template<typename ValueType, typename ContextType> - static Object create(ContextType& ctx, std::shared_ptr<Realm> const& realm, - StringData object_type, ValueType value, - CreatePolicy policy = CreatePolicy::ForceCreate, - ObjKey current_obj = ObjKey(), Obj* = nullptr); - - template<typename ValueType, typename ContextType> - static Object get_for_primary_key(ContextType& ctx, - std::shared_ptr<Realm> const& realm, - const ObjectSchema &object_schema, - ValueType primary_value); - - template<typename ValueType, typename ContextType> - static Object get_for_primary_key(ContextType& ctx, - std::shared_ptr<Realm> const& realm, - StringData object_type, - ValueType primary_value); - -private: - friend class Results; - - std::shared_ptr<Realm> m_realm; - const ObjectSchema *m_object_schema; - Obj m_obj; - _impl::CollectionNotifier::Handle<_impl::ObjectNotifier> m_notifier; - - - template<typename ValueType, typename ContextType> - void set_property_value_impl(ContextType& ctx, const Property &property, - ValueType value, CreatePolicy policy, bool is_default); - template<typename ValueType, typename ContextType> - ValueType get_property_value_impl(ContextType& ctx, const Property &property) const; - - template<typename ValueType, typename ContextType> - static ObjKey get_for_primary_key_impl(ContextType& ctx, Table const& table, - const Property &primary_prop, - ValueType primary_value); - - void verify_attached() const; - Property const& property_for_name(StringData prop_name) const; - void validate_property_for_setter(Property const&) const; -}; - -struct InvalidatedObjectException : public std::logic_error { - InvalidatedObjectException(const std::string& 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 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 object_type; - const std::string property_name; -}; - -struct MissingPrimaryKeyException : public std::logic_error { - MissingPrimaryKeyException(const std::string& 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 object_type; - const std::string property_name; -}; - -struct ModifyPrimaryKeyException : public std::logic_error { - ModifyPrimaryKeyException(const std::string& object_type, const std::string& property_name); - const std::string object_type; - const std::string property_name; -}; - -} // namespace realm - -#endif // REALM_OS_OBJECT_HPP diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object_accessor.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object_accessor.hpp deleted file mode 100644 index f02b6f901..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object_accessor.hpp +++ /dev/null @@ -1,349 +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_OS_OBJECT_ACCESSOR_HPP -#define REALM_OS_OBJECT_ACCESSOR_HPP - -#include "object.hpp" - -#include "feature_checks.hpp" -#include "list.hpp" -#include "object_schema.hpp" -#include "object_store.hpp" -#include "results.hpp" -#include "schema.hpp" -#include "shared_realm.hpp" - -#include <realm/util/assert.hpp> -#include <realm/table_view.hpp> - -#if REALM_ENABLE_SYNC -#include <realm/sync/object.hpp> -#endif // REALM_ENABLE_SYNC - -#include <string> - -namespace realm { -template <typename ValueType, typename ContextType> -void Object::set_property_value(ContextType& ctx, StringData prop_name, - ValueType value, CreatePolicy policy) -{ - auto& property = property_for_name(prop_name); - validate_property_for_setter(property); - set_property_value_impl(ctx, property, value, policy, false); -} - -template <typename ValueType, typename ContextType> -ValueType Object::get_property_value(ContextType& ctx, const Property& property) const -{ - return get_property_value_impl<ValueType>(ctx, property); -} - -template <typename ValueType, typename ContextType> -ValueType Object::get_property_value(ContextType& ctx, StringData prop_name) const -{ - return get_property_value_impl<ValueType>(ctx, property_for_name(prop_name)); -} - -namespace { -template <typename ValueType, typename ContextType> -struct ValueUpdater { - ContextType& ctx; - Property const& property; - ValueType& value; - Obj& obj; - ColKey col; - CreatePolicy policy; - bool is_default; - - void operator()(Obj*) - { - ContextType child_ctx(ctx, property); - auto curr_link = obj.get<ObjKey>(col); - auto link = child_ctx.template unbox<Obj>(value, policy, curr_link); - if (policy != CreatePolicy::UpdateModified || curr_link != link.get_key()) { - obj.set(col, link.get_key()); - } - } - - template<typename T> - void operator()(T*) - { - auto new_val = ctx.template unbox<T>(value); - if (policy != CreatePolicy::UpdateModified || obj.get<T>(col) != new_val) { - obj.set(col, new_val, is_default); - } - } -}; -} - -template <typename ValueType, typename ContextType> -void Object::set_property_value_impl(ContextType& ctx, const Property &property, - ValueType value, CreatePolicy policy, bool is_default) -{ - ctx.will_change(*this, property); - - ColKey col{property.column_key}; - if (is_nullable(property.type) && ctx.is_null(value)) { - if (policy != CreatePolicy::UpdateModified || !m_obj.is_null(col)) { - if (property.type == PropertyType::Object) { - if (!is_default) - m_obj.set_null(col); - } - else { - m_obj.set_null(col, is_default); - } - } - - ctx.did_change(); - return; - } - - if (is_array(property.type)) { - if (property.type == PropertyType::LinkingObjects) - throw ReadOnlyPropertyException(m_object_schema->name, property.name); - - ContextType child_ctx(ctx, property); - List list(m_realm, m_obj, col); - list.assign(child_ctx, value, policy); - ctx.did_change(); - return; - } - - ValueUpdater<ValueType, ContextType> updater{ctx, property, value, - m_obj, col, policy, is_default}; - switch_on_type(property.type, updater); - ctx.did_change(); -} - -template <typename ValueType, typename ContextType> -ValueType Object::get_property_value_impl(ContextType& ctx, const Property &property) const -{ - verify_attached(); - - ColKey column{property.column_key}; - if (is_nullable(property.type) && m_obj.is_null(column)) - return ctx.null_value(); - if (is_array(property.type) && property.type != PropertyType::LinkingObjects) - return ctx.box(List(m_realm, m_obj, column)); - - switch (property.type & ~PropertyType::Flags) { - case PropertyType::Bool: return ctx.box(m_obj.get<bool>(column)); - case PropertyType::Int: return is_nullable(property.type) ? ctx.box(*m_obj.get<util::Optional<int64_t>>(column)) : ctx.box(m_obj.get<int64_t>(column)); - case PropertyType::Float: return ctx.box(m_obj.get<float>(column)); - case PropertyType::Double: return ctx.box(m_obj.get<double>(column)); - case PropertyType::String: return ctx.box(m_obj.get<StringData>(column)); - case PropertyType::Data: return ctx.box(m_obj.get<BinaryData>(column)); - case PropertyType::Date: return ctx.box(m_obj.get<Timestamp>(column)); -// case PropertyType::Any: return ctx.box(m_obj.get<Mixed>(column)); - case PropertyType::Object: { - auto linkObjectSchema = m_realm->schema().find(property.object_type); - return ctx.box(Object(m_realm, *linkObjectSchema, - const_cast<Obj&>(m_obj).get_linked_object(column))); - } - case PropertyType::LinkingObjects: { - auto target_object_schema = m_realm->schema().find(property.object_type); - auto link_property = target_object_schema->property_for_name(property.link_origin_property_name); - auto table = m_realm->read_group().get_table(target_object_schema->table_key); - auto tv = const_cast<Obj&>(m_obj).get_backlink_view(table, ColKey(link_property->column_key)); - return ctx.box(Results(m_realm, std::move(tv))); - } - default: REALM_UNREACHABLE(); - } -} - -template<typename ValueType, typename ContextType> -Object Object::create(ContextType& ctx, std::shared_ptr<Realm> const& realm, - StringData object_type, ValueType value, - CreatePolicy policy, ObjKey current_obj, Obj* out_row) -{ - auto object_schema = realm->schema().find(object_type); - REALM_ASSERT(object_schema != realm->schema().end()); - return create(ctx, realm, *object_schema, value, policy, current_obj, out_row); -} - -template<typename ValueType, typename ContextType> -Mixed as_mixed(ContextType& ctx, ValueType& value, PropertyType type) -{ - if (!value) - return {}; - return switch_on_type(type, [&](auto* t) { - return Mixed(ctx.template unbox<NonObjTypeT<decltype(*t)>>(*value)); - }); -} - -template<typename ValueType, typename ContextType> -Object Object::create(ContextType& ctx, std::shared_ptr<Realm> const& realm, - ObjectSchema const& object_schema, ValueType value, - CreatePolicy policy, ObjKey current_obj, Obj* out_row) -{ - realm->verify_in_write(); - - // When setting each property, we normally want to skip over the primary key - // as that's set as part of object creation. However, during migrations the - // property marked as the primary key in the schema may not currently be - // considered a primary key by core, and so will need to be set. - bool skip_primary = true; - // If the input value is missing values for any of the properties we want to - // set the propery to the default value for new objects, but leave it - // untouched for existing objects. - bool created = false; - - Obj obj; - auto table = realm->read_group().get_table(object_schema.table_key); - - // If there's a primary key, we need to first check if an object with the - // same primary key already exists. If it does, we either update that object - // or throw an exception if updating is disabled. - if (auto primary_prop = object_schema.primary_key_property()) { - auto primary_value = ctx.value_for_property(value, *primary_prop, - primary_prop - &object_schema.persisted_properties[0]); - if (!primary_value) - primary_value = ctx.default_value_for_property(object_schema, *primary_prop); - if (!primary_value && !is_nullable(primary_prop->type)) - throw MissingPropertyValueException(object_schema.name, primary_prop->name); - - // When changing the primary key of a table, we remove the existing pk (if any), call - // the migration function, then add the new pk (if any). This means that we can't call - // create_object_with_primary_key(), and creating duplicate primary keys is allowed as - // long as they're unique by the end of the migration. - if (table->get_primary_key_column() == ColKey{}) { - REALM_ASSERT(realm->is_in_migration()); - if (policy != CreatePolicy::ForceCreate) { - if (auto key = get_for_primary_key_impl(ctx, *table, *primary_prop, *primary_value)) - obj = table->get_object(key); - } - if (!obj) - skip_primary = false; - } - else { - obj = table->create_object_with_primary_key(as_mixed(ctx, primary_value, primary_prop->type), &created); - if (!created && policy == CreatePolicy::ForceCreate) { - if (!realm->is_in_migration()) { - throw std::logic_error(util::format("Attempting to create an object of type '%1' with an existing primary key value '%2'.", - object_schema.name, ctx.print(*primary_value))); - } - table->set_primary_key_column(ColKey{}); - skip_primary = false; - obj = {}; - } - } - } - - // No primary key (possibly temporarily due to migrations). If we're - // currently performing a recursive update on an existing object tree then - // an object key was passed in that we need to look up, and otherwise we - // need to create the new object. - if (!obj) { - if (policy == CreatePolicy::UpdateModified && current_obj) { - obj = table->get_object(current_obj); - } - else { - obj = table->create_object(); - created = true; - } - } - - Object object(realm, object_schema, obj); - // KVO in Cocoa requires that the obj ivar on the wrapper object be set - // *before* we start setting the properties, so it passes in a pointer to - // that. - if (out_row) - *out_row = obj; - for (size_t i = 0; i < object_schema.persisted_properties.size(); ++i) { - auto& prop = object_schema.persisted_properties[i]; - if (skip_primary && prop.is_primary) - continue; - - auto v = ctx.value_for_property(value, prop, i); - if (!created && !v) - continue; - - bool is_default = false; - if (!v) { - v = ctx.default_value_for_property(object_schema, prop); - is_default = true; - } - // We consider null or a missing value to be equivalent to an empty - // array for historical reasons; the original implementation did this - // accidentally and it's not worth changing. - if ((!v || ctx.is_null(*v)) && !is_nullable(prop.type) && !is_array(prop.type)) { - if (prop.is_primary || !ctx.allow_missing(value)) - throw MissingPropertyValueException(object_schema.name, prop.name); - } - if (v) - object.set_property_value_impl(ctx, prop, *v, policy, is_default); - } -#if REALM_ENABLE_SYNC - if (realm->is_partial() && object_schema.name == "__User") { - object.ensure_user_in_everyone_role(); - object.ensure_private_role_exists_for_user(); - } -#endif - return object; -} - -template<typename ValueType, typename ContextType> -Object Object::get_for_primary_key(ContextType& ctx, std::shared_ptr<Realm> const& realm, - StringData object_type, ValueType primary_value) -{ - auto object_schema = realm->schema().find(object_type); - REALM_ASSERT(object_schema != realm->schema().end()); - return get_for_primary_key(ctx, realm, *object_schema, primary_value); -} - -template<typename ValueType, typename ContextType> -Object Object::get_for_primary_key(ContextType& ctx, std::shared_ptr<Realm> const& realm, - const ObjectSchema &object_schema, - ValueType primary_value) -{ - auto primary_prop = object_schema.primary_key_property(); - if (!primary_prop) { - throw MissingPrimaryKeyException(object_schema.name); - } - - TableRef table; - if (object_schema.table_key) - table = realm->read_group().get_table(object_schema.table_key); - if (!table) - return Object(realm, object_schema, Obj()); - auto key = get_for_primary_key_impl(ctx, *table, *primary_prop, primary_value); - return Object(realm, object_schema, key ? table->get_object(key) : Obj{}); -} - -template<typename ValueType, typename ContextType> -ObjKey Object::get_for_primary_key_impl(ContextType& ctx, Table const& table, - const Property &primary_prop, - ValueType primary_value) { - bool is_null = ctx.is_null(primary_value); - if (is_null && !is_nullable(primary_prop.type)) - throw std::logic_error("Invalid null value for non-nullable primary key."); - if (primary_prop.type == PropertyType::String) { - return table.find_first(primary_prop.column_key, - ctx.template unbox<StringData>(primary_value)); - } - if (is_nullable(primary_prop.type)) - return table.find_first(primary_prop.column_key, - ctx.template unbox<util::Optional<int64_t>>(primary_value)); - return table.find_first(primary_prop.column_key, - ctx.template unbox<int64_t>(primary_value)); -} - -} // namespace realm - -#endif // REALM_OS_OBJECT_ACCESSOR_HPP diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object_schema.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object_schema.cpp deleted file mode 100644 index c3b91cb82..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object_schema.cpp +++ /dev/null @@ -1,343 +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 "feature_checks.hpp" -#include "object_store.hpp" -#include "property.hpp" -#include "schema.hpp" - -#include <realm/data_type.hpp> -#include <realm/group.hpp> -#include <realm/table.hpp> - -using namespace realm; - -ObjectSchema::ObjectSchema() = default; -ObjectSchema::~ObjectSchema() = default; - -ObjectSchema::ObjectSchema(std::string name, std::initializer_list<Property> persisted_properties) -: ObjectSchema(std::move(name), persisted_properties, {}) -{ -} - -ObjectSchema::ObjectSchema(std::string name, std::initializer_list<Property> persisted_properties, - std::initializer_list<Property> computed_properties) -: name(std::move(name)) -, persisted_properties(persisted_properties) -, computed_properties(computed_properties) -{ - for (auto const& prop : persisted_properties) { - if (prop.is_primary) { - primary_key = prop.name; - break; - } - } -} - -PropertyType ObjectSchema::from_core_type(Table const& table, ColKey col) -{ - auto flags = PropertyType::Required; - auto attr = table.get_column_attr(col); - if (attr.test(col_attr_Nullable)) - flags |= PropertyType::Nullable; - if (attr.test(col_attr_List)) - flags |= PropertyType::Array; - switch (table.get_column_type(col)) { - case type_Int: return PropertyType::Int | flags; - case type_Float: return PropertyType::Float | flags; - case type_Double: return PropertyType::Double | flags; - case type_Bool: return PropertyType::Bool | flags; - case type_String: return PropertyType::String | flags; - case type_Binary: return PropertyType::Data | flags; - case type_Timestamp: return PropertyType::Date | flags; - case type_OldMixed: return PropertyType::Any | flags; - case type_Link: return PropertyType::Object | PropertyType::Nullable; - case type_LinkList: return PropertyType::Object | PropertyType::Array; - default: REALM_UNREACHABLE(); - } -} - -ObjectSchema::ObjectSchema(Group const& group, StringData name, TableKey key) -: name(name) -{ - ConstTableRef table; - if (key) { - table = group.get_table(key); - } - else { - table = ObjectStore::table_for_object_type(group, name); - } - table_key = table->get_key(); - - size_t count = table->get_column_count(); - persisted_properties.reserve(count); - - for (auto col_key : table->get_column_keys()) { - StringData column_name = table->get_column_name(col_key); - -#if REALM_ENABLE_SYNC - // The object ID column is an implementation detail, and is omitted from the schema. - // FIXME: this can go away once sync adopts stable ids? - if (column_name.begins_with("!")) - continue; -#endif - - Property property; - property.name = column_name; - property.type = ObjectSchema::from_core_type(*table, col_key); - property.is_indexed = table->has_search_index(col_key) || table->get_primary_key_column() == col_key; - property.column_key = col_key; - - if (property.type == PropertyType::Object) { - // set link type for objects and arrays - ConstTableRef linkTable = table->get_link_target(col_key); - property.object_type = ObjectStore::object_type_for_table_name(linkTable->get_name().data()); - } - persisted_properties.push_back(std::move(property)); - } - - primary_key = ObjectStore::get_primary_key_for_object(group, name); - set_primary_key_property(); -} - -Property *ObjectSchema::property_for_name(StringData name) noexcept -{ - for (auto& prop : persisted_properties) { - if (StringData(prop.name) == name) { - return ∝ - } - } - for (auto& prop : computed_properties) { - if (StringData(prop.name) == name) { - return ∝ - } - } - return nullptr; -} - -Property *ObjectSchema::property_for_public_name(StringData public_name) noexcept -{ - // If no `public_name` is defined, the internal `name` is also considered the public name. - for (auto& prop : persisted_properties) { - if (prop.public_name == public_name || (prop.public_name.empty() && prop.name == public_name)) - return ∝ - } - - // Computed properties are not persisted, so creating a public name for such properties - // are a bit pointless since the internal name is already the "public name", but since - // this distinction isn't visible in the Property struct we allow it anyway. - for (auto& prop : computed_properties) { - if (StringData(prop.public_name.empty() ? prop.name : prop.public_name) == public_name) - return ∝ - } - return nullptr; -} - -const Property *ObjectSchema::property_for_public_name(StringData public_name) const noexcept -{ - return const_cast<ObjectSchema *>(this)->property_for_public_name(public_name); -} - -const Property *ObjectSchema::property_for_name(StringData name) const noexcept -{ - return const_cast<ObjectSchema *>(this)->property_for_name(name); -} - -bool ObjectSchema::property_is_computed(Property const& property) const noexcept -{ - auto end = computed_properties.end(); - return std::find(computed_properties.begin(), end, property) != end; -} - -void ObjectSchema::set_primary_key_property() noexcept -{ - if (primary_key.length()) { - if (auto primary_key_prop = primary_key_property()) { - primary_key_prop->is_primary = true; - } - } -} - -static void validate_property(Schema const& schema, - std::string const& object_name, - Property const& prop, - Property const** primary, - std::vector<ObjectSchemaValidationException>& exceptions) -{ - if (prop.type == PropertyType::LinkingObjects && !is_array(prop.type)) { - exceptions.emplace_back("Linking Objects property '%1.%2' must be an array.", - object_name, prop.name); - } - - // check nullablity - if (is_nullable(prop.type) && !prop.type_is_nullable()) { - exceptions.emplace_back("Property '%1.%2' of type '%3' cannot be nullable.", - object_name, prop.name, string_for_property_type(prop.type)); - } - else if (prop.type == PropertyType::Object && !is_nullable(prop.type) && !is_array(prop.type)) { - exceptions.emplace_back("Property '%1.%2' of type 'object' must be nullable.", object_name, prop.name); - } - - // check primary keys - if (prop.is_primary) { - if (prop.type != PropertyType::Int && prop.type != PropertyType::String) { - exceptions.emplace_back("Property '%1.%2' of type '%3' cannot be made the primary key.", - object_name, prop.name, string_for_property_type(prop.type)); - } - if (*primary) { - exceptions.emplace_back("Properties '%1' and '%2' are both marked as the primary key of '%3'.", - prop.name, (*primary)->name, object_name); - } - *primary = ∝ - } - - // check indexable - if (prop.is_indexed && !prop.type_is_indexable()) { - exceptions.emplace_back("Property '%1.%2' of type '%3' cannot be indexed.", - object_name, prop.name, string_for_property_type(prop.type)); - } - - // check that only link properties have object types - if (prop.type != PropertyType::LinkingObjects && !prop.link_origin_property_name.empty()) { - exceptions.emplace_back("Property '%1.%2' of type '%3' cannot have an origin property name.", - object_name, prop.name, string_for_property_type(prop.type)); - } - else if (prop.type == PropertyType::LinkingObjects && prop.link_origin_property_name.empty()) { - exceptions.emplace_back("Property '%1.%2' of type '%3' must have an origin property name.", - object_name, prop.name, string_for_property_type(prop.type)); - } - - if (prop.type != PropertyType::Object && prop.type != PropertyType::LinkingObjects) { - if (!prop.object_type.empty()) { - exceptions.emplace_back("Property '%1.%2' of type '%3' cannot have an object type.", - object_name, prop.name, prop.type_string()); - } - return; - } - - - // check that the object_type is valid for link properties - auto it = schema.find(prop.object_type); - if (it == schema.end()) { - exceptions.emplace_back("Property '%1.%2' of type '%3' has unknown object type '%4'", - object_name, prop.name, string_for_property_type(prop.type), prop.object_type); - return; - } - if (prop.type != PropertyType::LinkingObjects) { - return; - } - - const Property *origin_property = it->property_for_name(prop.link_origin_property_name); - if (!origin_property) { - exceptions.emplace_back("Property '%1.%2' declared as origin of linking objects property '%3.%4' does not exist", - prop.object_type, prop.link_origin_property_name, - object_name, prop.name); - } - else if (origin_property->type != PropertyType::Object) { - exceptions.emplace_back("Property '%1.%2' declared as origin of linking objects property '%3.%4' is not a link", - prop.object_type, prop.link_origin_property_name, - object_name, prop.name); - } - else if (origin_property->object_type != object_name) { - exceptions.emplace_back("Property '%1.%2' declared as origin of linking objects property '%3.%4' links to type '%5'", - prop.object_type, prop.link_origin_property_name, - object_name, prop.name, origin_property->object_type); - } -} - -void ObjectSchema::validate(Schema const& schema, std::vector<ObjectSchemaValidationException>& exceptions) const -{ - std::vector<StringData> public_property_names; - std::vector<StringData> internal_property_names; - internal_property_names.reserve(persisted_properties.size() + computed_properties.size()); - auto gather_names = [&](auto const &properties) { - for (auto const &prop : properties) { - internal_property_names.push_back(prop.name); - if (!prop.public_name.empty()) - public_property_names.push_back(prop.public_name); - } - }; - gather_names(persisted_properties); - gather_names(computed_properties); - std::sort(public_property_names.begin(), public_property_names.end()); - std::sort(internal_property_names.begin(), internal_property_names.end()); - - // Check that property names and aliases are unique - auto for_each_duplicate = [](auto &&container, auto &&fn) { - auto end = container.end(); - for (auto it = std::adjacent_find(container.begin(), end); it != end; it = std::adjacent_find(it + 2, end)) - fn(*it); - }; - for_each_duplicate(public_property_names, [&](auto public_property_name) { - exceptions.emplace_back("Alias '%1' appears more than once in the schema for type '%2'.", - public_property_name, name); - }); - for_each_duplicate(internal_property_names, [&](auto internal_name) { - exceptions.emplace_back("Property '%1' appears more than once in the schema for type '%2'.", - internal_name, name); - }); - - // Check that no aliases conflict with property names - struct ErrorWriter { - ObjectSchema const &os; - std::vector<ObjectSchemaValidationException> &exceptions; - - struct Proxy { - ObjectSchema const &os; - std::vector<ObjectSchemaValidationException> &exceptions; - - Proxy &operator=(StringData name) { - exceptions.emplace_back( - "Property '%1.%2' has an alias '%3' that conflicts with a property of the same name.", - os.name, os.property_for_public_name(name)->name, name); - return *this; - } - }; - - Proxy operator*() { return Proxy{os, exceptions}; } - ErrorWriter &operator=(const ErrorWriter &) { return *this; } - ErrorWriter &operator++() { return *this; } - ErrorWriter &operator++(int) { return *this; } - } writer{*this, exceptions}; - std::set_intersection(public_property_names.begin(), public_property_names.end(), - internal_property_names.begin(), internal_property_names.end(), writer); - - // Validate all properties - const Property *primary = nullptr; - for (auto const& prop : persisted_properties) { - validate_property(schema, name, prop, &primary, exceptions); - } - for (auto const& prop : computed_properties) { - validate_property(schema, name, prop, &primary, exceptions); - } - - if (!primary_key.empty() && !primary && !primary_key_property()) { - exceptions.emplace_back("Specified primary key '%1.%2' does not exist.", name, primary_key); - } -} - -namespace realm { -bool operator==(ObjectSchema const& a, ObjectSchema const& b) noexcept -{ - return std::tie(a.name, a.primary_key, a.persisted_properties, a.computed_properties) - == std::tie(b.name, b.primary_key, b.persisted_properties, b.computed_properties); - -} -} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object_schema.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object_schema.hpp deleted file mode 100644 index 072a1c203..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object_schema.hpp +++ /dev/null @@ -1,82 +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 <realm/keys.hpp> -#include <realm/string_data.hpp> - -#include <string> -#include <vector> - -namespace realm { -class Group; -class Schema; -class Table; -enum class PropertyType: unsigned char; -struct ObjectSchemaValidationException; -struct Property; - -class ObjectSchema { -public: - ObjectSchema(); - ObjectSchema(std::string name, std::initializer_list<Property> persisted_properties); - ObjectSchema(std::string name, std::initializer_list<Property> persisted_properties, - std::initializer_list<Property> computed_properties); - ~ObjectSchema(); - - ObjectSchema(ObjectSchema const&) = default; - ObjectSchema(ObjectSchema&&) noexcept = default; - ObjectSchema& operator=(ObjectSchema const&) = default; - ObjectSchema& operator=(ObjectSchema&&) noexcept = default; - - // create object schema from existing table - // if no table key is provided it is looked up in the group - ObjectSchema(Group const& group, StringData name, TableKey key); - - std::string name; - std::vector<Property> persisted_properties; - std::vector<Property> computed_properties; - std::string primary_key; - TableKey table_key; - - Property *property_for_public_name(StringData public_name) noexcept; - const Property *property_for_public_name(StringData public_name) const noexcept; - Property *property_for_name(StringData name) noexcept; - const Property *property_for_name(StringData name) const noexcept; - Property *primary_key_property() noexcept { - return property_for_name(primary_key); - } - const Property *primary_key_property() const noexcept { - return property_for_name(primary_key); - } - bool property_is_computed(Property const& property) const noexcept; - - void validate(Schema const& schema, std::vector<ObjectSchemaValidationException>& exceptions) const; - - friend bool operator==(ObjectSchema const& a, ObjectSchema const& b) noexcept; - - static PropertyType from_core_type(Table const& table, ColKey col); - -private: - void set_primary_key_property() noexcept; -}; -} - -#endif /* defined(REALM_OBJECT_SCHEMA_HPP) */ diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object_store.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object_store.cpp deleted file mode 100644 index c49efae7c..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object_store.cpp +++ /dev/null @@ -1,926 +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 "feature_checks.hpp" -#include "object_schema.hpp" -#include "schema.hpp" -#include "shared_realm.hpp" -#include "sync/partial_sync.hpp" - -#include <realm/group.hpp> -#include <realm/table.hpp> -#include <realm/table_view.hpp> -#include <realm/util/assert.hpp> - -#if REALM_ENABLE_SYNC -#include <realm/sync/object.hpp> -#include <realm/sync/permissions.hpp> -#include <realm/sync/instruction_replication.hpp> -#endif // REALM_ENABLE_SYNC - -#include <string.h> - -using namespace realm; - -constexpr uint64_t ObjectStore::NotVersioned; - -namespace { -const char * const c_metadataTableName = "metadata"; -const char * const c_versionColumnName = "version"; - -const char c_object_table_prefix[] = "class_"; - -void create_metadata_tables(Group& group) { - // The 'metadata' table is simply ignored by Sync - TableRef metadata_table = group.get_or_add_table(c_metadataTableName); - - if (metadata_table->get_column_count() == 0) { - metadata_table->add_column(type_Int, c_versionColumnName); - metadata_table->create_object().set(c_versionColumnName, int64_t(ObjectStore::NotVersioned)); - } -} - -void set_schema_version(Group& group, uint64_t version) { - group.get_table(c_metadataTableName)->get_object(0).set<int64_t>(c_versionColumnName, version); -} - -template<typename Group> -auto table_for_object_schema(Group& group, ObjectSchema const& object_schema) -{ - return ObjectStore::table_for_object_type(group, object_schema.name); -} - -DataType to_core_type(PropertyType type) -{ - REALM_ASSERT(type != PropertyType::Object); // Link columns have to be handled differently - REALM_ASSERT(type != PropertyType::Any); // Mixed columns can't be created - switch (type & ~PropertyType::Flags) { - case PropertyType::Int: return type_Int; - case PropertyType::Bool: return type_Bool; - case PropertyType::Float: return type_Float; - case PropertyType::Double: return type_Double; - case PropertyType::String: return type_String; - case PropertyType::Date: return type_Timestamp; - case PropertyType::Data: return type_Binary; - default: REALM_COMPILER_HINT_UNREACHABLE(); - } -} - -ColKey add_column(Group& group, Table& table, Property const& property) -{ - // Cannot directly insert a LinkingObjects column (a computed property). - // LinkingObjects must be an artifact of an existing link column. - REALM_ASSERT(property.type != PropertyType::LinkingObjects); - - if (property.is_primary) { - // Primary key columns should have been created when the table was created - if (auto col = table.get_column_key(property.name)) { - return col; - } - } - if (property.type == PropertyType::Object) { - auto target_name = ObjectStore::table_name_for_object_type(property.object_type); - TableRef link_table = group.get_table(target_name); - REALM_ASSERT(link_table); - return table.add_column_link(is_array(property.type) ? type_LinkList : type_Link, - property.name, *link_table); - } - else if (is_array(property.type)) { - return table.add_column_list(to_core_type(property.type & ~PropertyType::Flags), - property.name, is_nullable(property.type)); - } - else { - auto key = table.add_column(to_core_type(property.type), property.name, is_nullable(property.type)); - if (property.requires_index()) - table.add_search_index(key); - return key; - } -} - -void replace_column(Group& group, Table& table, Property const& old_property, - Property const& new_property) -{ - table.remove_column(old_property.column_key); - add_column(group, table, new_property); -} - -TableRef create_table(Group& group, ObjectSchema const& object_schema) -{ - auto name = ObjectStore::table_name_for_object_type(object_schema.name); - - TableRef table; - if (auto* pk_property = object_schema.primary_key_property()) { - table = group.get_table(name); - if (!table) { - table = group.add_table_with_primary_key(name, to_core_type(pk_property->type), pk_property->name, - is_nullable(pk_property->type)); - } - } - else { - table = group.get_or_add_table(name); - } - - return table; -} - -void add_initial_columns(Group& group, ObjectSchema const& object_schema) -{ - auto name = ObjectStore::table_name_for_object_type(object_schema.name); - TableRef table = group.get_table(name); - - for (auto const& prop : object_schema.persisted_properties) { -#if REALM_ENABLE_SYNC - // The sync::create_table* functions create the PK column for us. - if (prop.is_primary) - continue; -#endif // REALM_ENABLE_SYNC - add_column(group, *table, prop); - } -} - -void make_property_optional(Table& table, Property property) -{ - property.type |= PropertyType::Nullable; - const bool throw_on_null = false; - property.column_key = table.set_nullability(property.column_key, true, throw_on_null); -} - -void make_property_required(Group& group, Table& table, Property property) -{ - property.type &= ~PropertyType::Nullable; - table.remove_column(property.column_key); - property.column_key = add_column(group, table, property).value; -} - -} // anonymous namespace - -void ObjectStore::set_schema_version(Group& group, uint64_t version) { - ::create_metadata_tables(group); - ::set_schema_version(group, version); -} - -uint64_t ObjectStore::get_schema_version(Group const& group) { - ConstTableRef table = group.get_table(c_metadataTableName); - if (!table || table->get_column_count() == 0) { - return ObjectStore::NotVersioned; - } - return table->get_object(0).get<int64_t>(c_versionColumnName); -} - -StringData ObjectStore::get_primary_key_for_object(Group const& group, StringData object_type) { - if (ConstTableRef table = table_for_object_type(group, object_type)) { - if (auto col = table->get_primary_key_column()) { - return table->get_column_name(col); - } - } - return ""; -} - -void ObjectStore::set_primary_key_for_object(Group& group, StringData object_type, StringData primary_key) { - auto t = table_for_object_type(group, object_type); - ColKey pk_col; - if (primary_key.size()) { - pk_col = t->get_column_key(primary_key); - REALM_ASSERT(pk_col); - } - t->set_primary_key_column(pk_col); -} - -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) + std::string(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(Group const& group, StringData object_type) { - auto name = table_name_for_object_type(object_type); - return group.get_table(name); -} - -namespace { -struct SchemaDifferenceExplainer { - std::vector<ObjectSchemaValidationException> errors; - - void operator()(schema_change::AddTable op) - { - errors.emplace_back("Class '%1' has been added.", op.object->name); - } - - void operator()(schema_change::RemoveTable) - { - // We never do anything for RemoveTable - } - - void operator()(schema_change::AddInitialProperties) - { - // Nothing. Always preceded by AddTable. - } - - void operator()(schema_change::AddProperty op) - { - errors.emplace_back("Property '%1.%2' has been added.", op.object->name, op.property->name); - } - - void operator()(schema_change::RemoveProperty op) - { - errors.emplace_back("Property '%1.%2' has been removed.", op.object->name, op.property->name); - } - - void operator()(schema_change::ChangePropertyType op) - { - errors.emplace_back("Property '%1.%2' has been changed from '%3' to '%4'.", - op.object->name, op.new_property->name, - op.old_property->type_string(), - op.new_property->type_string()); - } - - void operator()(schema_change::MakePropertyNullable op) - { - errors.emplace_back("Property '%1.%2' has been made optional.", op.object->name, op.property->name); - } - - void operator()(schema_change::MakePropertyRequired op) - { - errors.emplace_back("Property '%1.%2' has been made required.", op.object->name, op.property->name); - } - - void operator()(schema_change::ChangePrimaryKey op) - { - if (op.property && !op.object->primary_key.empty()) { - errors.emplace_back("Primary Key for class '%1' has changed from '%2' to '%3'.", - op.object->name, op.object->primary_key, op.property->name); - } - else if (op.property) { - errors.emplace_back("Primary Key for class '%1' has been added.", op.object->name); - } - else { - errors.emplace_back("Primary Key for class '%1' has been removed.", op.object->name); - } - } - - void operator()(schema_change::AddIndex op) - { - errors.emplace_back("Property '%1.%2' has been made indexed.", op.object->name, op.property->name); - } - - void operator()(schema_change::RemoveIndex op) - { - errors.emplace_back("Property '%1.%2' has been made unindexed.", op.object->name, op.property->name); - } -}; - -class TableHelper { -public: - TableHelper(Group& g) : m_group(g) { } - - Table& operator()(const ObjectSchema* object_schema) - { - if (object_schema != m_current_object_schema) { - m_current_table = table_for_object_schema(m_group, *object_schema); - m_current_object_schema = object_schema; - } - REALM_ASSERT(m_current_table); - return *m_current_table; - } - -private: - Group& m_group; - const ObjectSchema* m_current_object_schema = nullptr; - TableRef m_current_table; -}; - -template<typename ErrorType, typename Verifier> -void verify_no_errors(Verifier&& verifier, std::vector<SchemaChange> const& changes) -{ - for (auto& change : changes) { - change.visit(verifier); - } - - if (!verifier.errors.empty()) { - throw ErrorType(verifier.errors); - } -} -} // anonymous namespace - -bool ObjectStore::needs_migration(std::vector<SchemaChange> const& changes) -{ - using namespace schema_change; - struct Visitor { - bool operator()(AddIndex) { return false; } - bool operator()(AddInitialProperties) { return false; } - bool operator()(AddProperty) { return true; } - bool operator()(AddTable) { return false; } - bool operator()(RemoveTable) { return false; } - bool operator()(ChangePrimaryKey) { return true; } - bool operator()(ChangePropertyType) { return true; } - bool operator()(MakePropertyNullable) { return true; } - bool operator()(MakePropertyRequired) { return true; } - bool operator()(RemoveIndex) { return false; } - bool operator()(RemoveProperty) { return true; } - }; - - return std::any_of(begin(changes), end(changes), - [](auto&& change) { return change.visit(Visitor()); }); -} - -void ObjectStore::verify_no_changes_required(std::vector<SchemaChange> const& changes) -{ - verify_no_errors<SchemaMismatchException>(SchemaDifferenceExplainer(), changes); -} - -void ObjectStore::verify_no_migration_required(std::vector<SchemaChange> const& changes) -{ - using namespace schema_change; - struct Verifier : SchemaDifferenceExplainer { - using SchemaDifferenceExplainer::operator(); - - // Adding a table or adding/removing indexes can be done automatically. - // All other changes require migrations. - void operator()(AddTable) { } - void operator()(AddInitialProperties) { } - void operator()(AddIndex) { } - void operator()(RemoveIndex) { } - } verifier; - verify_no_errors<SchemaMismatchException>(verifier, changes); -} - -bool ObjectStore::verify_valid_additive_changes(std::vector<SchemaChange> const& changes, bool update_indexes) -{ - using namespace schema_change; - struct Verifier : SchemaDifferenceExplainer { - using SchemaDifferenceExplainer::operator(); - - bool index_changes = false; - bool other_changes = false; - - // Additive mode allows adding things, extra columns, and adding/removing indexes - void operator()(AddTable) { other_changes = true; } - void operator()(AddInitialProperties) { other_changes = true; } - void operator()(AddProperty) { other_changes = true; } - void operator()(RemoveProperty) { } - void operator()(AddIndex) { index_changes = true; } - void operator()(RemoveIndex) { index_changes = true; } - } verifier; - verify_no_errors<InvalidSchemaChangeException>(verifier, changes); - return verifier.other_changes || (verifier.index_changes && update_indexes); -} - -void ObjectStore::verify_valid_external_changes(std::vector<SchemaChange> const& changes) -{ - using namespace schema_change; - struct Verifier : SchemaDifferenceExplainer { - using SchemaDifferenceExplainer::operator(); - - // Adding new things is fine - void operator()(AddTable) { } - void operator()(AddInitialProperties) { } - void operator()(AddProperty) { } - void operator()(AddIndex) { } - void operator()(RemoveIndex) { } - - // Deleting tables is not okay - void operator()(RemoveTable op) { - errors.emplace_back("Class '%1' has been removed.", op.object->name); - } - } verifier; - verify_no_errors<InvalidExternalSchemaChangeException>(verifier, changes); -} - -void ObjectStore::verify_compatible_for_immutable_and_readonly(std::vector<SchemaChange> const& changes) -{ - using namespace schema_change; - struct Verifier : SchemaDifferenceExplainer { - using SchemaDifferenceExplainer::operator(); - - void operator()(AddTable) { } - void operator()(AddInitialProperties) { } - void operator()(RemoveProperty) { } - void operator()(AddIndex) { } - void operator()(RemoveIndex) { } - } verifier; - verify_no_errors<InvalidSchemaChangeException>(verifier, changes); -} - -static void apply_non_migration_changes(Group& group, std::vector<SchemaChange> const& changes) -{ - using namespace schema_change; - struct Applier : SchemaDifferenceExplainer { - Applier(Group& group) : group{group}, table{group} { } - Group& group; - TableHelper table; - - // Produce an exception listing the unsupported schema changes for - // everything but the explicitly supported ones - using SchemaDifferenceExplainer::operator(); - - void operator()(AddTable op) { create_table(group, *op.object); } - void operator()(AddInitialProperties op) { add_initial_columns(group, *op.object); } - void operator()(AddIndex op) { table(op.object).add_search_index(op.property->column_key); } - void operator()(RemoveIndex op) { table(op.object).remove_search_index(op.property->column_key); } - } applier{group}; - verify_no_errors<SchemaMismatchException>(applier, changes); -} - -static void create_initial_tables(Group& group, std::vector<SchemaChange> const& changes) -{ - using namespace schema_change; - struct Applier { - Applier(Group& group) : group{group}, table{group} { } - Group& group; - TableHelper table; - - void operator()(AddTable op) { create_table(group, *op.object); } - void operator()(RemoveTable) { } - void operator()(AddInitialProperties op) { add_initial_columns(group, *op.object); } - - // Note that in normal operation none of these will be hit, as if we're - // creating the initial tables there shouldn't be anything to update. - // Implementing these makes us better able to handle weird - // not-quite-correct files produced by other things and has no obvious - // downside. - void operator()(AddProperty op) { add_column(group, table(op.object), *op.property); } - void operator()(RemoveProperty op) { table(op.object).remove_column(op.property->column_key); } - void operator()(MakePropertyNullable op) { make_property_optional(table(op.object), *op.property); } - void operator()(MakePropertyRequired op) { make_property_required(group, table(op.object), *op.property); } - void operator()(ChangePrimaryKey op) { ObjectStore::set_primary_key_for_object(group, op.object->name, op.property ? StringData{op.property->name} : ""); } - void operator()(AddIndex op) { table(op.object).add_search_index(op.property->column_key); } - void operator()(RemoveIndex op) { table(op.object).remove_search_index(op.property->column_key); } - - void operator()(ChangePropertyType op) - { - replace_column(group, table(op.object), *op.old_property, *op.new_property); - } - } applier{group}; - - for (auto& change : changes) { - change.visit(applier); - } -} - -void ObjectStore::apply_additive_changes(Group& group, std::vector<SchemaChange> const& changes, bool update_indexes) -{ - using namespace schema_change; - struct Applier { - Applier(Group& group, bool update_indexes) - : group{group}, table{group}, update_indexes{update_indexes} { } - Group& group; - TableHelper table; - bool update_indexes; - - void operator()(AddTable op) { create_table(group, *op.object); } - void operator()(RemoveTable) { } - void operator()(AddInitialProperties op) { add_initial_columns(group, *op.object); } - void operator()(AddProperty op) { add_column(group, table(op.object), *op.property); } - void operator()(AddIndex op) { if (update_indexes) table(op.object).add_search_index(op.property->column_key); } - void operator()(RemoveIndex op) { if (update_indexes) table(op.object).remove_search_index(op.property->column_key); } - void operator()(RemoveProperty) { } - - // No need for errors for these, as we've already verified that they aren't present - void operator()(ChangePrimaryKey) { } - void operator()(ChangePropertyType) { } - void operator()(MakePropertyNullable) { } - void operator()(MakePropertyRequired) { } - } applier{group, update_indexes}; - - for (auto& change : changes) { - change.visit(applier); - } -} - -static void apply_pre_migration_changes(Group& group, std::vector<SchemaChange> const& changes) -{ - using namespace schema_change; - struct Applier { - Applier(Group& group) : group{group}, table{group} { } - Group& group; - TableHelper table; - - void operator()(AddTable op) { create_table(group, *op.object); } - void operator()(RemoveTable) { } - void operator()(AddInitialProperties op) { add_initial_columns(group, *op.object); } - void operator()(AddProperty op) { add_column(group, table(op.object), *op.property); } - void operator()(RemoveProperty) { /* delayed until after the migration */ } - void operator()(ChangePropertyType op) { replace_column(group, table(op.object), *op.old_property, *op.new_property); } - void operator()(MakePropertyNullable op) { make_property_optional(table(op.object), *op.property); } - void operator()(MakePropertyRequired op) { make_property_required(group, table(op.object), *op.property); } - void operator()(ChangePrimaryKey op) { table(op.object).set_primary_key_column(ColKey{}); } - void operator()(AddIndex op) { table(op.object).add_search_index(op.property->column_key); } - void operator()(RemoveIndex op) { table(op.object).remove_search_index(op.property->column_key); } - } applier{group}; - - for (auto& change : changes) { - change.visit(applier); - } -} - -enum class DidRereadSchema { Yes, No }; - -static void apply_post_migration_changes(Group& group, - std::vector<SchemaChange> const& changes, - Schema const& initial_schema, - DidRereadSchema did_reread_schema) -{ - using namespace schema_change; - struct Applier { - Applier(Group& group, Schema const& initial_schema, DidRereadSchema did_reread_schema) - : group{group}, initial_schema(initial_schema), table(group) - , did_reread_schema(did_reread_schema == DidRereadSchema::Yes) - { } - Group& group; - Schema const& initial_schema; - TableHelper table; - bool did_reread_schema; - - void operator()(RemoveProperty op) - { - if (!initial_schema.empty() && !initial_schema.find(op.object->name)->property_for_name(op.property->name)) - throw std::logic_error(util::format("Renamed property '%1.%2' does not exist.", op.object->name, op.property->name)); - auto table = table_for_object_schema(group, *op.object); - table->remove_column(op.property->column_key); - } - - void operator()(ChangePrimaryKey op) - { - Table& t = table(op.object); - if (op.property) { - auto col = t.get_column_key(op.property->name); - REALM_ASSERT(col); - t.set_primary_key_column(col); - } - else { - t.set_primary_key_column(ColKey()); - } - } - - void operator()(AddTable op) { create_table(group, *op.object); } - - void operator()(AddInitialProperties op) { - if (did_reread_schema) - add_initial_columns(group, *op.object); - else { - // If we didn't re-read the schema then AddInitialProperties was already taken care of - // during apply_pre_migration_changes. - } - } - - void operator()(AddIndex op) { table(op.object).add_search_index(op.property->column_key); } - void operator()(RemoveIndex op) { table(op.object).remove_search_index(op.property->column_key); } - - void operator()(RemoveTable) { } - void operator()(ChangePropertyType) { } - void operator()(MakePropertyNullable) { } - void operator()(MakePropertyRequired) { } - void operator()(AddProperty) { } - } applier{group, initial_schema, did_reread_schema}; - - for (auto& change : changes) { - change.visit(applier); - } -} - -static void create_default_permissions(Transaction& group, std::vector<SchemaChange> const& changes, - std::string const& sync_user_id) -{ -#if !REALM_ENABLE_SYNC - static_cast<void>(group); - static_cast<void>(changes); - static_cast<void>(sync_user_id); -#else - _impl::initialize_schema(group); - sync::set_up_basic_permissions(group, true); - - // Ensure that this user exists so that local privileges checks work immediately - sync::add_user_to_role(group, sync_user_id, "everyone"); - - // Ensure that the user's private role exists so that local privilege checks work immediately. - ObjectStore::ensure_private_role_exists_for_user(group, sync_user_id); - - // Mark all tables we just created as fully world-accessible - // This has to be done after the first pass of schema init is done so that we can be - // sure that the permissions tables actually exist. - using namespace schema_change; - struct Applier { - Transaction& group; - void operator()(AddTable op) - { - sync::set_class_permissions_for_role(group, op.object->name, "everyone", - static_cast<int>(ComputedPrivileges::All)); - } - - void operator()(RemoveTable) { } - void operator()(AddInitialProperties) { } - void operator()(AddProperty) { } - void operator()(RemoveProperty) { } - void operator()(MakePropertyNullable) { } - void operator()(MakePropertyRequired) { } - void operator()(ChangePrimaryKey) { } - void operator()(AddIndex) { } - void operator()(RemoveIndex) { } - void operator()(ChangePropertyType) { } - } applier{group}; - - for (auto& change : changes) { - change.visit(applier); - } -#endif -} - -#if REALM_ENABLE_SYNC -void ObjectStore::ensure_private_role_exists_for_user(Transaction& group, StringData sync_user_id) -{ - std::string private_role_name = util::format("__User:%1", sync_user_id); - - TableRef roles = ObjectStore::table_for_object_type(group, "__Role"); - ObjKey private_role_ndx = roles->find_first_string(roles->get_column_key("name"), private_role_name); - if (private_role_ndx) { - // The private role already exists, so there's nothing for us to do. - return; - } - - // Add the user to the private role, creating the private role in the process. - sync::add_user_to_role(group, sync_user_id, private_role_name); - - // Set the private role on the user. - private_role_ndx = roles->find_first_string(roles->get_column_key("name"), private_role_name); - TableRef users = ObjectStore::table_for_object_type(group, "__User"); - ObjKey user_ndx = users->find_first_string(users->get_column_key("id"), sync_user_id); - users->get_object(user_ndx).set("role", private_role_ndx); -} -#endif - -void ObjectStore::apply_schema_changes(Transaction& group, uint64_t schema_version, - Schema& target_schema, uint64_t target_schema_version, - SchemaMode mode, std::vector<SchemaChange> const& changes, - util::Optional<std::string> sync_user_id, - std::function<void()> migration_function) -{ - create_metadata_tables(group); - - if (mode == SchemaMode::Additive) { - bool target_schema_is_newer = (schema_version < target_schema_version - || schema_version == ObjectStore::NotVersioned); - - // With sync v2.x, indexes are no longer synced, so there's no reason to avoid creating them. - bool update_indexes = true; - apply_additive_changes(group, changes, update_indexes); - - if (target_schema_is_newer) - set_schema_version(group, target_schema_version); - - if (sync_user_id) - create_default_permissions(group, changes, *sync_user_id); - - set_schema_keys(group, target_schema); - return; - } - - if (schema_version == ObjectStore::NotVersioned) { - create_initial_tables(group, changes); - set_schema_version(group, target_schema_version); - set_schema_keys(group, target_schema); - return; - } - - if (mode == SchemaMode::Manual) { - set_schema_keys(group, target_schema); - if (migration_function) { - migration_function(); - } - - verify_no_changes_required(schema_from_group(group).compare(target_schema)); - group.validate_primary_columns(); - set_schema_keys(group, target_schema); - set_schema_version(group, target_schema_version); - return; - } - - if (schema_version == target_schema_version) { - apply_non_migration_changes(group, changes); - set_schema_keys(group, target_schema); - return; - } - - auto old_schema = schema_from_group(group); - apply_pre_migration_changes(group, changes); - if (migration_function) { - set_schema_keys(group, target_schema); - migration_function(); - - // Migration function may have changed the schema, so we need to re-read it - auto schema = schema_from_group(group); - apply_post_migration_changes(group, schema.compare(target_schema), old_schema, DidRereadSchema::Yes); - group.validate_primary_columns(); - } - else { - apply_post_migration_changes(group, changes, {}, DidRereadSchema::No); - } - - set_schema_version(group, target_schema_version); - set_schema_keys(group, target_schema); -} - -Schema ObjectStore::schema_from_group(Group const& group) { - std::vector<ObjectSchema> schema; - schema.reserve(group.size()); - for (auto key : group.get_table_keys()) { - auto object_type = object_type_for_table_name(group.get_table_name(key)); - if (object_type.size()) { - schema.emplace_back(group, object_type, key); - } - } - return schema; -} - -util::Optional<Property> ObjectStore::property_for_column_index(ConstTableRef& table, ColKey column_key) -{ - StringData column_name = table->get_column_name(column_key); - - Property property; - property.name = column_name; - property.type = ObjectSchema::from_core_type(*table, column_key); - property.is_primary = table->get_primary_key_column() == column_key; - property.is_indexed = table->has_search_index(column_key); - property.column_key = column_key; - - if (property.type == PropertyType::Object) { - // set link type for objects and arrays - ConstTableRef linkTable = table->get_link_target(column_key); - property.object_type = ObjectStore::object_type_for_table_name(linkTable->get_name().data()); - } - return property; -} - -void ObjectStore::set_schema_keys(Group const& group, Schema& schema) -{ - for (auto& object_schema : schema) { - auto table = table_for_object_schema(group, object_schema); - if (!table) { - continue; - } - object_schema.table_key = table->get_key(); - for (auto& property : object_schema.persisted_properties) { - property.column_key = table->get_column_key(property.name); - } - } -} - -void ObjectStore::delete_data_for_object(Group& group, StringData object_type) -{ - if (TableRef table = table_for_object_type(group, object_type)) { - ObjectStore::set_primary_key_for_object(group, object_type, ""); - group.remove_table(table->get_key()); - } -} - -bool ObjectStore::is_empty(Group const& group) -{ - for (auto key : group.get_table_keys()) { - ConstTableRef table = group.get_table(key); - auto object_type = object_type_for_table_name(table->get_name()); - if (object_type.size() == 0 || object_type.begins_with("__")) { - continue; - } - if (!table->is_empty()) { - return false; - } - } - return true; -} - -void ObjectStore::rename_property(Group& group, Schema& target_schema, StringData object_type, StringData old_name, StringData new_name) -{ - TableRef table = table_for_object_type(group, object_type); - if (!table) { - throw std::logic_error(util::format("Cannot rename properties for type '%1' because it does not exist.", object_type)); - } - - auto target_object_schema = target_schema.find(object_type); - if (target_object_schema == target_schema.end()) { - throw std::logic_error(util::format("Cannot rename properties for type '%1' because it has been removed from the Realm.", object_type)); - } - - if (target_object_schema->property_for_name(old_name)) { - throw std::logic_error(util::format("Cannot rename property '%1.%2' to '%3' because the source property still exists.", - object_type, old_name, new_name)); - } - - ObjectSchema table_object_schema(group, object_type, table->get_key()); - Property *old_property = table_object_schema.property_for_name(old_name); - if (!old_property) { - throw std::logic_error(util::format("Cannot rename property '%1.%2' because it does not exist.", object_type, old_name)); - } - - Property *new_property = table_object_schema.property_for_name(new_name); - if (!new_property) { - // New property doesn't exist in the table, which means we're probably - // renaming to an intermediate property in a multi-version migration. - // This is safe because the migration will fail schema validation unless - // this property is renamed again to a valid name before the end. - table->rename_column(old_property->column_key, new_name); - return; - } - - if (old_property->type != new_property->type || old_property->object_type != new_property->object_type) { - throw std::logic_error(util::format("Cannot rename property '%1.%2' to '%3' because it would change from type '%4' to '%5'.", - object_type, old_name, new_name, old_property->type_string(), new_property->type_string())); - } - - if (is_nullable(old_property->type) && !is_nullable(new_property->type)) { - throw std::logic_error(util::format("Cannot rename property '%1.%2' to '%3' because it would change from optional to required.", - object_type, old_name, new_name)); - } - - table->remove_column(new_property->column_key); - table->rename_column(old_property->column_key, new_name); - - if (auto prop = target_object_schema->property_for_name(new_name)) { - prop->column_key = old_property->column_key; - } - - // update nullability for column - if (is_nullable(new_property->type) && !is_nullable(old_property->type)) { - auto prop = *new_property; - prop.column_key = old_property->column_key; - make_property_optional(*table, prop); - } -} - -InvalidSchemaVersionException::InvalidSchemaVersionException(uint64_t old_version, uint64_t new_version) -: logic_error(util::format("Provided schema version %1 is less than last set version %2.", new_version, old_version)) -, m_old_version(old_version), m_new_version(new_version) -{ -} - -SchemaValidationException::SchemaValidationException(std::vector<ObjectSchemaValidationException> const& errors) -: std::logic_error([&] { - std::string message = "Schema validation failed due to the following errors:"; - for (auto const& error : errors) { - message += std::string("\n- ") + error.what(); - } - return message; -}()) -{ -} - -SchemaMismatchException::SchemaMismatchException(std::vector<ObjectSchemaValidationException> const& errors) -: std::logic_error([&] { - std::string message = "Migration is required due to the following errors:"; - for (auto const& error : errors) { - message += std::string("\n- ") + error.what(); - } - return message; -}()) -{ -} - -InvalidSchemaChangeException::InvalidSchemaChangeException(std::vector<ObjectSchemaValidationException> const& errors) -: std::logic_error([&] { - std::string message = "The following changes cannot be made in additive-only schema mode:"; - for (auto const& error : errors) { - message += std::string("\n- ") + error.what(); - } - return message; -}()) -{ -} - -InvalidExternalSchemaChangeException::InvalidExternalSchemaChangeException(std::vector<ObjectSchemaValidationException> const& errors) -: std::logic_error([&] { - std::string message = - "Unsupported schema changes were made by another client or process. For a " - "synchronized Realm, this may be due to the server reverting schema changes which " - "the local user did not have permission to make."; - for (auto const& error : errors) { - message += std::string("\n- ") + error.what(); - } - return message; -}()) -{ -} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object_store.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object_store.hpp deleted file mode 100644 index b989b5fc8..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object_store.hpp +++ /dev/null @@ -1,170 +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 <realm/table_ref.hpp> -#include <realm/util/optional.hpp> - -#include <functional> -#include <string> -#include <vector> -#include <limits> - -namespace realm { -class Group; -class Transaction; -class Schema; -class SchemaChange; -class StringData; -enum class SchemaMode : uint8_t; - -namespace util { -template<typename... Args> -std::string format(const char* fmt, Args&&... args); -} - -class ObjectStore { -public: - // Schema version used for uninitialized Realms - static constexpr uint64_t NotVersioned = std::numeric_limits<uint64_t>::max(); - - // Column name used for subtables which store an array - static constexpr const char* const ArrayColumnName = "!ARRAY_VALUE"; - - // get the last set schema version - static uint64_t get_schema_version(Group const& group); - - // set the schema version without any checks - // and the tables for the schema version and the primary key are created if they don't exist - // NOTE: must be performed within a write transaction - static void set_schema_version(Group& group, uint64_t version); - - // check if all of the changes in the list can be applied automatically, or - // throw if any of them require a schema version bump and migration function - static void verify_no_migration_required(std::vector<SchemaChange> const& changes); - - // Similar to above, but returns a bool rather than throwing/not throwing - static bool needs_migration(std::vector<SchemaChange> const& changes); - - // check if any of the schema changes in the list are forbidden in - // additive-only mode, and if any are throw an exception - // returns true if any of the changes are not no-ops - static bool verify_valid_additive_changes(std::vector<SchemaChange> const& changes, - bool update_indexes=false); - - // check if the schema changes made by a different process made any changes - // which will prevent us from being able to continue (such as removing a - // property we were relying on) - static void verify_valid_external_changes(std::vector<SchemaChange> const& changes); - - static void verify_compatible_for_immutable_and_readonly(std::vector<SchemaChange> const& changes); - - // check if changes is empty, and throw an exception if not - static void verify_no_changes_required(std::vector<SchemaChange> const& changes); - - // 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 - static void apply_schema_changes(Transaction& group, uint64_t schema_version, - Schema& target_schema, uint64_t target_schema_version, - SchemaMode mode, std::vector<SchemaChange> const& changes, - util::Optional<std::string> sync_user_id, - std::function<void()> migration_function={}); - - static void apply_additive_changes(Group&, std::vector<SchemaChange> const&, bool update_indexes); - - // 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(Group const& group, StringData object_type); - - // get existing Schema from a group - static Schema schema_from_group(Group const& group); - - // get the property for a existing column in the given table. return none if the column is reserved internally. - // NOTE: is_primary won't be set for the returned property. - static util::Optional<Property> property_for_column_index(ConstTableRef& table, ColKey column_key); - - static void set_schema_keys(Group const& group, Schema& schema); - - // 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(Group const& group); - - // renames the object_type's column of the old_name to the new name - static void rename_property(Group& group, Schema& schema, StringData object_type, StringData old_name, StringData new_name); - - // get primary key property name for object type - static StringData get_primary_key_for_object(Group const& 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 std::string table_name_for_object_type(StringData class_name); - static StringData object_type_for_table_name(StringData table_name); - - // creates the private role for the given user if it does not exist - static void ensure_private_role_exists_for_user(Transaction& group, StringData sync_user_id); - -private: - friend class ObjectSchema; -}; - -class InvalidSchemaVersionException : public std::logic_error { -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; -}; - -// Schema validation exceptions -struct ObjectSchemaValidationException : public std::logic_error { - ObjectSchemaValidationException(std::string message) : logic_error(std::move(message)) {} - - template<typename... Args> - ObjectSchemaValidationException(const char* fmt, Args&&... args) - : std::logic_error(util::format(fmt, std::forward<Args>(args)...)) { } -}; - -struct SchemaValidationException : public std::logic_error { - SchemaValidationException(std::vector<ObjectSchemaValidationException> const& errors); -}; - -struct SchemaMismatchException : public std::logic_error { - SchemaMismatchException(std::vector<ObjectSchemaValidationException> const& errors); -}; - -struct InvalidSchemaChangeException : public std::logic_error { - InvalidSchemaChangeException(std::vector<ObjectSchemaValidationException> const& errors); -}; - -struct InvalidExternalSchemaChangeException : public std::logic_error { - InvalidExternalSchemaChangeException(std::vector<ObjectSchemaValidationException> const& errors); -}; -} // namespace realm - -#endif /* defined(REALM_OBJECT_STORE_HPP) */ diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/placeholder.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/placeholder.cpp deleted file mode 100644 index 893653421..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/placeholder.cpp +++ /dev/null @@ -1 +0,0 @@ -// This file is intentionally left blank. diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/property.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/property.hpp deleted file mode 100644 index 72198ae08..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/property.hpp +++ /dev/null @@ -1,297 +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 "util/tagged_bool.hpp" - -#include <realm/util/features.h> -// FIXME: keys.hpp is currently pretty heavyweight -#include <realm/keys.hpp> - -#include <string> - -namespace realm { -namespace util { - template<typename> class Optional; -} -class BinaryData; -class Obj; -class StringData; -class Table; -class Timestamp; - -enum class PropertyType : unsigned char { - Int = 0, - Bool = 1, - String = 2, - Data = 3, - Date = 4, - Float = 5, - Double = 6, - Object = 7, // currently must be either Array xor Nullable - LinkingObjects = 8, // currently must be Array and not Nullable - - // deprecated and remains only for reading old files - Any = 9, - - // Flags which can be combined with any of the above types except as noted - Required = 0, - Nullable = 64, - Array = 128, - Flags = Nullable | Array -}; - -struct Property { - using IsPrimary = util::TaggedBool<class IsPrimaryTag>; - using IsIndexed = util::TaggedBool<class IsIndexedTag>; - - // The internal column name used in the Realm file. - std::string name; - - // The public name used by the binding to represent the internal column name in the Realm file. Bindings can use - // this to expose a different name in the binding API, e.g. to map between different naming conventions. - // - // Public names are only ever user defined, they are not persisted on disk, so reading the schema from the file - // will leave this field empty. If `public_name` is empty, the internal and public name are considered to be the same. - // - // ObjectStore will ensure that no conflicts occur between persisted properties and the public name, so - // the public name is just as unique an identifier as the internal name in the file. - // - // In order to respect public names bindings should use `ObjectSchema::property_for_public_name()` in the schema - // and `Object::value_for_property()` in the Object accessor for reading fields defined by the public name. - // - // For queries, bindings should provide an appropriate `KeyPathMapping` definition. Bindings are responsible - // for creating this. - std::string public_name; - PropertyType type = PropertyType::Int; - std::string object_type; - std::string link_origin_property_name; - IsPrimary is_primary = false; - IsIndexed is_indexed = false; - - ColKey column_key; - - Property() = default; - - Property(std::string name, PropertyType type, IsPrimary primary = false, - IsIndexed indexed = false, std::string public_name = ""); - - Property(std::string name, PropertyType type, std::string object_type, - std::string link_origin_property_name = "", std::string public_name = ""); - - Property(Property const&) = default; - Property(Property&&) noexcept = default; - Property& operator=(Property const&) = default; - Property& operator=(Property&&) noexcept = default; - - bool requires_index() const { return is_primary || is_indexed; } - - bool type_is_indexable() const noexcept; - bool type_is_nullable() const noexcept; - - std::string type_string() const; -}; - -template<typename E> -constexpr auto to_underlying(E e) -{ - return static_cast<typename std::underlying_type<E>::type>(e); -} - -inline constexpr PropertyType operator&(PropertyType a, PropertyType b) -{ - return static_cast<PropertyType>(to_underlying(a) & to_underlying(b)); -} - -inline constexpr PropertyType operator|(PropertyType a, PropertyType b) -{ - return static_cast<PropertyType>(to_underlying(a) | to_underlying(b)); -} - -inline constexpr PropertyType operator^(PropertyType a, PropertyType b) -{ - return static_cast<PropertyType>(to_underlying(a) ^ to_underlying(b)); -} - -inline constexpr PropertyType operator~(PropertyType a) -{ - return static_cast<PropertyType>(~to_underlying(a)); -} - -inline constexpr bool operator==(PropertyType a, PropertyType b) -{ - return to_underlying(a & ~PropertyType::Flags) == to_underlying(b & ~PropertyType::Flags); -} - -inline constexpr bool operator!=(PropertyType a, PropertyType b) -{ - return !(a == b); -} - -inline PropertyType& operator&=(PropertyType & a, PropertyType b) -{ - a = a & b; - return a; -} - -inline PropertyType& operator|=(PropertyType & a, PropertyType b) -{ - a = a | b; - return a; -} - -inline PropertyType& operator^=(PropertyType & a, PropertyType b) -{ - a = a ^ b; - return a; -} - -inline constexpr bool is_array(PropertyType a) -{ - return to_underlying(a & PropertyType::Array) == to_underlying(PropertyType::Array); -} - -inline constexpr bool is_nullable(PropertyType a) -{ - return to_underlying(a & PropertyType::Nullable) == to_underlying(PropertyType::Nullable); -} - -// Some of the places we use switch_on_type() the Obj version isn't instantiatable -// or reachable, so we want to map it to a valid type to let the unreachable code compile -template<typename T> -struct NonObjType { - using type = std::remove_reference_t<T>; -}; -template<> -struct NonObjType<Obj&> { - using type = int64_t; -}; -template<typename T> -using NonObjTypeT = typename NonObjType<T>::type; - -template<typename ObjType=Obj, typename Fn> -static auto switch_on_type(PropertyType type, Fn&& fn) -{ - using PT = PropertyType; - bool is_optional = is_nullable(type); - switch (type & ~PropertyType::Flags) { - case PT::Int: return is_optional ? fn((util::Optional<int64_t>*)0) : fn((int64_t*)0); - case PT::Bool: return is_optional ? fn((util::Optional<bool>*)0) : fn((bool*)0); - case PT::Float: return is_optional ? fn((util::Optional<float>*)0) : fn((float*)0); - case PT::Double: return is_optional ? fn((util::Optional<double>*)0) : fn((double*)0); - case PT::String: return fn((StringData*)0); - case PT::Data: return fn((BinaryData*)0); - case PT::Date: return fn((Timestamp*)0); - case PT::Object: return fn((ObjType*)0); - default: REALM_COMPILER_HINT_UNREACHABLE(); - } -} - -static const char *string_for_property_type(PropertyType type) -{ - if (is_array(type)) { - if (type == PropertyType::LinkingObjects) - return "linking objects"; - return "array"; - } - switch (type & ~PropertyType::Flags) { - 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::Object: return "object"; - case PropertyType::Any: return "any"; - case PropertyType::LinkingObjects: return "linking objects"; - default: REALM_COMPILER_HINT_UNREACHABLE(); - } -} - -inline Property::Property(std::string name, PropertyType type, - IsPrimary primary, IsIndexed indexed, - std::string public_name) -: name(std::move(name)) -, public_name(std::move(public_name)) -, type(type) -, is_primary(primary) -, is_indexed(indexed) -{ -} - -inline Property::Property(std::string name, PropertyType type, - std::string object_type, - std::string link_origin_property_name, - std::string public_name) -: name(std::move(name)) -, public_name(std::move(public_name)) -, type(type) -, object_type(std::move(object_type)) -, link_origin_property_name(std::move(link_origin_property_name)) -{ -} - -inline bool Property::type_is_indexable() const noexcept -{ - return type == PropertyType::Int - || type == PropertyType::Bool - || type == PropertyType::Date - || type == PropertyType::String; -} - -inline bool Property::type_is_nullable() const noexcept -{ - return !(is_array(type) && type == PropertyType::Object) && type != PropertyType::LinkingObjects; -} - -inline std::string Property::type_string() const -{ - if (is_array(type)) { - if (type == PropertyType::Object) - return "array<" + object_type + ">"; - if (type == PropertyType::LinkingObjects) - return "linking objects<" + object_type + ">"; - return std::string("array<") + string_for_property_type(type & ~PropertyType::Flags) + ">"; - } - switch (auto base_type = (type & ~PropertyType::Flags)) { - case PropertyType::Object: - return "<" + object_type + ">"; - case PropertyType::LinkingObjects: - return "linking objects<" + object_type + ">"; - default: - return string_for_property_type(base_type); - } -} - -inline bool operator==(Property const& lft, Property const& rgt) -{ - // note: not checking column_key - // ordered roughly by the cost of the check - return to_underlying(lft.type) == to_underlying(rgt.type) - && lft.is_primary == rgt.is_primary - && lft.requires_index() == rgt.requires_index() - && lft.name == rgt.name - && lft.object_type == rgt.object_type - && lft.link_origin_property_name == rgt.link_origin_property_name; -} -} // namespace realm - -#endif // REALM_PROPERTY_HPP diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/results.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/results.cpp deleted file mode 100644 index ea93e85da..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/results.cpp +++ /dev/null @@ -1,1114 +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 "audit.hpp" -#include "object_schema.hpp" -#include "object_store.hpp" -#include "schema.hpp" - -#include <stdexcept> - -namespace realm { - -Results::Results() = default; -Results::~Results() = default; - -Results::Results(SharedRealm r, Query q, DescriptorOrdering o) -: m_realm(std::move(r)) -, m_query(std::move(q)) -, m_table(m_query.get_table()) -, m_descriptor_ordering(std::move(o)) -, m_mode(Mode::Query) -, m_mutex(m_realm && m_realm->is_frozen()) -{ -} - -Results::Results(SharedRealm r, ConstTableRef table) -: m_realm(std::move(r)) -, m_table(table) -, m_mode(Mode::Table) -, m_mutex(m_realm && m_realm->is_frozen()) -{ -} - -Results::Results(std::shared_ptr<Realm> r, std::shared_ptr<LstBase> list) -: m_realm(std::move(r)) -, m_list(list) -, m_mode(Mode::List) -, m_mutex(m_realm && m_realm->is_frozen()) -{ -} - -Results::Results(std::shared_ptr<Realm> r, std::shared_ptr<LstBase> list, DescriptorOrdering o) -: m_realm(std::move(r)) -, m_descriptor_ordering(std::move(o)) -, m_list(std::move(list)) -, m_mode(Mode::List) -, m_mutex(m_realm && m_realm->is_frozen()) -{ -} - -Results::Results(std::shared_ptr<Realm> r, TableView tv, DescriptorOrdering o) -: m_realm(std::move(r)) -, m_table_view(std::move(tv)) -, m_descriptor_ordering(std::move(o)) -, m_mode(Mode::TableView) -, m_mutex(m_realm && m_realm->is_frozen()) -{ - m_table = m_table_view.get_parent(); -} - -Results::Results(std::shared_ptr<Realm> r, std::shared_ptr<LnkLst> lv, util::Optional<Query> q, SortDescriptor s) -: m_realm(std::move(r)) -, m_link_list(std::move(lv)) -, m_mode(Mode::LinkList) -, m_mutex(m_realm && m_realm->is_frozen()) -{ - m_table = m_link_list->get_target_table(); - if (q) { - m_query = std::move(*q); - m_mode = Mode::Query; - } - m_descriptor_ordering.append_sort(std::move(s)); -} - -Results::Results(const Results&) = default; -Results& Results::operator=(const Results&) = default; -Results::Results(Results&&) = default; -Results& Results::operator=(Results&&) = default; - -Results::Mode Results::get_mode() const noexcept -{ - CheckedUniqueLock lock(m_mutex); - return m_mode; -} - -bool Results::is_valid() const -{ - if (m_realm) { - m_realm->verify_thread(); - } - - // Here we cannot just use if (m_table) as it combines a check if the - // reference contains a value and if that value is valid. - // First we check if a table is referenced ... - if (m_table.unchecked_ptr() != nullptr) - return !!m_table; // ... and then we check if it is valid - - if (m_list) - return m_list->is_attached(); - - 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() -{ - util::CheckedUniqueLock lock(m_mutex); - return do_size(); -} - -size_t Results::do_size() -{ - validate_read(); - switch (m_mode) { - case Mode::Empty: return 0; - case Mode::Table: return m_table->size(); - case Mode::LinkList: return m_link_list->size(); - case Mode::List: - evaluate_sort_and_distinct_on_list(); - return m_list_indices ? m_list_indices->size() : m_list->size(); - case Mode::Query: - m_query.sync_view_if_needed(); - if (!m_descriptor_ordering.will_apply_distinct()) - return m_query.count(m_descriptor_ordering); - REALM_FALLTHROUGH; - case Mode::TableView: - do_evaluate_query_if_needed(); - return m_table_view.size(); - } - REALM_COMPILER_HINT_UNREACHABLE(); -} - -const ObjectSchema& Results::get_object_schema() const -{ - validate_read(); - - auto object_schema = m_object_schema.load(); - if (!object_schema) { - REALM_ASSERT(m_realm); - auto it = m_realm->schema().find(get_object_type()); - REALM_ASSERT(it != m_realm->schema().end()); - m_object_schema = object_schema = &*it; - } - - return *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()); -} - -template<typename T> -auto& Results::list_as() const -{ - return static_cast<Lst<T>&>(*m_list); -} - -void Results::evaluate_sort_and_distinct_on_list() -{ - if (m_descriptor_ordering.is_empty()) - return; - - // We can't use the sorted list from the notifier if we're in a write - // transaction as we only check the transaction version to see if the data matches - if (m_notifier && m_notifier->get_list_indices(m_list_indices) && !m_realm->is_in_transaction()) - return; - - bool needs_update = m_list->has_changed(); - if (!m_list_indices) { - m_list_indices = std::vector<size_t>{}; - needs_update = true; - } - if (!needs_update) - return; - if (m_list->is_empty()) { - m_list_indices->clear(); - return; - } - - util::Optional<bool> sort_order; - bool do_distinct = false; - auto sz = m_descriptor_ordering.size(); - for (size_t i = 0; i < sz; i++) { - auto descr = m_descriptor_ordering[i]; - if (descr->get_type() == DescriptorType::Sort) - sort_order = static_cast<const SortDescriptor*>(descr)->is_ascending(0); - if (descr->get_type() == DescriptorType::Distinct) - do_distinct = true; - } - - if (do_distinct) - m_list->distinct(*m_list_indices, sort_order); - else if (sort_order) - m_list->sort(*m_list_indices, *sort_order); -} - -template<typename T> -util::Optional<T> Results::try_get(size_t ndx) -{ - validate_read(); - if (m_mode == Mode::List) { - evaluate_sort_and_distinct_on_list(); - if (m_list_indices) { - if (ndx < m_list_indices->size()) - return list_as<T>().get((*m_list_indices)[ndx]); - } - else { - if (ndx < m_list->size()) - return list_as<T>().get(ndx); - } - } - return util::none; -} - -Results::IteratorWrapper::IteratorWrapper(IteratorWrapper const& rgt) -{ - *this = rgt; -} - -Results::IteratorWrapper& Results::IteratorWrapper::operator=(IteratorWrapper const& rgt) -{ - if (rgt.m_it) - m_it = std::make_unique<Table::ConstIterator>(*rgt.m_it); - return *this; -} - -Obj Results::IteratorWrapper::get(Table const& table, size_t ndx) -{ - // Using a Table iterator is much faster for repeated access into a table - // than indexing into it as the iterator caches the cluster the last accessed - // object is stored in. - if (!m_it && table.size() > 5) { - m_it = std::make_unique<Table::ConstIterator>(table.begin()); - } - if (!m_it) { - return const_cast<Table&>(table).get_object(ndx); - } - try { - return (*m_it)[ndx]; - } - catch (...) { - // Iterator might be outdated - m_it = std::make_unique<Table::ConstIterator>(table.begin()); - return (*m_it)[ndx]; - } -} - -template<> -util::Optional<Obj> Results::try_get(size_t row_ndx) -{ - validate_read(); - switch (m_mode) { - case Mode::Empty: - case Mode::List: - break; - case Mode::Table: - if (row_ndx < m_table->size()) - return m_table_iterator.get(*m_table, row_ndx); - break; - case Mode::LinkList: - if (update_linklist()) { - if (row_ndx < m_link_list->size()) - return m_link_list->get_object(row_ndx); - break; - } - REALM_FALLTHROUGH; - case Mode::Query: - case Mode::TableView: - do_evaluate_query_if_needed(); - if (row_ndx >= m_table_view.size()) - break; - if (m_update_policy == UpdatePolicy::Never && !m_table_view.is_obj_valid(row_ndx)) - return Obj{}; - return m_table_view.get(row_ndx); - } - return util::none; -} - -template<typename T> -T Results::get(size_t row_ndx) -{ - util::CheckedUniqueLock lock(m_mutex); - if (auto row = try_get<T>(row_ndx)) { - return *row; - } - throw OutOfBoundsIndexException{row_ndx, do_size()}; -} - -template<typename T> -util::Optional<T> Results::first() -{ - util::CheckedUniqueLock lock(m_mutex); - return try_get<T>(0); -} - -template<typename T> -util::Optional<T> Results::last() -{ - util::CheckedUniqueLock lock(m_mutex); - validate_read(); - if (m_mode == Mode::Query) - do_evaluate_query_if_needed(); // avoid running the query twice (for size() and for get()) - return try_get<T>(do_size() - 1); -} - -bool Results::update_linklist() -{ - REALM_ASSERT(m_update_policy == UpdatePolicy::Auto); - - if (!m_descriptor_ordering.is_empty()) { - m_query = do_get_query(); - m_mode = Mode::Query; - do_evaluate_query_if_needed(); - return false; - } - return true; -} - -void Results::evaluate_query_if_needed(bool wants_notifications) -{ - util::CheckedUniqueLock lock(m_mutex); - validate_read(); - do_evaluate_query_if_needed(wants_notifications); -} - -void Results::do_evaluate_query_if_needed(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::List: - case Mode::LinkList: - return; - case Mode::Query: - if (m_notifier && m_notifier->get_tableview(m_table_view)) { - m_mode = Mode::TableView; - break; - } - m_query.sync_view_if_needed(); - if (m_update_policy == UpdatePolicy::Auto) - m_table_view = m_query.find_all(m_descriptor_ordering); - m_mode = Mode::TableView; - REALM_FALLTHROUGH; - case Mode::TableView: - if (wants_notifications && !m_notifier) - prepare_async(ForCallback{false}); - else if (m_notifier) - m_notifier->get_tableview(m_table_view); - if (m_update_policy == UpdatePolicy::Auto) - m_table_view.sync_if_needed(); - if (auto audit = m_realm->audit_context()) - audit->record_query(m_realm->read_transaction_version(), m_table_view); - break; - } -} - -template<> -size_t Results::index_of(Obj const& row) -{ - util::CheckedUniqueLock lock(m_mutex); - validate_read(); - if (!row.is_valid()) { - throw DetatchedAccessorException{}; - } - if (m_table && row.get_table() != m_table) { - throw IncorrectTableException( - ObjectStore::object_type_for_table_name(m_table->get_name()), - ObjectStore::object_type_for_table_name(row.get_table()->get_name()), - "Attempting to get the index of a Row of the wrong type" - ); - } - - switch (m_mode) { - case Mode::Empty: - case Mode::List: - return not_found; - case Mode::Table: - return m_table->get_object_ndx(row.get_key()); - case Mode::LinkList: - if (update_linklist()) - return m_link_list->Lst<ObjKey>::find_first(row.get_key()); - REALM_FALLTHROUGH; - case Mode::Query: - case Mode::TableView: - do_evaluate_query_if_needed(); - return m_table_view.find_by_source_ndx(row.get_key()); - } - REALM_COMPILER_HINT_UNREACHABLE(); -} - -template<typename T> -size_t Results::index_of(T const& value) -{ - util::CheckedUniqueLock lock(m_mutex); - validate_read(); - if (m_mode != Mode::List) - return not_found; // Non-List results can only ever contain Objects - evaluate_sort_and_distinct_on_list(); - if (m_list_indices) { - for (size_t i = 0; i < m_list_indices->size(); ++i) { - if (list_as<T>().get((*m_list_indices)[i]) == value) - return i; - } - return not_found; - } - return list_as<T>().find_first(value); -} - -size_t Results::index_of(Query&& q) -{ - if (m_descriptor_ordering.will_apply_sort()) { - Results filtered(filter(std::move(q))); - filtered.assert_unlocked(); - auto first = filtered.first(); - return first ? index_of(*first) : not_found; - } - - auto query = get_query().and_query(std::move(q)); - query.sync_view_if_needed(); - ObjKey row = query.find(); - return row ? index_of(const_cast<Table&>(*m_table).get_object(row)) : not_found; -} - -DataType Results::prepare_for_aggregate(ColKey column, const char* name) -{ - DataType type; - switch (m_mode) { - case Mode::Table: - type = m_table->get_column_type(column); - break; - case Mode::List: - type = m_list->get_table()->get_column_type(m_list->get_col_key()); - break; - case Mode::LinkList: - m_query = do_get_query(); - m_mode = Mode::Query; - REALM_FALLTHROUGH; - case Mode::Query: - case Mode::TableView: - do_evaluate_query_if_needed(); - type = m_table->get_column_type(column); - break; - default: - REALM_COMPILER_HINT_UNREACHABLE(); - } - switch (type) { - case type_Timestamp: case type_Double: case type_Float: case type_Int: break; - default: throw UnsupportedColumnTypeException{column, *m_table, name}; - } - return type; -} - -namespace { -template<typename T, typename Table> -struct AggregateHelper; - -template<typename Table> -struct AggregateHelper<int64_t, Table> { - Table& table; - Mixed min(ColKey col, ObjKey* obj) { return table.minimum_int(col, obj); } - Mixed max(ColKey col, ObjKey* obj) { return table.maximum_int(col, obj); } - Mixed sum(ColKey col) { return table.sum_int(col); } - Mixed avg(ColKey col, size_t* count) { return table.average_int(col, count); } -}; - -template<typename Table> -struct AggregateHelper<double, Table> { - Table& table; - Mixed min(ColKey col, ObjKey* obj) { return table.minimum_double(col, obj); } - Mixed max(ColKey col, ObjKey* obj) { return table.maximum_double(col, obj); } - Mixed sum(ColKey col) { return table.sum_double(col); } - Mixed avg(ColKey col, size_t* count) { return table.average_double(col, count); } -}; - -template<typename Table> -struct AggregateHelper<float, Table> { - Table& table; - Mixed min(ColKey col, ObjKey* obj) { return table.minimum_float(col, obj); } - Mixed max(ColKey col, ObjKey* obj) { return table.maximum_float(col, obj); } - Mixed sum(ColKey col) { return table.sum_float(col); } - Mixed avg(ColKey col, size_t* count) { return table.average_float(col, count); } -}; - -template<typename Table> -struct AggregateHelper<Timestamp, Table> { - Table& table; - Mixed min(ColKey col, ObjKey* obj) { return table.minimum_timestamp(col, obj); } - Mixed max(ColKey col, ObjKey* obj) { return table.maximum_timestamp(col, obj); } - Mixed sum(ColKey col) { throw Results::UnsupportedColumnTypeException{col, table, "sum"}; } - Mixed avg(ColKey col, size_t*) { throw Results::UnsupportedColumnTypeException{col, table, "avg"}; } -}; - -struct ListAggregateHelper { - LstBase& list; - Mixed min(ColKey, size_t* ndx) { return list.min(ndx); } - Mixed max(ColKey, size_t* ndx) { return list.max(ndx); } - Mixed sum(ColKey) { return list.sum(); } - Mixed avg(ColKey, size_t* count) { return list.avg(count); } -}; - -template<> struct AggregateHelper<int64_t, LstBase&> : ListAggregateHelper -{ AggregateHelper(LstBase& l) : ListAggregateHelper{l} {} }; -template<> struct AggregateHelper<double, LstBase&> : ListAggregateHelper -{ AggregateHelper(LstBase& l) : ListAggregateHelper{l} {} }; -template<> struct AggregateHelper<float, LstBase&> : ListAggregateHelper -{ AggregateHelper(LstBase& l) : ListAggregateHelper{l} {} }; - -template<> -struct AggregateHelper<Timestamp, LstBase&> : ListAggregateHelper { - AggregateHelper(LstBase& l) : ListAggregateHelper{l} {} - Mixed sum(ColKey) { throw Results::UnsupportedColumnTypeException{list.get_col_key(), *list.get_table(), "sum"}; } - Mixed avg(ColKey, size_t*) { throw Results::UnsupportedColumnTypeException{list.get_col_key(), *list.get_table(), "avg"}; } -}; - -template<typename Table, typename Func> -Mixed call_with_helper(Func&& func, Table&& table, DataType type) -{ - switch (type) { - case type_Timestamp: return func(AggregateHelper<Timestamp, Table>{table}); - case type_Double: return func(AggregateHelper<double, Table>{table}); - case type_Float: return func(AggregateHelper<Float, Table>{table}); - case type_Int: return func(AggregateHelper<Int, Table>{table}); - default: REALM_COMPILER_HINT_UNREACHABLE(); - } -} - -struct ReturnIndexHelper { - ObjKey key; - size_t index = npos; - operator ObjKey*() { return &key; } - operator size_t*() { return &index; } - operator bool() { return key || index != npos; } -}; -} // anonymous namespace - -template<typename AggregateFunction> -util::Optional<Mixed> Results::aggregate(ColKey column, const char* name, - AggregateFunction&& func) -{ - util::CheckedUniqueLock lock(m_mutex); - validate_read(); - if (!m_table && !m_list) - return none; - - auto type = prepare_for_aggregate(column, name); - switch (m_mode) { - case Mode::Table: - return call_with_helper(func, *m_table, type); - case Mode::List: - return call_with_helper(func, *m_list, type); - default: - return call_with_helper(func, m_table_view, type); - } -} - -util::Optional<Mixed> Results::max(ColKey column) -{ - ReturnIndexHelper return_ndx; - auto results = aggregate(column, "max", [&](auto&& helper) { - return helper.max(column, return_ndx); - }); - return return_ndx ? results : none; -} - -util::Optional<Mixed> Results::min(ColKey column) -{ - ReturnIndexHelper return_ndx; - auto results = aggregate(column, "min", [&](auto&& helper) { - return helper.min(column, return_ndx); - }); - return return_ndx ? results : none; -} - -util::Optional<Mixed> Results::sum(ColKey column) -{ - return aggregate(column, "sum", [&](auto&& helper) { return helper.sum(column); }); -} - -util::Optional<double> Results::average(ColKey column) -{ - size_t value_count = 0; - auto results = aggregate(column, "avg", [&](auto&& helper) { - return helper.avg(column, &value_count); - }); - return value_count == 0 ? none : util::make_optional(results->get_double()); -} - -void Results::clear() -{ - util::CheckedUniqueLock lock(m_mutex); - switch (m_mode) { - case Mode::Empty: - return; - case Mode::Table: - validate_write(); - if (m_realm->is_partial()) - m_table->where().find_all().clear(); - else - const_cast<Table&>(*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(); - do_evaluate_query_if_needed(); - - switch (m_update_policy) { - case UpdatePolicy::Auto: - m_table_view.clear(); - break; - case UpdatePolicy::AsyncOnly: - case UpdatePolicy::Never: { - // Copy the TableView because a frozen Results shouldn't let its size() change. - TableView copy(m_table_view); - copy.clear(); - break; - } - } - break; - case Mode::List: - validate_write(); - m_list->clear(); - break; - case Mode::LinkList: - validate_write(); - m_link_list->remove_all_target_rows(); - break; - } -} - -PropertyType Results::get_type() const -{ - util::CheckedUniqueLock lock(m_mutex); - return do_get_type(); -} - -PropertyType Results::do_get_type() const -{ - validate_read(); - switch (m_mode) { - case Mode::Empty: - case Mode::LinkList: - return PropertyType::Object; - case Mode::Query: - case Mode::TableView: - case Mode::Table: - return PropertyType::Object; - case Mode::List: - return ObjectSchema::from_core_type(*m_list->get_table(), m_list->get_col_key()); - } - REALM_COMPILER_HINT_UNREACHABLE(); -} - -Query Results::get_query() const -{ - util::CheckedUniqueLock lock(m_mutex); - return do_get_query(); -} - -Query Results::do_get_query() const -{ - validate_read(); - switch (m_mode) { - case Mode::Empty: - case Mode::Query: - case Mode::List: - return m_query; - case Mode::TableView: { - if (const_cast<Query&>(m_query).get_table()) - return m_query; - - // 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<ConstTableView>(new TableView(m_table_view))); - } - case Mode::LinkList: - return m_table->where(*m_link_list); - case Mode::Table: - return m_table->where(); - } - REALM_COMPILER_HINT_UNREACHABLE(); -} - -TableView Results::get_tableview() -{ - util::CheckedUniqueLock lock(m_mutex); - validate_read(); - switch (m_mode) { - case Mode::Empty: - case Mode::List: - return {}; - case Mode::LinkList: - if (update_linklist()) - return m_table->where(*m_link_list).find_all(); - REALM_FALLTHROUGH; - case Mode::Query: - case Mode::TableView: - do_evaluate_query_if_needed(); - return m_table_view; - case Mode::Table: - return m_table->where().find_all(); - } - REALM_COMPILER_HINT_UNREACHABLE(); -} - -static std::vector<ColKey> parse_keypath(StringData keypath, Schema const& schema, - const ObjectSchema *object_schema) -{ - auto check = [&](bool condition, const char* fmt, auto... args) { - if (!condition) { - throw std::invalid_argument(util::format("Cannot sort on key path '%1': %2.", - keypath, util::format(fmt, args...))); - } - }; - auto is_sortable_type = [](PropertyType type) { - return !is_array(type) && type != PropertyType::LinkingObjects && type != PropertyType::Data; - }; - - const char* begin = keypath.data(); - const char* end = keypath.data() + keypath.size(); - check(begin != end, "missing property name"); - - std::vector<ColKey> indices; - while (begin != end) { - auto sep = std::find(begin, end, '.'); - check(sep != begin && sep + 1 != end, "missing property name"); - StringData key(begin, sep - begin); - begin = sep + (sep != end); - - auto prop = object_schema->property_for_name(key); - check(prop, "property '%1.%2' does not exist", object_schema->name, key); - check(is_sortable_type(prop->type), "property '%1.%2' is of unsupported type '%3'", - object_schema->name, key, string_for_property_type(prop->type)); - if (prop->type == PropertyType::Object) - check(begin != end, "property '%1.%2' of type 'object' cannot be the final property in the key path", - object_schema->name, key); - else - check(begin == end, "property '%1.%2' of type '%3' may only be the final property in the key path", - object_schema->name, key, prop->type_string()); - - indices.push_back(ColKey(prop->column_key)); - if (prop->type == PropertyType::Object) - object_schema = &*schema.find(prop->object_type); - } - return indices; -} - -Results Results::sort(std::vector<std::pair<std::string, bool>> const& keypaths) const -{ - if (keypaths.empty()) - return *this; - auto type = get_type(); - if (type != PropertyType::Object) { - if (keypaths.size() != 1) - throw std::invalid_argument(util::format("Cannot sort array of '%1' on more than one key path", - string_for_property_type(type & ~PropertyType::Flags))); - if (keypaths[0].first != "self") - throw std::invalid_argument( - util::format("Cannot sort on key path '%1': arrays of '%2' can only be sorted on 'self'", - keypaths[0].first, string_for_property_type(type & ~PropertyType::Flags))); - return sort({{{}}, {keypaths[0].second}}); - } - - std::vector<std::vector<ColKey>> column_keys; - std::vector<bool> ascending; - column_keys.reserve(keypaths.size()); - ascending.reserve(keypaths.size()); - - for (auto& keypath : keypaths) { - column_keys.push_back(parse_keypath(keypath.first, m_realm->schema(), - &get_object_schema())); - ascending.push_back(keypath.second); - } - return sort({std::move(column_keys), std::move(ascending)}); -} - -Results Results::sort(SortDescriptor&& sort) const -{ - util::CheckedUniqueLock lock(m_mutex); - DescriptorOrdering new_order = m_descriptor_ordering; - new_order.append_sort(std::move(sort)); - if (m_mode == Mode::LinkList) - return Results(m_realm, m_link_list, util::none, std::move(sort)); - else if (m_mode == Mode::List) - return Results(m_realm, m_list, std::move(new_order)); - return Results(m_realm, do_get_query(), std::move(new_order)); -} - -Results Results::filter(Query&& q) const -{ - if (m_descriptor_ordering.will_apply_limit()) - throw UnimplementedOperationException("Filtering a Results with a limit is not yet implemented"); - return Results(m_realm, get_query().and_query(std::move(q)), m_descriptor_ordering); -} - -Results Results::limit(size_t max_count) const -{ - auto new_order = m_descriptor_ordering; - new_order.append_limit(max_count); - return Results(m_realm, get_query(), std::move(new_order)); -} - -Results Results::apply_ordering(DescriptorOrdering&& ordering) -{ - DescriptorOrdering new_order = m_descriptor_ordering; - for (size_t i = 0; i < ordering.size(); ++i) { - switch (ordering.get_type(i)) { - case DescriptorType::Sort: { - auto sort = dynamic_cast<const SortDescriptor*>(ordering[i]); - new_order.append_sort(std::move(*sort)); - break; - } - case DescriptorType::Distinct: { - auto distinct = dynamic_cast<const DistinctDescriptor*>(ordering[i]); - new_order.append_distinct(std::move(*distinct)); - break; - } - case DescriptorType::Limit: { - auto limit = dynamic_cast<const LimitDescriptor*>(ordering[i]); - new_order.append_limit(std::move(*limit)); - break; - } - case DescriptorType::Include: { - auto include = dynamic_cast<const IncludeDescriptor*>(ordering[i]); - new_order.append_include(std::move(*include)); - break; - } - } - } - return Results(m_realm, get_query(), std::move(new_order)); -} - -Results Results::distinct(DistinctDescriptor&& uniqueness) const -{ - DescriptorOrdering new_order = m_descriptor_ordering; - new_order.append_distinct(std::move(uniqueness)); - util::CheckedUniqueLock lock(m_mutex); - if (m_mode == Mode::List) - return Results(m_realm, m_list, std::move(new_order)); - return Results(m_realm, do_get_query(), std::move(new_order)); -} - -Results Results::distinct(std::vector<std::string> const& keypaths) const -{ - if (keypaths.empty()) - return *this; - auto type = get_type(); - if (type != PropertyType::Object) { - if (keypaths.size() != 1) - throw std::invalid_argument(util::format("Cannot sort array of '%1' on more than one key path", - string_for_property_type(type & ~PropertyType::Flags))); - if (keypaths[0] != "self") - throw std::invalid_argument( - util::format("Cannot sort on key path '%1': arrays of '%2' can only be sorted on 'self'", keypaths[0], - string_for_property_type(type & ~PropertyType::Flags))); - return distinct(DistinctDescriptor({{ColKey()}})); - } - - std::vector<std::vector<ColKey>> column_keys; - column_keys.reserve(keypaths.size()); - for (auto& keypath : keypaths) - column_keys.push_back(parse_keypath(keypath, m_realm->schema(), &get_object_schema())); - return distinct({std::move(column_keys)}); -} - -Results Results::snapshot() const& -{ - validate_read(); - auto clone = *this; - clone.assert_unlocked(); - return static_cast<Results&&>(clone).snapshot(); -} - -Results Results::snapshot() && -{ - util::CheckedUniqueLock lock(m_mutex); - validate_read(); - switch (m_mode) { - case Mode::Empty: - return Results(); - - case Mode::Table: - case Mode::LinkList: - m_query = do_get_query(); - m_mode = Mode::Query; - - REALM_FALLTHROUGH; - case Mode::Query: - case Mode::TableView: - case Mode::List: // FIXME Correct? - do_evaluate_query_if_needed(false); - m_notifier.reset(); - m_update_policy = UpdatePolicy::Never; - return std::move(*this); - } - REALM_COMPILER_HINT_UNREACHABLE(); -} - -// This function cannot be called on frozen results and so does not require locking -void Results::prepare_async(ForCallback force) NO_THREAD_SAFETY_ANALYSIS -{ - REALM_ASSERT(m_realm); - if (m_notifier) - return; - if (!m_realm->verify_notifications_available(force)) - return; - if (m_update_policy == UpdatePolicy::Never) { - if (force) - throw std::logic_error("Cannot create asynchronous query for snapshotted Results."); - return; - } - - REALM_ASSERT(!force || !m_realm->is_frozen()); - if (!force) { - // Don't do implicit background updates if we can't actually deliver them - if (!m_realm->can_deliver_notifications()) - return; - // Don't do implicit background updates if there isn't actually anything - // that needs to be run. - if (!m_query.get_table() && m_descriptor_ordering.is_empty()) - return; - } - - if (m_list) - m_notifier = std::make_shared<_impl::ListResultsNotifier>(*this); - else - m_notifier = std::make_shared<_impl::ResultsNotifier>(*this); - _impl::RealmCoordinator::register_notifier(m_notifier); -} - -NotificationToken Results::add_notification_callback(CollectionChangeCallback cb) & -{ - prepare_async(ForCallback{true}); - return {m_notifier, m_notifier->add_callback(std::move(cb))}; -} - -// This function cannot be called on frozen results and so does not require locking -bool Results::is_in_table_order() const NO_THREAD_SAFETY_ANALYSIS -{ - REALM_ASSERT(!m_realm || !m_realm->is_frozen()); - switch (m_mode) { - case Mode::Empty: - case Mode::Table: - case Mode::List: - return true; - case Mode::LinkList: - return false; - case Mode::Query: - return m_query.produces_results_in_table_order() - && !m_descriptor_ordering.will_apply_sort(); - case Mode::TableView: - return m_table_view.is_in_table_order(); - } - REALM_COMPILER_HINT_UNREACHABLE(); -} - -ColKey Results::key(StringData name) const -{ - return m_table->get_column_key(name); -} -#define REALM_RESULTS_TYPE(T) \ - template T Results::get<T>(size_t); \ - template util::Optional<T> Results::first<T>(); \ - template util::Optional<T> Results::last<T>(); \ - template size_t Results::index_of<T>(T const&); - -template Obj Results::get<Obj>(size_t); -template util::Optional<Obj> Results::first<Obj>(); -template util::Optional<Obj> Results::last<Obj>(); - -REALM_RESULTS_TYPE(bool) -REALM_RESULTS_TYPE(int64_t) -REALM_RESULTS_TYPE(float) -REALM_RESULTS_TYPE(double) -REALM_RESULTS_TYPE(StringData) -REALM_RESULTS_TYPE(BinaryData) -REALM_RESULTS_TYPE(Timestamp) -REALM_RESULTS_TYPE(util::Optional<bool>) -REALM_RESULTS_TYPE(util::Optional<int64_t>) -REALM_RESULTS_TYPE(util::Optional<float>) -REALM_RESULTS_TYPE(util::Optional<double>) - -#undef REALM_RESULTS_TYPE - -Results Results::freeze(std::shared_ptr<Realm> const& frozen_realm) -{ - util::CheckedUniqueLock lock(m_mutex); - if (m_mode == Mode::Empty) - return *this; - switch (m_mode) { - case Mode::Table: - return Results(frozen_realm, frozen_realm->import_copy_of(m_table)); - case Mode::List: - return Results(frozen_realm, frozen_realm->import_copy_of(*m_list), m_descriptor_ordering); - case Mode::LinkList: { - std::shared_ptr<LnkLst> frozen_ll(frozen_realm->import_copy_of(std::make_unique<LnkLst>(*m_link_list)).release()); - - // If query/sort was provided for the original Results, mode would have changed to Query, so no need - // include them here. - return Results(frozen_realm, std::move(frozen_ll)); - } - case Mode::Query: - return Results(frozen_realm, *frozen_realm->import_copy_of(m_query, PayloadPolicy::Copy), m_descriptor_ordering); - case Mode::TableView: { - Results results(frozen_realm, *frozen_realm->import_copy_of(m_table_view, PayloadPolicy::Copy), m_descriptor_ordering); - results.assert_unlocked(); - results.evaluate_query_if_needed(false); - return results; - } - default: - REALM_COMPILER_HINT_UNREACHABLE(); - } -} - -bool Results::is_frozen() -{ - return !m_realm || m_realm->is_frozen(); -} - -Results::OutOfBoundsIndexException::OutOfBoundsIndexException(size_t r, size_t c) -: std::out_of_range(util::format("Requested index %1 greater than max %2", r, c - 1)) -, requested(r), valid_count(c) {} - -static std::string unsupported_operation_msg(ColKey column, Table const& table, const char* operation) -{ - auto type = ObjectSchema::from_core_type(table, column); - const char* column_type = string_for_property_type(type & ~PropertyType::Array); - if (!is_array(type)) - return util::format("Cannot %1 property '%2': operation not supported for '%3' properties", - operation, table.get_column_name(column), column_type); - return util::format("Cannot %1 '%2' array: operation not supported", - operation, column_type); -} - -Results::UnsupportedColumnTypeException::UnsupportedColumnTypeException(ColKey column, Table const& table, - const char* operation) -: std::logic_error(unsupported_operation_msg(column, table, operation)) -, column_key(column) -, column_name(table.get_column_name(column)) -, property_type(ObjectSchema::from_core_type(table, ColKey(column)) & ~PropertyType::Array) -{ -} - -Results::UnsupportedColumnTypeException::UnsupportedColumnTypeException(ColKey column, TableView const& tv, - const char* operation) -: UnsupportedColumnTypeException(column, tv.ObjList::get_parent(), operation) -{ -} - -Results::InvalidPropertyException::InvalidPropertyException(StringData object_type, StringData property_name) -: std::logic_error(util::format("Property '%1.%2' does not exist", object_type, property_name)) -, object_type(object_type), property_name(property_name) -{ -} - -Results::UnimplementedOperationException::UnimplementedOperationException(const char* msg) -: std::logic_error(msg) -{ -} - -} // namespace realm diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/results.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/results.hpp deleted file mode 100644 index e84e8c85d..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/results.hpp +++ /dev/null @@ -1,393 +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 "impl/collection_notifier.hpp" -#include "list.hpp" -#include "object.hpp" -#include "object_schema.hpp" -#include "property.hpp" -#include "shared_realm.hpp" -#include "util/checked_mutex.hpp" -#include "util/copyable_atomic.hpp" - -#include <realm/table_view.hpp> -#include <realm/util/optional.hpp> - -namespace realm { -class Mixed; -class ObjectSchema; - -namespace _impl { - class ResultsNotifierBase; -} - -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(std::shared_ptr<Realm> r, ConstTableRef table); - Results(std::shared_ptr<Realm> r, std::shared_ptr<LstBase> list); - Results(std::shared_ptr<Realm> r, std::shared_ptr<LstBase> list, DescriptorOrdering o); - Results(std::shared_ptr<Realm> r, Query q, DescriptorOrdering o = {}); - Results(std::shared_ptr<Realm> r, TableView tv, DescriptorOrdering o = {}); - Results(std::shared_ptr<Realm> r, std::shared_ptr<LnkLst> list, util::Optional<Query> q = {}, SortDescriptor s = {}); - ~Results(); - - // Results is copyable and moveable - Results(Results&&); - Results& operator=(Results&&); - Results(const Results&); - Results& operator=(const Results&); - - // Get the Realm - std::shared_ptr<Realm> get_realm() const { return m_realm; } - - // Object schema describing the vendored object type - const ObjectSchema &get_object_schema() const REQUIRES(!m_mutex); - - // 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 REQUIRES(!m_mutex); - - // Get the Lst this Results is derived from, if any - std::shared_ptr<LstBase> const& get_list() const { return m_list; } - - // Get the list of sort and distinct operations applied for this Results. - DescriptorOrdering const& get_descriptor_ordering() const noexcept { return m_descriptor_ordering; } - - // Get a tableview containing the same rows as this Results - TableView get_tableview() REQUIRES(!m_mutex); - - // Get the object type which will be returned by get() - StringData get_object_type() const noexcept; - - PropertyType get_type() const REQUIRES(!m_mutex); - - // Get the size of this results - // Can be either O(1) or O(N) depending on the state of things - size_t size() REQUIRES(!m_mutex); - - // Get the row accessor for the given index - // Throws OutOfBoundsIndexException if index >= size() - template<typename T = Obj> - T get(size_t index) REQUIRES(!m_mutex); - - // Get the boxed row accessor for the given index - // Throws OutOfBoundsIndexException if index >= size() - template<typename Context> - auto get(Context&, size_t index) REQUIRES(!m_mutex); - - // Get a row accessor for the first/last row, or none if the results are empty - // More efficient than calling size()+get() - template<typename T = Obj> - util::Optional<T> first() REQUIRES(!m_mutex); - template<typename T = Obj> - util::Optional<T> last() REQUIRES(!m_mutex); - - // Get the index of the first row matching the query in this table - size_t index_of(Query&& q) REQUIRES(!m_mutex); - - // Get the first index of the given value in this results, or not_found - // Throws DetachedAccessorException if row is not attached - // Throws IncorrectTableException if row belongs to a different table - template<typename T> - size_t index_of(T const& value) REQUIRES(!m_mutex); - - // 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() REQUIRES(!m_mutex); - - // Create a new Results by further filtering or sorting this Results - Results filter(Query&& q) const REQUIRES(!m_mutex); - Results sort(SortDescriptor&& sort) const REQUIRES(!m_mutex); - Results sort(std::vector<std::pair<std::string, bool>> const& keypaths) const REQUIRES(!m_mutex); - - // Create a new Results by removing duplicates - Results distinct(DistinctDescriptor&& uniqueness) const REQUIRES(!m_mutex); - Results distinct(std::vector<std::string> const& keypaths) const REQUIRES(!m_mutex); - - // Create a new Results with only the first `max_count` entries - Results limit(size_t max_count) const REQUIRES(!m_mutex); - - // Create a new Results by adding sort and distinct combinations - Results apply_ordering(DescriptorOrdering&& ordering) REQUIRES(!m_mutex); - - // Return a snapshot of this Results that never updates to reflect changes in the underlying data. - Results snapshot() const& REQUIRES(!m_mutex); - Results snapshot() && REQUIRES(!m_mutex); - - // Returns a frozen copy of this result - Results freeze(std::shared_ptr<Realm> const& realm) REQUIRES(!m_mutex); - - // Returns whether or not this Results is frozen. - bool is_frozen() REQUIRES(!m_mutex); - - // 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<Mixed> max(ColKey column={}) REQUIRES(!m_mutex); - util::Optional<Mixed> min(ColKey column={}) REQUIRES(!m_mutex); - util::Optional<double> average(ColKey column={}) REQUIRES(!m_mutex); - util::Optional<Mixed> sum(ColKey column={}) REQUIRES(!m_mutex); - - util::Optional<Mixed> max(StringData column_name) REQUIRES(!m_mutex) { return max(key(column_name)); } - util::Optional<Mixed> min(StringData column_name) REQUIRES(!m_mutex) { return min(key(column_name)); } - util::Optional<double> average(StringData column_name) REQUIRES(!m_mutex) { return average(key(column_name)); } - util::Optional<Mixed> sum(StringData column_name) REQUIRES(!m_mutex) { return sum(key(column_name)); } - - enum class Mode { - Empty, // Backed by nothing (for missing tables) - Table, // Backed directly by a Table - List, // Backed by a list-of-primitives that is not a link list. - Query, // Backed by a query that has not yet been turned into a TableView - LinkList, // Backed directly by a LinkList - 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 noexcept REQUIRES(!m_mutex); - - // 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 { - ColKey column_key; - StringData column_name; - PropertyType property_type; - - UnsupportedColumnTypeException(ColKey column, Table const& table, const char* operation); - UnsupportedColumnTypeException(ColKey column, TableView const& tv, const char* operation); - }; - - // The property request does not exist in the schema - struct InvalidPropertyException : public std::logic_error { - InvalidPropertyException(StringData object_type, StringData property_name); - const std::string object_type; - const std::string property_name; - }; - - // The requested operation is valid, but has not yet been implemented - struct UnimplementedOperationException : public std::logic_error { - UnimplementedOperationException(const char *message); - }; - - // 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 add_notification_callback(CollectionChangeCallback cb) &; - - // Returns whether the rows are guaranteed to be in table order. - bool is_in_table_order() const; - - template<typename Context> auto first(Context&) REQUIRES(!m_mutex); - template<typename Context> auto last(Context&) REQUIRES(!m_mutex); - - template<typename Context, typename T> - size_t index_of(Context&, T value) REQUIRES(!m_mutex); - - // Batch updates all items in this collection with the provided value - // Must be called inside a transaction - // Throws an exception if the value does not match the type for given prop_name - template<typename ValueType, typename ContextType> - void set_property_value(ContextType& ctx, StringData prop_name, ValueType value) REQUIRES(!m_mutex); - - // Execute the query immediately if needed. When the relevant query is slow, size() - // may cost similar time compared with creating the tableview. Use this function to - // avoid running the query twice for size() and other accessors. - void evaluate_query_if_needed(bool wants_notifications = true) REQUIRES(!m_mutex); - - enum class UpdatePolicy { - Auto, // Update automatically to reflect changes in the underlying data. - AsyncOnly, // Only update via ResultsNotifier and never run queries synchronously - Never, // Never update. - }; - // For tests only. Use snapshot() for normal uses. - void set_update_policy(UpdatePolicy policy) { m_update_policy = policy; } - -private: - std::shared_ptr<Realm> m_realm; - mutable util::CopyableAtomic<const ObjectSchema*> m_object_schema = nullptr; - Query m_query GUARDED_BY(m_mutex); - TableView m_table_view GUARDED_BY(m_mutex); - ConstTableRef m_table; - DescriptorOrdering m_descriptor_ordering; - std::shared_ptr<LnkLst> m_link_list; - std::shared_ptr<LstBase> m_list; - util::Optional<std::vector<size_t>> m_list_indices GUARDED_BY(m_mutex); - - _impl::CollectionNotifier::Handle<_impl::ResultsNotifierBase> m_notifier; - - Mode m_mode GUARDED_BY(m_mutex) = Mode::Empty; - UpdatePolicy m_update_policy = UpdatePolicy::Auto; - - bool update_linklist() REQUIRES(m_mutex); - - void validate_read() const; - void validate_write() const; - - size_t do_size() REQUIRES(m_mutex); - Query do_get_query() const REQUIRES(m_mutex); - PropertyType do_get_type() const REQUIRES(m_mutex); - - using ForCallback = util::TaggedBool<class ForCallback>; - void prepare_async(ForCallback); - - ColKey key(StringData) const; - - template<typename T> - util::Optional<T> try_get(size_t) REQUIRES(m_mutex); - - template<typename AggregateFunction> - util::Optional<Mixed> aggregate(ColKey column, const char* name, - AggregateFunction&& func) REQUIRES(!m_mutex); - DataType prepare_for_aggregate(ColKey column, const char* name) REQUIRES(m_mutex); - - template<typename Fn> - auto dispatch(Fn&&) const REQUIRES(!m_mutex); - - template<typename T> - auto& list_as() const; - - void evaluate_sort_and_distinct_on_list() REQUIRES(m_mutex); - void do_evaluate_query_if_needed(bool wants_notifications = true) REQUIRES(m_mutex); - - class IteratorWrapper { - public: - IteratorWrapper() = default; - IteratorWrapper(IteratorWrapper const&); - IteratorWrapper& operator=(IteratorWrapper const&); - IteratorWrapper(IteratorWrapper&&) = default; - IteratorWrapper& operator=(IteratorWrapper&&) = default; - - Obj get(Table const& table, size_t ndx); - private: - std::unique_ptr<Table::ConstIterator> m_it; - } m_table_iterator; - - util::CheckedOptionalMutex m_mutex; - - // A work around for what appears to be a false positive in clang's thread - // analysis when constructing a different object of the same type within a - // member function. Putting the ACQUIRE on the constructor seems like it - // should work, but doesn't. - void assert_unlocked() ACQUIRE(!m_mutex) {} -}; - -template<typename Fn> -auto Results::dispatch(Fn&& fn) const -{ - return switch_on_type(get_type(), std::forward<Fn>(fn)); -} - -template<typename Context> -auto Results::get(Context& ctx, size_t row_ndx) -{ - return dispatch([&](auto t) { return ctx.box(this->get<std::decay_t<decltype(*t)>>(row_ndx)); }); -} - -template<typename Context> -auto Results::first(Context& ctx) -{ - // GCC 4.9 complains about `ctx` not being defined within the lambda without this goofy capture - return dispatch([this, ctx = &ctx](auto t) { - auto value = this->first<std::decay_t<decltype(*t)>>(); - return value ? static_cast<decltype(ctx->no_value())>(ctx->box(std::move(*value))) : ctx->no_value(); - }); -} - -template<typename Context> -auto Results::last(Context& ctx) -{ - return dispatch([&](auto t) { - auto value = this->last<std::decay_t<decltype(*t)>>(); - return value ? static_cast<decltype(ctx.no_value())>(ctx.box(std::move(*value))) : ctx.no_value(); - }); -} - -template<typename Context, typename T> -size_t Results::index_of(Context& ctx, T value) -{ - return dispatch([&](auto t) { - return this->index_of(ctx.template unbox<std::decay_t<decltype(*t)>>(value, CreatePolicy::Skip)); - }); -} - -template <typename ValueType, typename ContextType> -void Results::set_property_value(ContextType& ctx, StringData prop_name, ValueType value) NO_THREAD_SAFETY_ANALYSIS -{ - // Check invariants for calling this method - validate_write(); - const ObjectSchema& object_schema = get_object_schema(); - const Property* prop = object_schema.property_for_name(prop_name); - if (!prop) { - throw InvalidPropertyException(object_schema.name, prop_name); - } - if (prop->is_primary && !m_realm->is_in_migration()) { - throw ModifyPrimaryKeyException(object_schema.name, prop->name); - } - - // Update all objects in this ResultSets. Use snapshot to avoid correctness problems if the - // object is removed from the TableView after the property update as well as avoiding to - // re-evaluating the query too many times. - auto snapshot = this->snapshot(); - size_t size = snapshot.size(); - for (size_t i = 0; i < size; ++i) { - Object obj(m_realm, *m_object_schema, snapshot.get(i)); - obj.set_property_value_impl(ctx, *prop, value, CreatePolicy::ForceCreate, false); - } -} - -} // namespace realm - -#endif // REALM_RESULTS_HPP diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/schema.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/schema.cpp deleted file mode 100644 index d91375081..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/schema.cpp +++ /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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "schema.hpp" - -#include "object_schema.hpp" -#include "object_store.hpp" -#include "object_schema.hpp" -#include "property.hpp" - -#include <algorithm> - -using namespace realm; - -namespace realm { -bool operator==(Schema const& a, Schema const& b) noexcept -{ - return static_cast<Schema::base const&>(a) == static_cast<Schema::base const&>(b); -} -} - -Schema::Schema() noexcept = default; -Schema::~Schema() = default; -Schema::Schema(Schema const&) = default; -Schema::Schema(Schema &&) noexcept = default; -Schema& Schema::operator=(Schema const&) = default; -Schema& Schema::operator=(Schema&&) noexcept = default; - -Schema::Schema(std::initializer_list<ObjectSchema> types) : Schema(base(types)) { } - -Schema::Schema(base types) noexcept -: base(std::move(types)) -{ - std::sort(begin(), end(), [](ObjectSchema const& lft, ObjectSchema const& rgt) { - return lft.name < rgt.name; - }); -} - -Schema::iterator Schema::find(StringData name) noexcept -{ - auto it = std::lower_bound(begin(), end(), name, [](ObjectSchema const& lft, StringData rgt) { - return lft.name < rgt; - }); - if (it != end() && it->name != name) { - it = end(); - } - return it; -} - -Schema::const_iterator Schema::find(StringData name) const noexcept -{ - return const_cast<Schema *>(this)->find(name); -} - -Schema::iterator Schema::find(ObjectSchema const& object) noexcept -{ - return find(object.name); -} - -Schema::const_iterator Schema::find(ObjectSchema const& object) const noexcept -{ - return const_cast<Schema *>(this)->find(object); -} - -void Schema::validate() const -{ - std::vector<ObjectSchemaValidationException> exceptions; - - // As the types are added sorted by name, we can detect duplicates by just looking at the following element. - auto find_next_duplicate = [&](const_iterator start) { - return std::adjacent_find(start, cend(), [](ObjectSchema const& lft, ObjectSchema const& rgt) { - return lft.name == rgt.name; - }); - }; - - for (auto it = find_next_duplicate(cbegin()); it != cend(); it = find_next_duplicate(++it)) { - exceptions.push_back(ObjectSchemaValidationException("Type '%1' appears more than once in the schema.", - it->name)); - } - - for (auto const& object : *this) { - object.validate(*this, exceptions); - } - - if (exceptions.size()) { - throw SchemaValidationException(exceptions); - } -} - -static void compare(ObjectSchema const& existing_schema, - ObjectSchema const& target_schema, - std::vector<SchemaChange>& changes) -{ - for (auto& current_prop : existing_schema.persisted_properties) { - auto target_prop = target_schema.property_for_name(current_prop.name); - - if (!target_prop) { - changes.emplace_back(schema_change::RemoveProperty{&existing_schema, ¤t_prop}); - continue; - } - if (target_schema.property_is_computed(*target_prop)) { - changes.emplace_back(schema_change::RemoveProperty{&existing_schema, ¤t_prop}); - continue; - } - if (current_prop.type != target_prop->type || - current_prop.object_type != target_prop->object_type || - is_array(current_prop.type) != is_array(target_prop->type)) { - - changes.emplace_back(schema_change::ChangePropertyType{&existing_schema, ¤t_prop, target_prop}); - continue; - } - if (is_nullable(current_prop.type) != is_nullable(target_prop->type)) { - if (is_nullable(current_prop.type)) - changes.emplace_back(schema_change::MakePropertyRequired{&existing_schema, ¤t_prop}); - else - changes.emplace_back(schema_change::MakePropertyNullable{&existing_schema, ¤t_prop}); - } - if (target_prop->requires_index()) { - if (!current_prop.is_indexed) - changes.emplace_back(schema_change::AddIndex{&existing_schema, ¤t_prop}); - } - else if (current_prop.requires_index()) { - changes.emplace_back(schema_change::RemoveIndex{&existing_schema, ¤t_prop}); - } - } - - for (auto& target_prop : target_schema.persisted_properties) { - if (!existing_schema.property_for_name(target_prop.name)) { - changes.emplace_back(schema_change::AddProperty{&existing_schema, &target_prop}); - } - } - - if (existing_schema.primary_key != target_schema.primary_key) { - changes.emplace_back(schema_change::ChangePrimaryKey{&existing_schema, target_schema.primary_key_property()}); - } -} - -template<typename T, typename U, typename Func> -void Schema::zip_matching(T&& a, U&& b, Func&& func) noexcept -{ - size_t i = 0, j = 0; - while (i < a.size() && j < b.size()) { - auto& object_schema = a[i]; - auto& matching_schema = b[j]; - int cmp = object_schema.name.compare(matching_schema.name); - if (cmp == 0) { - func(&object_schema, &matching_schema); - ++i; - ++j; - } - else if (cmp < 0) { - func(&object_schema, nullptr); - ++i; - } - else { - func(nullptr, &matching_schema); - ++j; - } - } - for (; i < a.size(); ++i) - func(&a[i], nullptr); - for (; j < b.size(); ++j) - func(nullptr, &b[j]); - -} - -std::vector<SchemaChange> Schema::compare(Schema const& target_schema, bool include_table_removals) const -{ - std::vector<SchemaChange> changes; - - // Add missing tables - zip_matching(target_schema, *this, [&](const ObjectSchema* target, const ObjectSchema* existing) { - if (target && !existing) { - changes.emplace_back(schema_change::AddTable{target}); - } - else if (include_table_removals && existing && !target) { - changes.emplace_back(schema_change::RemoveTable{existing}); - } - }); - - // Modify columns - zip_matching(target_schema, *this, [&](const ObjectSchema* target, const ObjectSchema* existing) { - if (target && existing) - ::compare(*existing, *target, changes); - else if (target) { - // Target is a new table -- add all properties - changes.emplace_back(schema_change::AddInitialProperties{target}); - } - // nothing for tables in existing but not target - }); - return changes; -} - -void Schema::copy_keys_from(realm::Schema const& other) noexcept -{ - zip_matching(*this, other, [&](ObjectSchema* existing, const ObjectSchema* other) { - if (!existing || !other) - return; - - existing->table_key = other->table_key; - for (auto& current_prop : other->persisted_properties) { - auto target_prop = existing->property_for_name(current_prop.name); - if (target_prop) { - target_prop->column_key = current_prop.column_key; - } - } - }); -} - -namespace realm { -bool operator==(SchemaChange const& lft, SchemaChange const& rgt) noexcept -{ - if (lft.m_kind != rgt.m_kind) - return false; - - using namespace schema_change; - struct Visitor { - SchemaChange const& value; - - #define REALM_SC_COMPARE(type, ...) \ - bool operator()(type rgt) const \ - { \ - auto cmp = [](auto&& v) { return std::tie(__VA_ARGS__); }; \ - return cmp(value.type) == cmp(rgt); \ - } - - REALM_SC_COMPARE(AddIndex, v.object, v.property) - REALM_SC_COMPARE(AddProperty, v.object, v.property) - REALM_SC_COMPARE(AddInitialProperties, v.object) - REALM_SC_COMPARE(AddTable, v.object) - REALM_SC_COMPARE(RemoveTable, v.object) - REALM_SC_COMPARE(ChangePrimaryKey, v.object, v.property) - REALM_SC_COMPARE(ChangePropertyType, v.object, v.old_property, v.new_property) - REALM_SC_COMPARE(MakePropertyNullable, v.object, v.property) - REALM_SC_COMPARE(MakePropertyRequired, v.object, v.property) - REALM_SC_COMPARE(RemoveIndex, v.object, v.property) - REALM_SC_COMPARE(RemoveProperty, v.object, v.property) - - #undef REALM_SC_COMPARE - } visitor{lft}; - return rgt.visit(visitor); -} -} // namespace realm diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/schema.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/schema.hpp deleted file mode 100644 index 178a3fa83..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/schema.hpp +++ /dev/null @@ -1,183 +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 <string> -#include <vector> - -#include <realm/util/features.h> - -namespace realm { -class ObjectSchema; -class SchemaChange; -class StringData; -struct Property; - -class Schema : private std::vector<ObjectSchema> { -private: - using base = std::vector<ObjectSchema>; -public: - Schema() noexcept; - ~Schema(); - // Create a schema from a vector of ObjectSchema - Schema(base types) noexcept; - Schema(std::initializer_list<ObjectSchema> types); - - Schema(Schema const&); - Schema(Schema&&) noexcept; - Schema& operator=(Schema const&); - Schema& operator=(Schema&&) noexcept; - - // find an ObjectSchema by name - iterator find(StringData name) noexcept; - const_iterator find(StringData name) const noexcept; - - // 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; - - // Get the changes which must be applied to this schema to produce the passed-in schema - std::vector<SchemaChange> compare(Schema const&, bool include_removals=false) const; - - void copy_keys_from(Schema const&) noexcept; - - friend bool operator==(Schema const&, Schema const&) noexcept; - friend bool operator!=(Schema const& a, Schema const& b) noexcept { return !(a == b); } - - using base::iterator; - using base::const_iterator; - using base::begin; - using base::end; - using base::empty; - using base::size; - -private: - template<typename T, typename U, typename Func> - static void zip_matching(T&& a, U&& b, Func&& func) noexcept; -}; - -namespace schema_change { -struct AddTable { - const ObjectSchema* object; -}; - -struct RemoveTable { - const ObjectSchema* object; -}; - -struct AddInitialProperties { - const ObjectSchema* object; -}; - -struct AddProperty { - const ObjectSchema* object; - const Property* property; -}; - -struct RemoveProperty { - const ObjectSchema* object; - const Property* property; -}; - -struct ChangePropertyType { - const ObjectSchema* object; - const Property* old_property; - const Property* new_property; -}; - -struct MakePropertyNullable { - const ObjectSchema* object; - const Property* property; -}; - -struct MakePropertyRequired { - const ObjectSchema* object; - const Property* property; -}; - -struct AddIndex { - const ObjectSchema* object; - const Property* property; -}; - -struct RemoveIndex { - const ObjectSchema* object; - const Property* property; -}; - -struct ChangePrimaryKey { - const ObjectSchema* object; - const Property* property; -}; -} - -#define REALM_FOR_EACH_SCHEMA_CHANGE_TYPE(macro) \ - macro(AddTable) \ - macro(RemoveTable) \ - macro(AddInitialProperties) \ - macro(AddProperty) \ - macro(RemoveProperty) \ - macro(ChangePropertyType) \ - macro(MakePropertyNullable) \ - macro(MakePropertyRequired) \ - macro(AddIndex) \ - macro(RemoveIndex) \ - macro(ChangePrimaryKey) \ - -class SchemaChange { -public: -#define REALM_SCHEMA_CHANGE_CONSTRUCTOR(name) \ - SchemaChange(schema_change::name value) : m_kind(Kind::name) { name = value; } - REALM_FOR_EACH_SCHEMA_CHANGE_TYPE(REALM_SCHEMA_CHANGE_CONSTRUCTOR) -#undef REALM_SCHEMA_CHANGE_CONSTRUCTOR - - template<typename Visitor> - auto visit(Visitor&& visitor) const { - switch (m_kind) { -#define REALM_SWITCH_CASE(name) case Kind::name: return visitor(name); - REALM_FOR_EACH_SCHEMA_CHANGE_TYPE(REALM_SWITCH_CASE) -#undef REALM_SWITCH_CASE - } - REALM_COMPILER_HINT_UNREACHABLE(); - } - - friend bool operator==(SchemaChange const& lft, SchemaChange const& rgt) noexcept; -private: - enum class Kind { -#define REALM_SCHEMA_CHANGE_TYPE(name) name, - REALM_FOR_EACH_SCHEMA_CHANGE_TYPE(REALM_SCHEMA_CHANGE_TYPE) -#undef REALM_SCHEMA_CHANGE_TYPE - - } m_kind; - union { -#define REALM_DEFINE_FIELD(name) schema_change::name name; - REALM_FOR_EACH_SCHEMA_CHANGE_TYPE(REALM_DEFINE_FIELD) -#undef REALM_DEFINE_FIELD - }; -}; - -#undef REALM_FOR_EACH_SCHEMA_CHANGE_TYPE -} - -#endif /* defined(REALM_SCHEMA_HPP) */ diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/shared_realm.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/shared_realm.cpp deleted file mode 100644 index 41ef88471..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/shared_realm.cpp +++ /dev/null @@ -1,1029 +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 "impl/collection_notifier.hpp" -#include "impl/realm_coordinator.hpp" -#include "impl/transact_log_handler.hpp" - -#include "audit.hpp" -#include "binding_context.hpp" -#include "list.hpp" -#include "object.hpp" -#include "object_schema.hpp" -#include "object_store.hpp" -#include "results.hpp" -#include "schema.hpp" -#include "thread_safe_reference.hpp" - -#include "util/scheduler.hpp" - -#include <realm/db.hpp> -#include <realm/util/scope_exit.hpp> -#include <realm/util/fifo_helper.hpp> - -#if REALM_ENABLE_SYNC -#include "sync/impl/sync_file.hpp" -#include "sync/sync_config.hpp" -#include "sync/sync_manager.hpp" - -#include <realm/sync/history.hpp> -#include <realm/sync/permissions.hpp> -#include <realm/sync/version.hpp> -#else -namespace realm { -namespace sync { - struct PermissionsCache {}; - struct TableInfoCache {}; -} -} -#endif - -using namespace realm; -using namespace realm::_impl; - -Realm::Realm(Config config, util::Optional<VersionID> version, std::shared_ptr<_impl::RealmCoordinator> coordinator, MakeSharedTag) -: m_config(std::move(config)) -, m_frozen_version(std::move(version)) -, m_scheduler(m_config.scheduler) -{ - if (!coordinator->get_cached_schema(m_schema, m_schema_version, m_schema_transaction_version)) { - m_group = coordinator->begin_read(); - read_schema_from_group_if_needed(); - coordinator->cache_schema(m_schema, m_schema_version, m_schema_transaction_version); - m_group = nullptr; - } - - m_coordinator = std::move(coordinator); -} - -Realm::~Realm() -{ - if (m_coordinator) { - m_coordinator->unregister_realm(this); - } -} - -bool Realm::is_partial() const noexcept -{ -#if REALM_ENABLE_SYNC - return m_config.sync_config && m_config.sync_config->is_partial; -#else - return false; -#endif -} - -Group& Realm::read_group() -{ - verify_open(); - - if (!m_group) - begin_read(m_frozen_version.value_or(VersionID{})); - return *m_group; -} - -Transaction& Realm::transaction() -{ - REALM_ASSERT(!m_config.immutable()); - return static_cast<Transaction&>(read_group()); -} - -Transaction& Realm::transaction() const -{ - REALM_ASSERT(!m_config.immutable()); - // FIXME: read_group() is not even remotly const - return static_cast<Transaction&>(const_cast<Realm*>(this)->read_group()); -} - -std::shared_ptr<Transaction> Realm::transaction_ref() -{ - return std::static_pointer_cast<Transaction>(m_group); -} - -std::shared_ptr<Transaction> Realm::duplicate() const -{ - return std::static_pointer_cast<Transaction>(m_coordinator->begin_read(read_transaction_version(), is_frozen())); -} - -std::shared_ptr<DB>& Realm::Internal::get_db(Realm& realm) { - return realm.m_coordinator->m_db; -} - -void Realm::Internal::begin_read(Realm& realm, VersionID version_id) -{ - realm.begin_read(version_id); -} - -void Realm::begin_read(VersionID version_id) -{ - REALM_ASSERT(!m_group); - m_group = m_coordinator->begin_read(version_id, bool(m_frozen_version)); - add_schema_change_handler(); - read_schema_from_group_if_needed(); -} - -SharedRealm Realm::get_shared_realm(Config config) -{ - auto coordinator = RealmCoordinator::get_coordinator(config.path); - return coordinator->get_realm(std::move(config), util::none); -} - -SharedRealm Realm::get_frozen_realm(Config config, VersionID version) -{ - auto coordinator = RealmCoordinator::get_coordinator(config.path); - SharedRealm realm = coordinator->get_realm(std::move(config), util::Optional<VersionID>(version)); - realm->set_auto_refresh(false); - return realm; -} - -SharedRealm Realm::get_shared_realm(ThreadSafeReference ref, std::shared_ptr<util::Scheduler> scheduler) -{ - if (!scheduler) - scheduler = util::Scheduler::make_default(); - SharedRealm realm = ref.resolve<std::shared_ptr<Realm>>(nullptr); - REALM_ASSERT(realm); - auto& config = realm->config(); - auto coordinator = RealmCoordinator::get_coordinator(config.path); - realm->m_scheduler = scheduler; - coordinator->bind_to_context(*realm); - return realm; -} - -#if REALM_ENABLE_SYNC -std::shared_ptr<AsyncOpenTask> Realm::get_synchronized_realm(Config config) -{ - auto coordinator = RealmCoordinator::get_coordinator(config.path); - return coordinator->get_synchronized_realm(std::move(config)); -} -#endif - -void Realm::set_schema(Schema const& reference, Schema schema) -{ - m_dynamic_schema = false; - schema.copy_keys_from(reference); - m_schema = std::move(schema); - notify_schema_changed(); -} - -void Realm::read_schema_from_group_if_needed() -{ - if (m_config.immutable()) { - REALM_ASSERT(m_group); - if (m_schema.empty()) { - m_schema_version = ObjectStore::get_schema_version(*m_group); - m_schema = ObjectStore::schema_from_group(*m_group); - } - return; - } - - Group& group = read_group(); - auto current_version = transaction().get_version_of_current_transaction().version; - if (m_schema_transaction_version == current_version) - return; - - m_schema_transaction_version = current_version; - m_schema_version = ObjectStore::get_schema_version(group); - auto schema = ObjectStore::schema_from_group(group); - if (m_coordinator) - m_coordinator->cache_schema(schema, m_schema_version, - m_schema_transaction_version); - - if (m_dynamic_schema) { - if (m_schema == schema) { - // The structure of the schema hasn't changed. Bring the table column indices up to date. - m_schema.copy_keys_from(schema); - } - else { - // The structure of the schema has changed, so replace our copy of the schema. - m_schema = std::move(schema); - } - } - else { - ObjectStore::verify_valid_external_changes(m_schema.compare(schema)); - m_schema.copy_keys_from(schema); - } - notify_schema_changed(); -} - -bool Realm::reset_file(Schema& schema, std::vector<SchemaChange>& required_changes) -{ - // FIXME: this does not work if multiple processes try to open the file at - // the same time, or even multiple threads if there is not any external - // synchronization. The latter is probably fixable, but making it - // multi-process-safe requires some sort of multi-process exclusive lock - m_group = nullptr; - m_coordinator->close(); - util::File::remove(m_config.path); - - m_schema = ObjectStore::schema_from_group(read_group()); - m_schema_version = ObjectStore::get_schema_version(read_group()); - required_changes = m_schema.compare(schema); - m_coordinator->clear_schema_cache_and_set_schema_version(m_schema_version); - return false; -} - -bool Realm::schema_change_needs_write_transaction(Schema& schema, - std::vector<SchemaChange>& changes, - uint64_t version) -{ - if (version == m_schema_version && changes.empty()) - return false; - - switch (m_config.schema_mode) { - case SchemaMode::Automatic: - if (version < m_schema_version && m_schema_version != ObjectStore::NotVersioned) - throw InvalidSchemaVersionException(m_schema_version, version); - return true; - - case SchemaMode::Immutable: - if (version != m_schema_version) - throw InvalidSchemaVersionException(m_schema_version, version); - REALM_FALLTHROUGH; - case SchemaMode::ReadOnlyAlternative: - ObjectStore::verify_compatible_for_immutable_and_readonly(changes); - return false; - - case SchemaMode::ResetFile: - if (m_schema_version == ObjectStore::NotVersioned) - return true; - if (m_schema_version == version && !ObjectStore::needs_migration(changes)) - return true; - reset_file(schema, changes); - return true; - - case SchemaMode::Additive: { - bool will_apply_index_changes = version > m_schema_version; - if (ObjectStore::verify_valid_additive_changes(changes, will_apply_index_changes)) - return true; - return version != m_schema_version; - } - - case SchemaMode::Manual: - if (version < m_schema_version && m_schema_version != ObjectStore::NotVersioned) - throw InvalidSchemaVersionException(m_schema_version, version); - if (version == m_schema_version) { - ObjectStore::verify_no_changes_required(changes); - REALM_UNREACHABLE(); // changes is non-empty so above line always throws - } - return true; - } - REALM_COMPILER_HINT_UNREACHABLE(); -} - -Schema Realm::get_full_schema() -{ - if (!m_config.immutable()) - do_refresh(); - - // If the user hasn't specified a schema previously then m_schema is always - // the full schema - if (m_dynamic_schema) - return m_schema; - - // Otherwise we may have a subset of the file's schema, so we need to get - // the complete thing to calculate what changes to make - if (m_config.immutable()) - return ObjectStore::schema_from_group(read_group()); - - Schema actual_schema; - uint64_t actual_version; - uint64_t version = -1; - bool got_cached = m_coordinator->get_cached_schema(actual_schema, actual_version, version); - if (!got_cached || version != transaction().get_version_of_current_transaction().version) - return ObjectStore::schema_from_group(read_group()); - return actual_schema; -} - -void Realm::set_schema_subset(Schema schema) -{ - REALM_ASSERT(m_dynamic_schema); - REALM_ASSERT(m_schema_version != ObjectStore::NotVersioned); - - std::vector<SchemaChange> changes = m_schema.compare(schema); - switch (m_config.schema_mode) { - case SchemaMode::Automatic: - case SchemaMode::ResetFile: - ObjectStore::verify_no_migration_required(changes); - break; - - case SchemaMode::Immutable: - case SchemaMode::ReadOnlyAlternative: - ObjectStore::verify_compatible_for_immutable_and_readonly(changes); - break; - - case SchemaMode::Additive: - ObjectStore::verify_valid_additive_changes(changes); - break; - - case SchemaMode::Manual: - ObjectStore::verify_no_changes_required(changes); - break; - } - - set_schema(m_schema, std::move(schema)); -} - -void Realm::update_schema(Schema schema, uint64_t version, MigrationFunction migration_function, - DataInitializationFunction initialization_function, bool in_transaction) -{ - schema.validate(); - - bool was_in_read_transaction = is_in_read_transaction(); - Schema actual_schema = get_full_schema(); - std::vector<SchemaChange> required_changes = actual_schema.compare(schema); - - if (!schema_change_needs_write_transaction(schema, required_changes, version)) { - if (!was_in_read_transaction) - m_group = nullptr; - set_schema(actual_schema, std::move(schema)); - return; - } - // Either the schema version has changed or we need to do non-migration changes - - // Cancel the write transaction if we exit this function before committing it - auto cleanup = util::make_scope_exit([&]() noexcept { - // When in_transaction is true, caller is responsible to cancel the transaction. - if (!in_transaction && is_in_transaction()) - cancel_transaction(); - if (!was_in_read_transaction) - m_group = nullptr; - }); - - if (!in_transaction) { - transaction().promote_to_write(); - - // Beginning the write transaction may have advanced the version and left - // us with nothing to do if someone else initialized the schema on disk - if (m_new_schema) { - actual_schema = *m_new_schema; - required_changes = actual_schema.compare(schema); - if (!schema_change_needs_write_transaction(schema, required_changes, version)) { - cancel_transaction(); - cache_new_schema(); - set_schema(actual_schema, std::move(schema)); - return; - } - } - cache_new_schema(); - } - - uint64_t old_schema_version = m_schema_version; - bool additive = m_config.schema_mode == SchemaMode::Additive; - if (migration_function && !additive) { - auto wrapper = [&] { - auto config = m_config; - config.schema_mode = SchemaMode::ReadOnlyAlternative; - config.schema = util::none; - // Don't go through the normal codepath for opening a Realm because - // we're using a mismatched config - auto old_realm = std::make_shared<Realm>(std::move(config), none, m_coordinator, MakeSharedTag{}); - migration_function(old_realm, shared_from_this(), m_schema); - }; - - // migration function needs to see the target schema on the "new" Realm - std::swap(m_schema, schema); - std::swap(m_schema_version, version); - m_in_migration = true; - auto restore = util::make_scope_exit([&]() noexcept { - std::swap(m_schema, schema); - std::swap(m_schema_version, version); - m_in_migration = false; - }); - - ObjectStore::apply_schema_changes(transaction(), version, m_schema, m_schema_version, - m_config.schema_mode, required_changes, util::none, wrapper); - } - else { - util::Optional<std::string> sync_user_id; -#if REALM_ENABLE_SYNC - if (m_config.sync_config && m_config.sync_config->is_partial) - sync_user_id = m_config.sync_config->user->identity(); -#endif - ObjectStore::apply_schema_changes(transaction(), m_schema_version, schema, version, - m_config.schema_mode, required_changes, std::move(sync_user_id)); - REALM_ASSERT_DEBUG(additive || (required_changes = ObjectStore::schema_from_group(read_group()).compare(schema)).empty()); - } - - if (initialization_function && old_schema_version == ObjectStore::NotVersioned) { - // Initialization function needs to see the latest schema - uint64_t temp_version = ObjectStore::get_schema_version(read_group()); - std::swap(m_schema, schema); - std::swap(m_schema_version, temp_version); - auto restore = util::make_scope_exit([&]() noexcept { - std::swap(m_schema, schema); - std::swap(m_schema_version, temp_version); - }); - initialization_function(shared_from_this()); - } - - m_schema = std::move(schema); - m_new_schema = ObjectStore::schema_from_group(read_group()); - m_schema_version = ObjectStore::get_schema_version(read_group()); - m_dynamic_schema = false; - m_coordinator->clear_schema_cache_and_set_schema_version(version); - - if (!in_transaction) { - m_coordinator->commit_write(*this); - invalidate_permission_cache(); - cache_new_schema(); - } - - notify_schema_changed(); -} - -void Realm::add_schema_change_handler() -{ - if (m_config.immutable()) - return; - m_group->set_schema_change_notification_handler([&] { - m_new_schema = ObjectStore::schema_from_group(read_group()); - m_schema_version = ObjectStore::get_schema_version(read_group()); - if (m_dynamic_schema) { - m_schema = *m_new_schema; - } - else - m_schema.copy_keys_from(*m_new_schema); - - notify_schema_changed(); - }); -} - -void Realm::cache_new_schema() -{ - if (!is_closed()) { - auto new_version = transaction().get_version_of_current_transaction().version; - if (m_new_schema) - m_coordinator->cache_schema(std::move(*m_new_schema), - m_schema_version, new_version); - else - m_coordinator->advance_schema_cache(m_schema_transaction_version, new_version); - m_schema_transaction_version = new_version; - m_new_schema = util::none; - } -} - -void Realm::translate_schema_error() -{ - // Read the new (incompatible) schema without changing our read transaction - auto new_schema = ObjectStore::schema_from_group(*m_coordinator->begin_read()); - - // Should always throw - ObjectStore::verify_valid_external_changes(m_schema.compare(new_schema, true)); - - // Something strange happened so just rethrow the old exception - throw; -} - -void Realm::notify_schema_changed() -{ - if (m_binding_context) { - m_binding_context->schema_did_change(m_schema); - } -} - -static void check_can_create_any_transaction(const Realm* realm) -{ - if (realm->config().immutable()) { - throw InvalidTransactionException("Can't perform transactions on read-only Realms."); - } -} - -static void check_can_create_write_transaction(const Realm* realm) -{ - if (realm->config().immutable() || realm->config().read_only_alternative()) { - throw InvalidTransactionException("Can't perform transactions on read-only Realms."); - } - if (realm->is_frozen()) { - throw InvalidTransactionException("Can't perform transactions on a frozen Realm"); - } - if (!realm->is_closed() && realm->get_number_of_versions() > realm->config().max_number_of_active_versions) { - throw InvalidTransactionException(util::format("Number of active versions (%1) in the Realm exceeded the limit of %2", - realm->get_number_of_versions(), - realm->config().max_number_of_active_versions)); - } -} - -void Realm::verify_thread() const -{ - if (m_scheduler && !m_scheduler->is_on_thread()) - throw IncorrectThreadException(); -} - -void Realm::verify_in_write() const -{ - if (!is_in_transaction()) { - throw InvalidTransactionException("Cannot modify managed objects outside of a write transaction."); - } -} - -void Realm::verify_open() const -{ - if (is_closed()) { - throw ClosedRealmException(); - } -} - -bool Realm::verify_notifications_available(bool throw_on_error) const -{ - if (is_frozen()) { - if (throw_on_error) - throw InvalidTransactionException("Notifications are not available on frozen lists since they do not change."); - return false; - } - if (config().immutable()) { - if (throw_on_error) - throw InvalidTransactionException("Cannot create asynchronous query for immutable Realms"); - return false; - } - if (is_in_transaction()) { - if (throw_on_error) - throw InvalidTransactionException("Cannot create asynchronous query while in a write transaction"); - return false; - } - - return true; -} - -VersionID Realm::read_transaction_version() const -{ - verify_thread(); - verify_open(); - check_can_create_any_transaction(this); - return static_cast<Transaction&>(*m_group).get_version_of_current_transaction(); -} - -uint_fast64_t Realm::get_number_of_versions() const -{ - verify_open(); - check_can_create_any_transaction(this); - return m_coordinator->get_number_of_versions(); -} - -bool Realm::is_in_transaction() const noexcept -{ - return !m_config.immutable() - && !is_closed() - && m_group && transaction().get_transact_stage() == DB::transact_Writing; -} - -util::Optional<VersionID> Realm::current_transaction_version() const -{ - util::Optional<VersionID> ret; - if (m_group) { - ret = static_cast<Transaction&>(*m_group).get_version_of_current_transaction(); - } - else if (m_frozen_version) { - ret = m_frozen_version; - } - return ret; -} - -void Realm::enable_wait_for_change() -{ - m_coordinator->enable_wait_for_change(); -} - -bool Realm::wait_for_change() -{ - if (m_frozen_version) { - return false; - } - return m_group ? m_coordinator->wait_for_change(transaction_ref()) : false; -} - -void Realm::wait_for_change_release() -{ - m_coordinator->wait_for_change_release(); -} - -void Realm::begin_transaction() -{ - verify_thread(); - check_can_create_write_transaction(this); - - if (is_in_transaction()) { - throw InvalidTransactionException("The Realm is already in a write transaction"); - } - - // Any of the callbacks to user code below could drop the last remaining - // strong reference to `this` - auto retain_self = shared_from_this(); - - // If we're already in the middle of sending notifications, just begin the - // write transaction without sending more notifications. If this actually - // advances the read version this could leave the user in an inconsistent - // state, but that's unavoidable. - if (m_is_sending_notifications) { - _impl::NotifierPackage notifiers; - transaction::begin(transaction_ref(), m_binding_context.get(), notifiers); - return; - } - - // make sure we have a read transaction - read_group(); - - m_is_sending_notifications = true; - auto cleanup = util::make_scope_exit([this]() noexcept { - m_is_sending_notifications = false; - }); - - try { - m_coordinator->promote_to_write(*this); - } - catch (_impl::UnsupportedSchemaChange const&) { - translate_schema_error(); - } - cache_new_schema(); -} - -void Realm::commit_transaction() -{ - check_can_create_write_transaction(this); - verify_thread(); - - if (!is_in_transaction()) { - throw InvalidTransactionException("Can't commit a non-existing write transaction"); - } - - if (auto audit = audit_context()) { - auto prev_version = transaction().get_version_of_current_transaction(); - m_coordinator->commit_write(*this); - audit->record_write(prev_version, transaction().get_version_of_current_transaction()); - // m_shared_group->unpin_version(prev_version); - } - else { - m_coordinator->commit_write(*this); - } - cache_new_schema(); - invalidate_permission_cache(); -} - -void Realm::cancel_transaction() -{ - check_can_create_write_transaction(this); - verify_thread(); - - if (!is_in_transaction()) { - throw InvalidTransactionException("Can't cancel a non-existing write transaction"); - } - - transaction::cancel(transaction(), m_binding_context.get()); - invalidate_permission_cache(); -} - -void Realm::invalidate() -{ - verify_open(); - verify_thread(); - check_can_create_any_transaction(this); - - if (m_is_sending_notifications) { - return; - } - - if (is_in_transaction()) { - cancel_transaction(); - } - - m_permissions_cache = nullptr; - m_table_info_cache = nullptr; - m_group = nullptr; -} - -bool Realm::compact() -{ - verify_thread(); - verify_open(); - - if (m_config.immutable() || m_config.read_only_alternative()) { - throw InvalidTransactionException("Can't compact a read-only Realm"); - } - if (is_in_transaction()) { - throw InvalidTransactionException("Can't compact a Realm within a write transaction"); - } - - verify_open(); - m_group = nullptr; - return m_coordinator->compact(); -} - -void Realm::write_copy(StringData path, BinaryData key) -{ - if (key.data() && key.size() != 64) { - throw InvalidEncryptionKeyException(); - } - verify_thread(); - try { - read_group().write(path, key.data()); - } - catch (...) { - _impl::translate_file_exception(path); - } -} - -OwnedBinaryData Realm::write_copy() -{ - verify_thread(); - BinaryData buffer = read_group().write_to_mem(); - - // Since OwnedBinaryData does not have a constructor directly taking - // ownership of BinaryData, we have to do this to avoid copying the buffer - return OwnedBinaryData(std::unique_ptr<char[]>((char*)buffer.data()), buffer.size()); -} - -void Realm::notify() -{ - if (is_closed() || is_in_transaction() || is_frozen()) { - return; - } - - verify_thread(); - invalidate_permission_cache(); - - // Any of the callbacks to user code below could drop the last remaining - // strong reference to `this` - auto retain_self = shared_from_this(); - - if (m_binding_context) { - m_binding_context->before_notify(); - if (is_closed() || is_in_transaction()) { - return; - } - } - - auto cleanup = util::make_scope_exit([this]() noexcept { m_is_sending_notifications = false; }); - if (!m_coordinator->can_advance(*this)) { - m_is_sending_notifications = true; - m_coordinator->process_available_async(*this); - return; - } - - if (m_binding_context) { - m_binding_context->changes_available(); - - // changes_available() may have advanced the read version, and if - // so we don't need to do anything further - if (!m_coordinator->can_advance(*this)) - return; - } - - m_is_sending_notifications = true; - if (m_auto_refresh) { - if (m_group) { - try { - m_coordinator->advance_to_ready(*this); - } - catch (_impl::UnsupportedSchemaChange const&) { - translate_schema_error(); - } - if (!is_closed()) - cache_new_schema(); - } - else { - if (m_binding_context) { - m_binding_context->did_change({}, {}); - } - if (!is_closed()) { - m_coordinator->process_available_async(*this); - } - } - } -} - -bool Realm::refresh() -{ - verify_thread(); - check_can_create_any_transaction(this); - return do_refresh(); -} - -bool Realm::do_refresh() -{ - // Frozen Realms never change. - if (is_frozen()) { - return false; - } - - // can't be any new changes if we're in a write transaction - if (is_in_transaction()) { - return false; - } - // don't advance if we're already in the process of advancing as that just - // makes things needlessly complicated - if (m_is_sending_notifications) { - return false; - } - invalidate_permission_cache(); - - // Any of the callbacks to user code below could drop the last remaining - // strong reference to `this` - auto retain_self = shared_from_this(); - - m_is_sending_notifications = true; - auto cleanup = util::make_scope_exit([this]() noexcept { m_is_sending_notifications = false; }); - - if (m_binding_context) { - m_binding_context->before_notify(); - } - if (m_group) { - try { - bool version_changed = m_coordinator->advance_to_latest(*this); - if (is_closed()) - return false; - cache_new_schema(); - return version_changed; - } - catch (_impl::UnsupportedSchemaChange const&) { - translate_schema_error(); - } - } - - // No current read transaction, so just create a new one - read_group(); - m_coordinator->process_available_async(*this); - return true; -} - -void Realm::set_auto_refresh(bool auto_refresh) -{ - if (is_frozen() && auto_refresh) { - throw std::logic_error("Auto-refresh cannot be enabled for frozen Realms."); - } - m_auto_refresh = auto_refresh; -} - - -bool Realm::can_deliver_notifications() const noexcept -{ - if (m_config.immutable() || !m_config.automatic_change_notifications) { - return false; - } - - if (!m_scheduler || !m_scheduler->can_deliver_notifications()) { - return false; - } - - return true; -} - -uint64_t Realm::get_schema_version(const Realm::Config &config) -{ - auto coordinator = RealmCoordinator::get_coordinator(config.path); - auto version = coordinator->get_schema_version(); - if (version == ObjectStore::NotVersioned) - version = ObjectStore::get_schema_version(coordinator->get_realm(config, util::none)->read_group()); - return version; -} - - -bool Realm::is_frozen() const -{ - bool result = bool(m_frozen_version); - REALM_ASSERT_DEBUG((result && m_group) ? m_group->is_frozen() : true); - return result; -} - -SharedRealm Realm::freeze() -{ - auto config = m_config; - config.scheduler = util::Scheduler::get_frozen(); - return Realm::get_frozen_realm(std::move(config), read_transaction_version()); -} - -void Realm::close() -{ - if (m_coordinator) { - m_coordinator->unregister_realm(this); - } - if (!m_config.immutable() && m_group) { - transaction().close(); - } - - m_permissions_cache = nullptr; - m_table_info_cache = nullptr; - m_group = nullptr; - m_binding_context = nullptr; - m_coordinator = nullptr; -} - -AuditInterface* Realm::audit_context() const noexcept -{ - return m_coordinator ? m_coordinator->audit_context() : nullptr; -} - -#if REALM_ENABLE_SYNC -static_assert(static_cast<int>(ComputedPrivileges::Read) == static_cast<int>(sync::Privilege::Read), ""); -static_assert(static_cast<int>(ComputedPrivileges::Update) == static_cast<int>(sync::Privilege::Update), ""); -static_assert(static_cast<int>(ComputedPrivileges::Delete) == static_cast<int>(sync::Privilege::Delete), ""); -static_assert(static_cast<int>(ComputedPrivileges::SetPermissions) == static_cast<int>(sync::Privilege::SetPermissions), ""); -static_assert(static_cast<int>(ComputedPrivileges::Query) == static_cast<int>(sync::Privilege::Query), ""); -static_assert(static_cast<int>(ComputedPrivileges::Create) == static_cast<int>(sync::Privilege::Create), ""); -static_assert(static_cast<int>(ComputedPrivileges::ModifySchema) == static_cast<int>(sync::Privilege::ModifySchema), ""); - -static constexpr const uint8_t s_allRealmPrivileges = sync::Privilege::Read - | sync::Privilege::Update - | sync::Privilege::SetPermissions - | sync::Privilege::ModifySchema; -static constexpr const uint8_t s_allClassPrivileges = sync::Privilege::Read - | sync::Privilege::Update - | sync::Privilege::Create - | sync::Privilege::Query - | sync::Privilege::SetPermissions; -static constexpr const uint8_t s_allObjectPrivileges = sync::Privilege::Read - | sync::Privilege::Update - | sync::Privilege::Delete - | sync::Privilege::SetPermissions; - -bool Realm::init_permission_cache() -{ - verify_thread(); - - if (m_permissions_cache) { - // Rather than trying to track changes to permissions tables, just skip the caching - // entirely within write transactions for now - if (is_in_transaction()) - m_permissions_cache->clear(); - return true; - } - - // Admin users bypass permissions checks outside of the logic in PermissionsCache - if (m_config.sync_config && m_config.sync_config->is_partial && !m_config.sync_config->user->is_admin()) { - m_table_info_cache = std::make_unique<sync::TableInfoCache>(transaction()); - m_permissions_cache = std::make_unique<sync::PermissionsCache>(transaction(), *m_table_info_cache, - m_config.sync_config->user->identity()); - return true; - } - return false; -} - -void Realm::invalidate_permission_cache() -{ - if (m_permissions_cache) - m_permissions_cache->clear(); -} - -ComputedPrivileges Realm::get_privileges() -{ - if (!init_permission_cache()) - return static_cast<ComputedPrivileges>(s_allRealmPrivileges); - return static_cast<ComputedPrivileges>(m_permissions_cache->get_realm_privileges() & s_allRealmPrivileges); -} - -static uint8_t inherited_mask(uint32_t privileges) -{ - uint8_t mask = ~0; - if (!(privileges & sync::Privilege::Read)) - mask = 0; - else if (!(privileges & sync::Privilege::Update)) - mask = static_cast<uint8_t>(sync::Privilege::Read | sync::Privilege::Query); - return mask; -} - -ComputedPrivileges Realm::get_privileges(StringData object_type) -{ - if (!init_permission_cache()) - return static_cast<ComputedPrivileges>(s_allClassPrivileges); - auto privileges = inherited_mask(m_permissions_cache->get_realm_privileges()) - & m_permissions_cache->get_class_privileges(object_type); - return static_cast<ComputedPrivileges>(privileges & s_allClassPrivileges); -} - -ComputedPrivileges Realm::get_privileges(ConstObj const& obj) -{ - if (!init_permission_cache()) - return static_cast<ComputedPrivileges>(s_allObjectPrivileges); - - auto table = obj.get_table(); - auto object_type = ObjectStore::object_type_for_table_name(table->get_name()); - sync::GlobalID global_id{object_type, table->get_object_id(obj.get_key())}; - auto privileges = inherited_mask(m_permissions_cache->get_realm_privileges()) - & inherited_mask(m_permissions_cache->get_class_privileges(object_type)) - & m_permissions_cache->get_object_privileges(global_id); - return static_cast<ComputedPrivileges>(privileges & s_allObjectPrivileges); -} -#else -void Realm::invalidate_permission_cache() { } -#endif - -MismatchedConfigException::MismatchedConfigException(StringData message, StringData path) -: std::logic_error(util::format(message.data(), path)) { } - -MismatchedRealmException::MismatchedRealmException(StringData message) -: std::logic_error(message.data()) { } - diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/shared_realm.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/shared_realm.hpp deleted file mode 100644 index b15f6efb1..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/shared_realm.hpp +++ /dev/null @@ -1,540 +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 "schema.hpp" - -#include <realm/util/optional.hpp> -#include <realm/binary_data.hpp> -#include <realm/db.hpp> -#include <realm/version_id.hpp> - -#if REALM_ENABLE_SYNC -#include <realm/sync/client.hpp> -#endif - -#include <memory> - -namespace realm { -class AsyncOpenTask; -class AuditInterface; -class BindingContext; -class DB; -class Group; -class Obj; -class Realm; -class Replication; -class StringData; -class Table; -class ThreadSafeReference; -class Transaction; -struct SyncConfig; -typedef std::shared_ptr<Realm> SharedRealm; -typedef std::weak_ptr<Realm> WeakRealm; - -namespace util { -class Scheduler; -} - -namespace _impl { - class AnyHandover; - class CollectionNotifier; - class PartialSyncHelper; - class RealmCoordinator; - class RealmFriend; -} -namespace sync { - struct PermissionsCache; - struct TableInfoCache; -} - -// How to handle update_schema() being called on a file which has -// already been initialized with a different schema -enum class SchemaMode : uint8_t { - // If the schema version has increased, automatically apply all - // changes, then call the migration function. - // - // If the schema version has not changed, verify that the only - // changes are to add new tables and add or remove indexes, and then - // apply them if so. Does not call the migration function. - // - // This mode does not automatically remove tables which are not - // present in the schema that must be manually done in the migration - // function, to support sharing a Realm file between processes using - // different class subsets. - // - // This mode allows using schemata with different subsets of tables - // on different threads, but the tables which are shared must be - // identical. - Automatic, - - // Open the file in immutable mode. Schema version must match the - // version in the file, and all tables present in the file must - // exactly match the specified schema, except for indexes. Tables - // are allowed to be missing from the file. - // WARNING: This is the original ReadOnly mode. - Immutable, - - // Open the Realm in read-only mode, transactions are not allowed to - // be performed on the Realm instance. The schema of the existing Realm - // file won't be changed through this Realm instance. Extra tables and - // extra properties are allowed in the existing Realm schema. The - // difference of indexes is allowed as well. Other schema differences - // than those will cause an exception. This is different from Immutable - // mode, sync Realm can be opened with ReadOnly mode. Changes - // can be made to the Realm file through another writable Realm instance. - // Thus, notifications are also allowed in this mode. - // FIXME: Rename this to ReadOnly - // WARNING: This is not the original ReadOnly mode. The original ReadOnly - // has been renamed to Immutable. - ReadOnlyAlternative, - - // If the schema version matches and the only schema changes are new - // tables and indexes being added or removed, apply the changes to - // the existing file. - // Otherwise delete the file and recreate it from scratch. - // The migration function is not used. - // - // This mode allows using schemata with different subsets of tables - // on different threads, but the tables which are shared must be - // identical. - ResetFile, - - // The only changes allowed are to add new tables, add columns to - // existing tables, and to add or remove indexes from existing - // columns. Extra tables not present in the schema are ignored. - // Indexes are only added to or removed from existing columns if the - // schema version is greater than the existing one (and unlike other - // modes, the schema version is allowed to be less than the existing - // one). - // The migration function is not used. - // - // This mode allows updating the schema with additive changes even - // if the Realm is already open on another thread. - Additive, - - // Verify that the schema version has increased, call the migraiton - // function, and then verify that the schema now matches. - // The migration function is mandatory for this mode. - // - // This mode requires that all threads and processes which open a - // file use identical schemata. - Manual -}; - -enum class ComputedPrivileges : uint8_t { - None = 0, - - Read = (1 << 0), - Update = (1 << 1), - Delete = (1 << 2), - SetPermissions = (1 << 3), - Query = (1 << 4), - Create = (1 << 5), - ModifySchema = (1 << 6), - - AllRealm = Read | Update | SetPermissions | ModifySchema, - AllClass = Read | Update | Create | Query | SetPermissions, - AllObject = Read | Update | Delete | SetPermissions, - All = (1 << 7) - 1 -}; - -class Realm : public std::enable_shared_from_this<Realm> { -public: - // A callback function to be called during a migration for Automatic and - // Manual schema modes. It is passed a SharedRealm at the version before - // the migration, the SharedRealm in the migration, and a mutable reference - // to the realm's Schema. Updating the schema with changes made within the - // migration function is only required if you wish to use the ObjectStore - // functions which take a Schema from within the migration function. - using MigrationFunction = std::function<void (SharedRealm old_realm, SharedRealm realm, Schema&)>; - - // A callback function to be called the first time when a schema is created. - // It is passed a SharedRealm which is in a write transaction with the schema - // initialized. So it is possible to create some initial objects inside the callback - // with the given SharedRealm. Those changes will be committed together with the - // schema creation in a single transaction. - using DataInitializationFunction = std::function<void (SharedRealm realm)>; - - // A callback function called when opening a SharedRealm when no cached - // version of this Realm exists. It is passed the total bytes allocated for - // the file (file size) and the total bytes used by data in the file. - // Return `true` to indicate that an attempt to compact the file should be made - // if it is possible to do so. - // Won't compact the file if another process is accessing it. - // - // WARNING / FIXME: compact() should NOT be exposed publicly on Windows - // because it's not crash safe! It may corrupt your database if something fails - using ShouldCompactOnLaunchFunction = std::function<bool (uint64_t total_bytes, uint64_t used_bytes)>; - - struct Config { - // Path and binary data are mutually exclusive - std::string path; - BinaryData realm_data; - // User-supplied encryption key. Must be either empty or 64 bytes. - std::vector<char> encryption_key; - - // Core and Object Store will in some cases need to create named pipes alongside the Realm file. - // But on some filesystems this can be a problem (e.g. external storage on Android that uses FAT32). - // In order to work around this, a separate path can be specified for these files. - std::string fifo_files_fallback_path; - - bool in_memory = false; - SchemaMode schema_mode = SchemaMode::Automatic; - - // Optional schema for the file. - // If the schema and schema version are supplied, update_schema() is - // called with the supplied schema, version and migration function when - // the Realm is actually opened and not just retrieved from the cache - util::Optional<Schema> schema; - uint64_t schema_version = -1; - MigrationFunction migration_function; - - DataInitializationFunction initialization_function; - - // A callback function called when opening a SharedRealm when no cached - // version of this Realm exists. It is passed the total bytes allocated for - // the file (file size) and the total bytes used by data in the file. - // Return `true` to indicate that an attempt to compact the file should be made - // if it is possible to do so. - // Won't compact the file if another process is accessing it. - // - // WARNING / FIXME: compact() should NOT be exposed publicly on Windows - // because it's not crash safe! It may corrupt your database if something fails - ShouldCompactOnLaunchFunction should_compact_on_launch_function; - - // WARNING: The original read_only() has been renamed to immutable(). - bool immutable() const { return schema_mode == SchemaMode::Immutable; } - // FIXME: Rename this to read_only(). - bool read_only_alternative() const { return schema_mode == SchemaMode::ReadOnlyAlternative; } - - // The following are intended for internal/testing purposes and - // should not be publicly exposed in binding APIs - - // 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; - - // The Scheduler which this Realm should be bound to. If not supplied, - // a default one for the current thread will be used. - std::shared_ptr<util::Scheduler> scheduler; - - /// A data structure storing data used to configure the Realm for sync support. - std::shared_ptr<SyncConfig> sync_config; - - // Open the Realm using the sync history mode even if a sync - // configuration is not supplied. - bool force_sync_history = false; - - // A factory function which produces an audit implementation. - std::function<std::shared_ptr<AuditInterface>()> audit_factory; - - // Maximum number of active versions in the Realm file allowed before an exception - // is thrown. - uint_fast64_t max_number_of_active_versions = std::numeric_limits<uint_fast64_t>::max(); - }; - - // Returns a thread-confined live Realm for the given configuration - static SharedRealm get_shared_realm(Config config); - - // Get a Realm for the given scheduler (or current thread if `none`) - // from the thread safe reference. - static SharedRealm get_shared_realm(ThreadSafeReference, std::shared_ptr<util::Scheduler> = nullptr); - -#if REALM_ENABLE_SYNC - // Open a synchronized Realm and make sure it is fully up to date before - // returning it. - // - // It is possible to both cancel the download and listen to download progress - // using the `AsyncOpenTask` returned. Note that the download doesn't actually - // start until you call `AsyncOpenTask::start(callback)` - static std::shared_ptr<AsyncOpenTask> get_synchronized_realm(Config config); -#endif - // Returns a frozen Realm for the given Realm. This Realm can be accessed from any thread. - static SharedRealm get_frozen_realm(Config config, VersionID version); - - // Updates a Realm to a given schema, using the Realm's pre-set schema mode. - void update_schema(Schema schema, uint64_t version=0, - MigrationFunction migration_function=nullptr, - DataInitializationFunction initialization_function=nullptr, - bool in_transaction=false); - - // Set the schema used for this Realm, but do not update the file's schema - // if it is not compatible (and instead throw an error). - // Cannot be called multiple times on a single Realm instance or an instance - // which has already had update_schema() called on it. - void set_schema_subset(Schema schema); - - // Read the schema version from the file specified by the given config, or - // ObjectStore::NotVersioned if it does not exist - static uint64_t get_schema_version(Config const& config); - - Config const& config() const { return m_config; } - Schema const& schema() const { return m_schema; } - uint64_t schema_version() const { return m_schema_version; } - - // Returns `true` if this Realm is a Partially synchronized Realm. - bool is_partial() const noexcept; - - void begin_transaction(); - void commit_transaction(); - void cancel_transaction(); - bool is_in_transaction() const noexcept; - - // Returns a frozen copy for the current version of this Realm - SharedRealm freeze(); - - // Returns `true` if the Realm is frozen, `false` otherwise. - bool is_frozen() const; - - // Returns true if the Realm is either in a read or frozen transaction - bool is_in_read_transaction() const { return m_group != nullptr; } - uint64_t last_seen_transaction_version() { return m_schema_transaction_version; } - - // Returns the number of versions in the Realm file. - uint_fast64_t get_number_of_versions() const; - - VersionID read_transaction_version() const; - Group& read_group(); - - // Get the version of the current read or frozen transaction, or `none` if the Realm - // is not in a read transaction - util::Optional<VersionID> current_transaction_version() const; - - TransactionRef duplicate() const; - - void enable_wait_for_change(); - bool wait_for_change(); - void wait_for_change_release(); - - bool is_in_migration() const noexcept { return m_in_migration; } - - bool refresh(); - void set_auto_refresh(bool auto_refresh); - bool auto_refresh() const { return m_auto_refresh; } - void notify(); - - void invalidate(); - - // WARNING / FIXME: compact() should NOT be exposed publicly on Windows - // because it's not crash safe! It may corrupt your database if something fails - bool compact(); - void write_copy(StringData path, BinaryData encryption_key); - OwnedBinaryData write_copy(); - - void verify_thread() const; - void verify_in_write() const; - void verify_open() const; - bool verify_notifications_available(bool throw_on_error = true) const; - - bool can_deliver_notifications() const noexcept; - std::shared_ptr<util::Scheduler> scheduler() const noexcept { return m_scheduler; } - - // Close this Realm. Continuing to use a Realm after closing it will throw ClosedRealmException - void close(); - bool is_closed() const { return !m_group && !m_coordinator; } - - // returns the file format version upgraded from if an upgrade took place - util::Optional<int> file_format_upgraded_from_version() const; - - Realm(const Realm&) = delete; - Realm& operator=(const Realm&) = delete; - Realm(Realm&&) = delete; - Realm& operator=(Realm&&) = delete; - ~Realm(); - - ComputedPrivileges get_privileges(); - ComputedPrivileges get_privileges(StringData object_type); - ComputedPrivileges get_privileges(ConstObj const& obj); - - AuditInterface* audit_context() const noexcept; - - template<typename... Args> - auto import_copy_of(Args&&... args) - { - return transaction().import_copy_of(std::forward<Args>(args)...); - } - - static SharedRealm make_shared_realm(Config config, util::Optional<VersionID> version, std::shared_ptr<_impl::RealmCoordinator> coordinator) - { - return std::make_shared<Realm>(std::move(config), std::move(version), std::move(coordinator), MakeSharedTag{}); - } - - // 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::PartialSyncHelper; - friend class _impl::RealmCoordinator; - friend class GlobalNotifier; - friend class TestHelper; - friend class ThreadSafeReference; - - static Transaction& get_transaction(Realm& realm) { return realm.transaction(); } - static std::shared_ptr<Transaction> get_transaction_ref(Realm& realm) { return realm.transaction_ref(); } - - // 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 std::shared_ptr<DB>& get_db(Realm& realm); - static void begin_read(Realm&, VersionID); - }; - -private: - struct MakeSharedTag {}; - - std::shared_ptr<_impl::RealmCoordinator> m_coordinator; - std::unique_ptr<sync::TableInfoCache> m_table_info_cache; - std::unique_ptr<sync::PermissionsCache> m_permissions_cache; - - Config m_config; - util::Optional<VersionID> m_frozen_version; - std::shared_ptr<util::Scheduler> m_scheduler; - bool m_auto_refresh = true; - - std::shared_ptr<Group> m_group; - - uint64_t m_schema_version; - Schema m_schema; - util::Optional<Schema> m_new_schema; - uint64_t m_schema_transaction_version = -1; - - // FIXME: this should be a Dynamic schema mode instead, but only once - // that's actually fully working - bool m_dynamic_schema = true; - - // True while sending the notifications caused by advancing the read - // transaction version, to avoid recursive notifications where possible - bool m_is_sending_notifications = false; - - // True while we're performing a schema migration via this Realm instance - // to allow for different behavior (such as allowing modifications to - // primary key values) - bool m_in_migration = false; - - void begin_read(VersionID); - bool do_refresh(); - - void set_schema(Schema const& reference, Schema schema); - bool reset_file(Schema& schema, std::vector<SchemaChange>& changes_required); - bool schema_change_needs_write_transaction(Schema& schema, std::vector<SchemaChange>& changes, uint64_t version); - Schema get_full_schema(); - - // Ensure that m_schema and m_schema_version match that of the current - // version of the file - void read_schema_from_group_if_needed(); - - void add_schema_change_handler(); - void cache_new_schema(); - void translate_schema_error(); - void notify_schema_changed(); - - bool init_permission_cache(); - void invalidate_permission_cache(); - - Transaction& transaction(); - Transaction& transaction() const; - std::shared_ptr<Transaction> transaction_ref(); - -public: - std::unique_ptr<BindingContext> m_binding_context; - - // `enable_shared_from_this` is unsafe with public constructors; use `make_shared_realm` instead - Realm(Config config, util::Optional<VersionID> version, std::shared_ptr<_impl::RealmCoordinator> coordinator, MakeSharedTag); -}; - -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 history type of the on-disk Realm is unexpected or incompatible. */ - BadHistoryError, - /** 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 MismatchedRealmException : public std::logic_error { -public: - MismatchedRealmException(StringData message); -}; - -class InvalidTransactionException : public std::logic_error { -public: - InvalidTransactionException(std::string message) : std::logic_error(message) {} -}; - -class IncorrectThreadException : public std::logic_error { -public: - IncorrectThreadException() : std::logic_error("Realm accessed from incorrect thread.") {} -}; - -class ClosedRealmException : public std::logic_error { -public: - ClosedRealmException() : std::logic_error("Cannot access realm that has been closed.") {} -}; - -class UninitializedRealmException : public std::runtime_error { -public: - UninitializedRealmException(std::string message) : std::runtime_error(message) {} -}; - -class InvalidEncryptionKeyException : public std::logic_error { -public: - InvalidEncryptionKeyException() : std::logic_error("Encryption key must be 64 bytes.") {} -}; -} // namespace realm - -#endif /* defined(REALM_REALM_HPP) */ diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/thread_safe_reference.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/thread_safe_reference.cpp deleted file mode 100644 index ecae494ae..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/thread_safe_reference.cpp +++ /dev/null @@ -1,243 +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_safe_reference.hpp" - -#include "list.hpp" -#include "object.hpp" -#include "object_schema.hpp" -#include "results.hpp" -#include "shared_realm.hpp" - -#include "impl/realm_coordinator.hpp" - -#include <realm/db.hpp> -#include <realm/keys.hpp> - -namespace realm { -class ThreadSafeReference::Payload { -public: - virtual ~Payload() = default; - Payload(Realm& realm) - : m_transaction(realm.is_in_read_transaction() ? realm.duplicate() : nullptr) - , m_coordinator(Realm::Internal::get_coordinator(realm).shared_from_this()) - , m_created_in_write_transaction(realm.is_in_transaction()) - { - } - - void refresh_target_realm(Realm&); - -protected: - const TransactionRef m_transaction; - -private: - const std::shared_ptr<_impl::RealmCoordinator> m_coordinator; - const bool m_created_in_write_transaction; -}; - -void ThreadSafeReference::Payload::refresh_target_realm(Realm& realm) -{ - if (!realm.is_in_read_transaction()) { - if (m_created_in_write_transaction) - realm.read_group(); - else - Realm::Internal::begin_read(realm, m_transaction->get_version_of_current_transaction()); - } - else { - auto version = realm.read_transaction_version(); - auto target_version = m_transaction->get_version_of_current_transaction(); - if (version < target_version || (version == target_version && m_created_in_write_transaction)) - realm.refresh(); - } -} - -template<> -class ThreadSafeReference::PayloadImpl<List> : public ThreadSafeReference::Payload { -public: - PayloadImpl(List const& list) - : Payload(*list.get_realm()) - , m_key(list.get_parent_object_key()) - , m_table_key(list.get_parent_table_key()) - , m_col_key(list.get_parent_column_key()) - { - } - - List import_into(std::shared_ptr<Realm> const& r) - { - Obj obj = r->read_group().get_table(m_table_key)->get_object(m_key); - return List(r, obj, m_col_key); - } - -private: - ObjKey m_key; - TableKey m_table_key; - ColKey m_col_key; -}; - -template<> -class ThreadSafeReference::PayloadImpl<Object> : public ThreadSafeReference::Payload { -public: - PayloadImpl(Object const& object) - : Payload(*object.get_realm()) - , m_key(object.obj().get_key()) - , m_object_schema_name(object.get_object_schema().name) - { - } - - Object import_into(std::shared_ptr<Realm> const& r) - { - return Object(r, m_object_schema_name, m_key); - } - -private: - ObjKey m_key; - std::string m_object_schema_name; -}; - -template<> -class ThreadSafeReference::PayloadImpl<Results> : public ThreadSafeReference::Payload { -public: - PayloadImpl(Results const& r) - : Payload(*r.get_realm()) - , m_ordering(r.get_descriptor_ordering()) - { - if (auto list = r.get_list()) { - m_key = list->get_key(); - m_table_key = list->get_table()->get_key(); - m_col_key = list->get_col_key(); - } - else { - Query q(r.get_query()); - if (!q.produces_results_in_table_order() && r.get_realm()->is_in_transaction()) { - // FIXME: This is overly restrictive. It's only a problem if - // the parent of the List or LinkingObjects was created in this - // write transaction, but Query doesn't expose a way to check - // if the source view is valid so we have to forbid it always. - throw std::logic_error("Cannot create a ThreadSafeReference to Results backed by a List of objects or LinkingObjects inside a write transaction"); - } - m_query = m_transaction->import_copy_of(q, PayloadPolicy::Stay); - } - } - - Results import_into(std::shared_ptr<Realm> const& r) - { - if (m_key) { - LstBasePtr list; - auto table = r->read_group().get_table(m_table_key); - try { - list = table->get_object(m_key).get_listbase_ptr(m_col_key); - } - catch (InvalidKey const&) { - // Create a detached list of the appropriate type so that we - // return an invalid Results rather than an Empty Results, to - // match what happens for other types of handover where the - // object doesn't exist. - switch_on_type(ObjectSchema::from_core_type(*table, m_col_key), [&](auto* t) -> void { - list = std::make_unique<Lst<NonObjTypeT<decltype(*t)>>>(); - }); - } - return Results(r, std::move(list), m_ordering); - } - auto q = r->import_copy_of(*m_query, PayloadPolicy::Stay); - return Results(std::move(r), std::move(*q), m_ordering); - } - -private: - DescriptorOrdering m_ordering; - std::unique_ptr<Query> m_query; - ObjKey m_key; - TableKey m_table_key; - ColKey m_col_key; -}; - -template<> -class ThreadSafeReference::PayloadImpl<std::shared_ptr<Realm>> : public ThreadSafeReference::Payload { -public: - PayloadImpl(std::shared_ptr<Realm> const& realm) - : Payload(*realm) - , m_realm(realm) - { - } - - std::shared_ptr<Realm> get_realm() - { - return std::move(m_realm); - } - -private: - std::shared_ptr<Realm> m_realm; -}; - -ThreadSafeReference::ThreadSafeReference() noexcept = default; -ThreadSafeReference::~ThreadSafeReference() = default; -ThreadSafeReference::ThreadSafeReference(ThreadSafeReference&&) noexcept = default; -ThreadSafeReference& ThreadSafeReference::operator=(ThreadSafeReference&&) noexcept = default; - -template<typename T> -ThreadSafeReference::ThreadSafeReference(T const& value) -{ - auto realm = value.get_realm(); - realm->verify_thread(); - m_payload.reset(new PayloadImpl<T>(value)); -} - -template<> -ThreadSafeReference::ThreadSafeReference(std::shared_ptr<Realm> const& value) -{ - m_payload.reset(new PayloadImpl<std::shared_ptr<Realm>>(value)); -} - -template ThreadSafeReference::ThreadSafeReference(List const&); -template ThreadSafeReference::ThreadSafeReference(Results const&); -template ThreadSafeReference::ThreadSafeReference(Object const&); - -template<typename T> -T ThreadSafeReference::resolve(std::shared_ptr<Realm> const& realm) -{ - REALM_ASSERT(realm); - realm->verify_thread(); - - REALM_ASSERT(m_payload); - auto& payload = static_cast<PayloadImpl<T>&>(*m_payload); - REALM_ASSERT(typeid(payload) == typeid(PayloadImpl<T>)); - - m_payload->refresh_target_realm(*realm); - try { - return payload.import_into(realm); - } - catch (InvalidKey const&) { - // Object was deleted in a version after when the TSR was created - return {}; - } -} - -template<> -std::shared_ptr<Realm> ThreadSafeReference::resolve<std::shared_ptr<Realm>>(std::shared_ptr<Realm> const&) -{ - REALM_ASSERT(m_payload); - auto& payload = static_cast<PayloadImpl<std::shared_ptr<Realm>>&>(*m_payload); - REALM_ASSERT(typeid(payload) == typeid(PayloadImpl<std::shared_ptr<Realm>>)); - - return payload.get_realm(); -} - -template Results ThreadSafeReference::resolve<Results>(std::shared_ptr<Realm> const&); -template List ThreadSafeReference::resolve<List>(std::shared_ptr<Realm> const&); -template Object ThreadSafeReference::resolve<Object>(std::shared_ptr<Realm> const&); - -} // namespace realm diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/thread_safe_reference.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/thread_safe_reference.hpp deleted file mode 100644 index 7dba87f5e..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/thread_safe_reference.hpp +++ /dev/null @@ -1,59 +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_OS_THREAD_SAFE_REFERENCE_HPP -#define REALM_OS_THREAD_SAFE_REFERENCE_HPP - -#include <memory> - -namespace realm { -class List; -class Object; -class Realm; -class Results; - -// Opaque type-ereased wrapper for a Realm object which can be imported into another Realm -class ThreadSafeReference { -public: - ThreadSafeReference() noexcept; - ~ThreadSafeReference(); - ThreadSafeReference(const ThreadSafeReference&) = delete; - ThreadSafeReference& operator=(const ThreadSafeReference&) = delete; - ThreadSafeReference(ThreadSafeReference&&) noexcept; - ThreadSafeReference& operator=(ThreadSafeReference&&) noexcept; - - template<typename T> - ThreadSafeReference(T const& value); - - // Import the object into the destination Realm - template<typename T> - T resolve(std::shared_ptr<Realm> const&); - - explicit operator bool() const noexcept { return !!m_payload; } - -private: - class Payload; - template<typename> class PayloadImpl; - std::unique_ptr<Payload> m_payload; -}; - -template<> ThreadSafeReference::ThreadSafeReference(std::shared_ptr<Realm> const&); -template<> std::shared_ptr<Realm> ThreadSafeReference::resolve(std::shared_ptr<Realm> const&); -} - -#endif /* REALM_OS_THREAD_SAFE_REFERENCE_HPP */ diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/util/aligned_union.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/util/aligned_union.hpp deleted file mode 100644 index b75c649cc..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/util/aligned_union.hpp +++ /dev/null @@ -1,65 +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 utilied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef REALM_OS_ALIGNED_UNION_HPP -#define REALM_OS_ALIGNED_UNION_HPP - -#include <cstddef> -#include <initializer_list> -#include <type_traits> - -namespace realm { - -// Provide our own implementation of max as GCC 4.9's is not marked as constexpr. -namespace _impl { - -template <typename T> -static constexpr const T& constexpr_max(const T& a, const T& b) -{ - return a > b ? a : b; -} - -template <typename T> -static constexpr const T& constexpr_max(const T* begin, const T *end) -{ - return begin + 1 == end ? *begin : constexpr_max(*begin, constexpr_max(begin + 1, end)); -} - -template <typename T> -static constexpr const T& constexpr_max(std::initializer_list<T> list) -{ - return constexpr_max(list.begin(), list.end()); -} - -} // namespace _impl - -namespace util { - -// Provide our own implementation of `std::aligned_union` as it is missing from GCC 4.9. -template <size_t Len, typename... Types> -struct AlignedUnion -{ - static constexpr size_t alignment_value = _impl::constexpr_max({alignof(Types)...}); - static constexpr size_t storage_size = _impl::constexpr_max({Len, sizeof(Types)...}); - using type = typename std::aligned_storage<storage_size, alignment_value>::type; -}; - -} // namespace util -} // namespace realm - -#endif // REALM_OS_ALIGNED_UNION_HPP diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/util/atomic_shared_ptr.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/util/atomic_shared_ptr.hpp deleted file mode 100644 index b4eb52f3a..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/util/atomic_shared_ptr.hpp +++ /dev/null @@ -1,148 +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 <atomic> -#include <memory> -#include <mutex> - -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<typename... Ts> struct make_void { typedef void type; }; -template<typename... Ts> using void_t = typename make_void<Ts...>::type; - -template<typename, typename = void_t<>> -struct HasAtomicPtrOps : std::false_type { }; - -template<class T> -struct HasAtomicPtrOps<T, void_t<decltype(std::atomic_load(std::declval<T*>()))>> : 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<typename T, bool = _impl::HasAtomicPtrOps<std::shared_ptr<T>>::value> -class AtomicSharedPtr; - -template<typename T> -class AtomicSharedPtr<T, true> { -public: - AtomicSharedPtr() = default; - AtomicSharedPtr(std::shared_ptr<T> 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<T> exchange(std::shared_ptr<T> ptr) - { - return std::atomic_exchange(&m_ptr, std::move(ptr)); - } - - std::shared_ptr<T> load() const noexcept - { - return std::atomic_load(&m_ptr); - } - -private: - std::shared_ptr<T> m_ptr = nullptr; -}; - -template<typename T> -class AtomicSharedPtr<T, false> { -public: - AtomicSharedPtr() = default; - AtomicSharedPtr(std::shared_ptr<T> ptr) : m_ptr(std::move(ptr)) { } - - AtomicSharedPtr(AtomicSharedPtr const& ptr) - { - std::lock_guard<std::mutex> lock(ptr.m_mutex); - m_ptr = ptr.m_ptr; - } - AtomicSharedPtr(AtomicSharedPtr&& ptr) - { - std::lock_guard<std::mutex> 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<T> exchange(std::shared_ptr<T> ptr) - { - std::lock_guard<std::mutex> lock(m_mutex); - m_ptr.swap(ptr); - return ptr; - } - - std::shared_ptr<T> load() const noexcept - { - std::lock_guard<std::mutex> lock(m_mutex); - return m_ptr; - } - -private: - mutable std::mutex m_mutex; - std::shared_ptr<T> m_ptr = nullptr; -}; - -} -} - -#endif // REALM_ATOMIC_SHARED_PTR_HPP diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/util/tagged_bool.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/util/tagged_bool.hpp deleted file mode 100644 index 9baa2f258..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/util/tagged_bool.hpp +++ /dev/null @@ -1,60 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 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_OS_UTIL_TAGGED_BOOL_HPP -#define REALM_OS_UTIL_TAGGED_BOOL_HPP - -#include <type_traits> - -namespace realm { -namespace util { -// A type factory which defines a type which is implicitly convertable to and -// from `bool`, but not to other TaggedBool types -// -// Usage: -// using IsIndexed = util::TaggedBool<class IsIndexedTag>; -// using IsPrimary = util::TaggedBool<class IsPrimaryTag>; -// void foo(IsIndexed is_indexed, IsPrimary is_primary); -// -// foo(IsIndexed{true}, IsPrimary{false}); // compiles -// foo(IsPrimary{true}, IsIndexed{false}); // doesn't compile -template <typename Tag> -struct TaggedBool { - // Allow explicit construction from anything convertible to bool - constexpr explicit TaggedBool(bool v) noexcept : m_value(v) { } - - // Allow implicit construction from *just* bool and not things convertible - // to bool (such as other types of tagged bools) - template <typename Bool, typename = typename std::enable_if<std::is_same<Bool, bool>::value>::type> - constexpr TaggedBool(Bool v) noexcept : m_value(v) {} - - constexpr TaggedBool(TaggedBool const& v) noexcept : m_value(v.m_value) {} - - constexpr operator bool() const noexcept { return m_value; } - constexpr TaggedBool operator!() const noexcept { return TaggedBool{!m_value}; } - - friend constexpr bool operator==(TaggedBool l, TaggedBool r) noexcept { return l.m_value == r.m_value; } - friend constexpr bool operator!=(TaggedBool l, TaggedBool r) noexcept { return l.m_value != r.m_value; } - -private: - bool m_value; -}; - -} -} -#endif // REALM_OS_UTIL_TAGGED_BOOL_HPP diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/util/uuid.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/util/uuid.cpp deleted file mode 100644 index 011000226..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/util/uuid.cpp +++ /dev/null @@ -1,80 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 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/uuid.hpp" - -#include <algorithm> -#include <array> -#include <functional> -#include <random> -#include <stdio.h> - -namespace { - -// Seed `engine` with as much random state as it requires, based on the approach outlined in P0205R0. -// <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0205r0.html> -template <typename T> -T create_and_seed_engine() -{ - constexpr auto bytes_needed = T::state_size * sizeof(typename T::result_type); - - constexpr auto numbers_needed = sizeof(std::random_device::result_type) < sizeof(std::seed_seq::result_type) - ? (bytes_needed / sizeof(std::random_device::result_type)) - : (bytes_needed / sizeof(std::seed_seq::result_type)); - - std::array<std::random_device::result_type, numbers_needed> state; - std::random_device rd; - std::generate(begin(state), end(state), std::ref(rd)); - std::seed_seq seeds(begin(state), end(state)); - - T engine; - engine.seed(seeds); - return engine; -} - -} // unnamed namespace - -namespace realm { -namespace util { - -std::string uuid_string() -{ - static auto engine = create_and_seed_engine<std::mt19937>(); - - std::array<uint8_t, 16> uuid_bytes; - std::uniform_int_distribution<unsigned int> distribution(0, std::numeric_limits<uint8_t>::max()); - std::generate(begin(uuid_bytes), end(uuid_bytes), [&] { return distribution(engine); }); - - // Version 4 UUID. - uuid_bytes[6] = (uuid_bytes[6] & 0x0f) | 0x40; - // IETF variant. - uuid_bytes[8] = (uuid_bytes[8] & 0x3f) | 0x80; - - std::array<char, 37> uuid_formatted; - snprintf(uuid_formatted.data(), uuid_formatted.size(), - "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", - uuid_bytes[0], uuid_bytes[1], uuid_bytes[2], uuid_bytes[3], - uuid_bytes[4], uuid_bytes[5], uuid_bytes[6], uuid_bytes[7], - uuid_bytes[8], uuid_bytes[9], uuid_bytes[10], uuid_bytes[11], - uuid_bytes[12], uuid_bytes[13], uuid_bytes[14], uuid_bytes[15]); - - return std::string(uuid_formatted.data(), uuid_formatted.size() - 1); -} - -} // namespace util -} // namespace realm diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/util/uuid.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/util/uuid.hpp deleted file mode 100644 index 8f37e5214..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/util/uuid.hpp +++ /dev/null @@ -1,33 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 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_OS_UTIL_UUID_HPP -#define REALM_OS_UTIL_UUID_HPP - -#include <string> - -namespace realm { -namespace util { - -// Generate a random UUID and return its formatted string representation. -std::string uuid_string(); - -} // namespace util -} // namespace realm - -#endif // REALM_OS_UTIL_UUID_HPP diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/CMakeLists.txt b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/CMakeLists.txt deleted file mode 100644 index 72f951c06..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/CMakeLists.txt +++ /dev/null @@ -1,83 +0,0 @@ -include_directories(../external/catch/single_include .) - -set(HEADERS - util/event_loop.hpp - util/index_helpers.hpp - util/test_file.hpp - util/test_utils.hpp -) - -set(SOURCES - collection_change_indices.cpp - frozen_objects.cpp - index_set.cpp - list.cpp - main.cpp - migrations.cpp - object.cpp - object_store.cpp - primitive_list.cpp - realm.cpp - results.cpp - schema.cpp - thread_safe_reference.cpp - transaction_log_parsing.cpp - uuid.cpp - - util/event_loop.cpp - util/test_file.cpp - util/test_utils.cpp -) - - -if(REALM_ENABLE_SYNC) - list(APPEND HEADERS - sync/sync_test_utils.hpp - sync/session/session_util.hpp - ) - list(APPEND SOURCES - sync/file.cpp - sync/metadata.cpp - sync/partial_sync.cpp - sync/permission.cpp - sync/session/connection_change_notifications.cpp - sync/session/progress_notifications.cpp - sync/session/session.cpp - sync/session/wait_for_completion.cpp - sync/sync_manager.cpp - sync/sync_test_utils.cpp - sync/user.cpp - ) -endif() - -if(REALM_ENABLE_SERVER) - list(APPEND SOURCES - sync/global_notifier.cpp - ) -endif() - -add_executable(tests ${SOURCES} ${HEADERS}) -target_compile_definitions(tests PRIVATE ${PLATFORM_DEFINES}) - -if(VSCODE_TEST_RUNNER) - # Increase the Catch2 virtual console width so that the Visual Studio Code - # Test Explorer extension can parse long test names - target_compile_definitions(tests PRIVATE -DCATCH_CONFIG_CONSOLE_WIDTH=300) -endif() - -if(REALM_ENABLE_SYNC) - # It's necessary to explicitly link to realm-sync here to control the order in which libraries are - # linked to avoid link errors when using GNU ld. - target_link_libraries(tests realm-sync realm-sync-server realm-parser) -endif() - -target_link_libraries(tests realm-object-store ${PLATFORM_LIBRARIES}) -add_custom_command(TARGET tests POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/sync-1.x.realm $<TARGET_FILE_DIR:tests>) - -create_coverage_target(generate-coverage tests) - -add_custom_target(run-tests USES_TERMINAL DEPENDS tests COMMAND ./tests) - -add_subdirectory(notifications-fuzzer) -add_subdirectory(benchmarks) diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/collection_change_indices.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/collection_change_indices.cpp deleted file mode 100644 index 62a218844..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/collection_change_indices.cpp +++ /dev/null @@ -1,965 +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 "catch2/catch.hpp" - -#include "impl/collection_notifier.hpp" - -#include "util/index_helpers.hpp" - -#include <limits> - -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, 1); - - c.insert(1); - REQUIRE_INDICES(c.insertions, 1, 6); - REQUIRE_INDICES(c.modifications, 9); - REQUIRE_COLUMN_INDICES(c.columns, 1, 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); - REQUIRE(c.columns.empty()); - } - - SECTION("also marks newly inserted rows as modified") { - c.insert(5); - c.modify(5); - REQUIRE_INDICES(c.modifications, 5); - REQUIRE(c.columns.empty()); - } - - SECTION("is idempotent") { - c.modify(5); - c.modify(5); - c.modify(5); - c.modify(5); - REQUIRE_INDICES(c.modifications, 5); - REQUIRE(c.columns.empty()); - } - - SECTION("marks the appropriate column as modified when applicable") { - c.modify(5, 2); - REQUIRE_INDICES(c.modifications, 5); - REQUIRE(c.columns.size() == 1); - REQUIRE_INDICES(c.columns[2], 5); - - c.modify(4, 2); - REQUIRE_INDICES(c.modifications, 4, 5); - REQUIRE(c.columns.size() == 1); - REQUIRE_INDICES(c.columns[2], 4, 5); - - c.modify(3, 1); - REQUIRE_INDICES(c.modifications, 3, 4, 5); - REQUIRE(c.columns.size() == 2); - REQUIRE_INDICES(c.columns[1], 3); - REQUIRE_INDICES(c.columns[2], 4, 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, 0); - c.erase(5); - REQUIRE(c.modifications.empty()); - REQUIRE(c.columns[0].empty()); - REQUIRE_INDICES(c.deletions, 5); - } - - SECTION("shifts previous modifications") { - c.modify(5, 0); - c.erase(4); - REQUIRE_INDICES(c.modifications, 4); - REQUIRE_COLUMN_INDICES(c.columns, 0, 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: 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); - } -} - -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, 0); - c.move(10, 0); - REQUIRE_INDICES(c.insertions, 0, 6); - REQUIRE_INDICES(c.modifications, 7); - REQUIRE_COLUMN_INDICES(c.columns, 0, 7); - REQUIRE_MOVES(c, {9, 0}); - } - - SECTION("marks the target row as modified if the source row was") { - c.modify(5, 0); - - c.move(5, 10); - REQUIRE_INDICES(c.modifications, 10); - REQUIRE_COLUMN_INDICES(c.columns, 0, 10); - - c.move(6, 12); - REQUIRE_INDICES(c.modifications, 9); - REQUIRE_COLUMN_INDICES(c.columns, 0, 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() table order") { - _impl::CollectionChangeBuilder c; - - auto all_modified = [](size_t) { return true; }; - auto none_modified = [](size_t) { return false; }; - bool in_table_order = true; - - SECTION("returns an empty set when input and output are identical") { - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3}, {1, 2, 3}, none_modified, in_table_order); - REQUIRE(c.empty()); - } - - SECTION("marks all as inserted when prev is empty") { - c = _impl::CollectionChangeBuilder::calculate({}, {1, 2, 3}, all_modified, in_table_order); - 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, in_table_order); - REQUIRE_INDICES(c.deletions, 0, 1, 2); - } - - SECTION("marks npos rows in prev as deleted") { - c = _impl::CollectionChangeBuilder::calculate({-1, 1, 2, 3, -1}, {1, 2, 3}, all_modified, in_table_order); - 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, in_table_order); - 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, in_table_order); - REQUIRE(c.modifications.empty()); - } - - SECTION("marks newly added rows as insertions") { - c = _impl::CollectionChangeBuilder::calculate({2, 3}, {1, 2, 3}, all_modified, in_table_order); - REQUIRE_INDICES(c.insertions, 0); - - c = _impl::CollectionChangeBuilder::calculate({1, 3}, {1, 2, 3}, all_modified, in_table_order); - REQUIRE_INDICES(c.insertions, 1); - - c = _impl::CollectionChangeBuilder::calculate({1, 2}, {1, 2, 3}, all_modified, in_table_order); - REQUIRE_INDICES(c.insertions, 2); - } - - SECTION("marks removed rows as deleted") { - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3}, {1, 2}, all_modified, in_table_order); - REQUIRE_INDICES(c.deletions, 2); - - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3}, {1, 3}, all_modified, in_table_order); - REQUIRE_INDICES(c.deletions, 1); - - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3}, {2, 3}, all_modified, in_table_order); - 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, in_table_order); - REQUIRE_INDICES(c.deletions, 1); - REQUIRE_INDICES(c.insertions, 2); - REQUIRE(c.moves.empty()); - } - - SECTION("does not mark rows as modified if they are new") { - c = _impl::CollectionChangeBuilder::calculate({3}, {3, 5}, all_modified, in_table_order); - REQUIRE_INDICES(c.modifications, 0); - } - -#if 0 // FIXME: these tests might be applicable to LinkingObjects - SECTION("reports moves which can be produced by move_last_over()") { - auto calc = [&](std::vector<int64_t> values) { - return _impl::CollectionChangeBuilder::calculate(values, {1, 2, 3}, none_modified, all); - }; - - 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}); - } - - SECTION("resolves ambiguous moves using the candidate set") { - c = _impl::CollectionChangeBuilder::calculate({3, 1, 2}, {1, 2, 3}, none_modified, all); - REQUIRE_MOVES(c, {1, 0}, {2, 1}); - c = _impl::CollectionChangeBuilder::calculate({3, 1, 2}, {1, 2, 3}, none_modified, IndexSet{3}); - REQUIRE_MOVES(c, {0, 2}); - } -#endif -} - -TEST_CASE("collection_change: calculate() sorted") { - _impl::CollectionChangeBuilder c; - - auto all_modified = [](size_t) { return true; }; - auto none_modified = [](size_t) { return false; }; - size_t npos = -1; - - SECTION("returns an empty set when input and output are identical") { - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3}, {1, 2, 3}, none_modified); - REQUIRE(c.empty()); - } - - SECTION("marks all as inserted when prev is empty") { - c = _impl::CollectionChangeBuilder::calculate({}, {1, 2, 3}, all_modified); - 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); - 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); - 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); - 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); - REQUIRE(c.modifications.empty()); - } - - SECTION("marks newly added rows as insertions") { - c = _impl::CollectionChangeBuilder::calculate({2, 3}, {1, 2, 3}, all_modified); - REQUIRE_INDICES(c.insertions, 0); - - c = _impl::CollectionChangeBuilder::calculate({1, 3}, {1, 2, 3}, all_modified); - REQUIRE_INDICES(c.insertions, 1); - - c = _impl::CollectionChangeBuilder::calculate({1, 2}, {1, 2, 3}, all_modified); - REQUIRE_INDICES(c.insertions, 2); - } - - SECTION("marks removed rows as deleted") { - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3}, {1, 2}, all_modified); - REQUIRE_INDICES(c.deletions, 2); - - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3}, {1, 3}, all_modified); - REQUIRE_INDICES(c.deletions, 1); - - c = _impl::CollectionChangeBuilder::calculate({1, 2, 3}, {2, 3}, all_modified); - 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); - 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); - 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); - REQUIRE_INDICES(c.modifications, 0); - } - - SECTION("reports inserts/deletes for simple reorderings") { - auto calc = [&](std::vector<size_t> old_rows, std::vector<size_t> new_rows) { - return _impl::CollectionChangeBuilder::calculate(old_rows, new_rows, none_modified); - }; - - 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); - 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); - 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); - 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); - 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); - 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); - 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); - REQUIRE_INDICES(c.deletions, 3); - REQUIRE_INDICES(c.insertions, 1, 2, 3); - } - - SECTION("properly recurses into smaller subblocks") { - std::vector<size_t> prev = {10, 1, 2, 11, 3, 4, 5, 12, 6, 7, 13}; - std::vector<size_t> next = {13, 1, 2, 12, 3, 4, 5, 11, 6, 7, 10}; - c = _impl::CollectionChangeBuilder::calculate(prev, next, all_modified); - 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<size_t> 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); - - std::vector<size_t> 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)); - - c.merge(_impl::CollectionChangeBuilder::calculate(after_move, {1, 2, 3}, four_modified)); - 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.columns = {{0, {6}}, {1, {7}}}; - c.merge({}); - REQUIRE_INDICES(c.deletions, 1, 2, 3, 8); - REQUIRE_INDICES(c.insertions, 4, 5, 9); - REQUIRE_INDICES(c.modifications, 6, 7); - REQUIRE_COLUMN_INDICES(c.columns, 0, 6); - REQUIRE_COLUMN_INDICES(c.columns, 1, 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()); - } - - SECTION("column-level modifications") { - _impl::CollectionChangeBuilder c2; - c = {{}, {}, {1, 2, 3}, {}}; - c.columns = {{0, {1}}, {1, {2, 3}}}; - - SECTION("preserved on no-op merges") { - c.merge({}); - REQUIRE_COLUMN_INDICES(c.columns, 0, 1); - REQUIRE_COLUMN_INDICES(c.columns, 1, 2, 3); - - c2.merge(std::move(c)); - REQUIRE_COLUMN_INDICES(c2.columns, 0, 1); - REQUIRE_COLUMN_INDICES(c2.columns, 1, 2, 3); - } - - SECTION("merged with other column-level modifications") { - c2.modifications = {0, 4}; - c2.columns = {{0, {1, 2}}, {2, {4}}}; - c.merge(std::move(c2)); - - REQUIRE_COLUMN_INDICES(c.columns, 0, 1, 2); - REQUIRE_COLUMN_INDICES(c.columns, 1, 2, 3); - REQUIRE_COLUMN_INDICES(c.columns, 2, 4); - } - - SECTION("removed by deletions") { - c2.deletions = {2}; - c.merge(std::move(c2)); - REQUIRE_COLUMN_INDICES(c.columns, 0, 1); - REQUIRE_COLUMN_INDICES(c.columns, 1, 2); - } - - SECTION("shifted by insertions") { - c2.insertions = {3}; - c.merge(std::move(c2)); - REQUIRE_COLUMN_INDICES(c.columns, 0, 1); - REQUIRE_COLUMN_INDICES(c.columns, 1, 2, 4); - } - } -} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/index_set.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/index_set.cpp deleted file mode 100644 index 2fb78009c..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/index_set.cpp +++ /dev/null @@ -1,610 +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 "catch2/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/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/list.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/list.cpp deleted file mode 100644 index 54f7741df..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/list.cpp +++ /dev/null @@ -1,912 +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 "catch2/catch.hpp" - -#include "util/test_file.hpp" -#include "util/index_helpers.hpp" - -#include "binding_context.hpp" -#include "list.hpp" -#include "object.hpp" -#include "object_schema.hpp" -#include "property.hpp" -#include "results.hpp" -#include "schema.hpp" - -#include "impl/realm_coordinator.hpp" -#include "impl/object_accessor_impl.hpp" - -#include <realm/version.hpp> -#include <realm/db.hpp> - -#include <cstdint> - -using namespace realm; - -TEST_CASE("list") { - InMemoryTestFile config; - config.automatic_change_notifications = false; - auto r = Realm::get_shared_realm(config); - r->update_schema({ - {"origin", { - {"pk", PropertyType::Int, Property::IsPrimary{true}}, - {"array", PropertyType::Array|PropertyType::Object, "target"} - }}, - {"target", { - {"value", PropertyType::Int} - }}, - {"other_origin", { - {"array", PropertyType::Array|PropertyType::Object, "other_target"} - }}, - {"other_target", { - {"value", PropertyType::Int} - }}, - }); - - auto& coordinator = *_impl::RealmCoordinator::get_coordinator(config.path); - - auto origin = r->read_group().get_table("class_origin"); - auto target = r->read_group().get_table("class_target"); - auto other_origin = r->read_group().get_table("class_other_origin"); - auto other_target = r->read_group().get_table("class_other_target"); - ColKey col_link = origin->get_column_key("array"); - ColKey col_value = target->get_column_key("value"); - ColKey other_col_link = other_origin->get_column_key("array"); - ColKey other_col_value = other_target->get_column_key("value"); - - r->begin_transaction(); - - std::vector<ObjKey> target_keys; - target->create_objects(10, target_keys); - for (int i = 0; i < 10; ++i) - target->get_object(target_keys[i]).set_all(i); - - Obj obj = origin->create_object(); - auto lv = obj.get_linklist_ptr(col_link); - for (int i = 0; i < 10; ++i) - lv->add(target_keys[i]); - auto lv2 = origin->create_object().get_linklist_ptr(col_link); - for (int i = 0; i < 10; ++i) - lv2->add(target_keys[i]); - - ObjKeys other_target_keys({3, 5, 7, 9, 11, 13, 15, 17, 19, 21}); - other_target->create_objects(other_target_keys); - for (int i = 0; i < 10; ++i) - other_target->get_object(other_target_keys[i]).set_all(i); - - Obj other_obj = other_origin->create_object(); - auto other_lv = other_obj.get_linklist_ptr(other_col_link); - for (int i = 0; i < 10; ++i) - other_lv->add(other_target_keys[i]); - - r->commit_transaction(); - - auto r2 = coordinator.get_realm(); - auto r2_lv = r2->read_group().get_table("class_origin")->get_object(0).get_linklist_ptr(col_link); - - SECTION("add_notification_block()") { - CollectionChangeSet change; - List lst(r, obj, col_link); - - 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([&] { if (lv2->size() > 5) 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([&] { if (lv2->size() > 5) lv2->remove(5); }); - } - - SECTION("deleting the list sends a change notification") { - auto token = require_change(); - write([&] { obj.remove(); }); - 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->create_object(); }); - REQUIRE(change.empty()); - } - - SECTION("deleting list before first run of notifier reports deletions") { - auto token = lst.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) { - change = c; - }); - advance_and_notify(*r); - write([&] { origin->begin()->remove(); }); - REQUIRE_INDICES(change.deletions, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9); - } - - SECTION("modifying one of the target rows sends a change notification") { - auto token = require_change(); - write([&] { lst.get(5).set(col_value, 6); }); - REQUIRE_INDICES(change.modifications, 5); - } - - SECTION("deleting a target row sends a change notification") { - auto token = require_change(); - write([&] { target->remove_object(target_keys[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([&] { - Obj obj = target->get_object(target_keys[5]); - lst.add(obj); - obj.set(col_value, 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->get_object(target_keys[5]).set(col_value, 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(target_keys[5]); - r->commit_transaction(); - - auto token = require_change(); - write([&] { target->get_object(target_keys[5]).set(col_value, 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(target_keys[5]); - r->commit_transaction(); - - auto token = require_change(); - write([&] { target->remove_object(target_keys[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(); - if (lv->size() > 2) - lv->remove(2); - r->commit_transaction(); - - auto token = require_no_change(); - write([&] { target->remove_object(target_keys[2]); }); - } - - SECTION("multiple LinkViews for the same LinkList can get notifications") { - r->begin_transaction(); - target->clear(); - std::vector<ObjKey> keys; - target->create_objects(5, keys); - r->commit_transaction(); - - auto get_list = [&] { - auto r = Realm::get_shared_realm(config); - auto obj = r->read_group().get_table("class_origin")->get_object(0); - return List(r, obj, col_link); - }; - auto change_list = [&] { - r->begin_transaction(); - if (lv->size()) { - target->get_object(lv->size() - 1).set(col_value, int64_t(lv->size())); - } - lv->add(keys[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("multiple callbacks for the same Lists can be skipped individually") { - auto token = require_no_change(); - auto token2 = require_change(); - - r->begin_transaction(); - lv->add(target_keys[0]); - token.suppress_next(); - r->commit_transaction(); - - advance_and_notify(*r); - REQUIRE_INDICES(change.insertions, 10); - } - - SECTION("multiple Lists for the same LinkView can be skipped individually") { - auto token = require_no_change(); - - List list2(r, obj, col_link); - auto token2 = list2.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) { - change = c; - }); - advance_and_notify(*r); - - r->begin_transaction(); - lv->add(target_keys[0]); - token.suppress_next(); - r->commit_transaction(); - - advance_and_notify(*r); - REQUIRE_INDICES(change.insertions, 10); - } - - SECTION("skipping only effects the current transaction even if no notification would occur anyway") { - auto token = require_change(); - - // would not produce a notification even if it wasn't skipped because no changes were made - r->begin_transaction(); - token.suppress_next(); - r->commit_transaction(); - advance_and_notify(*r); - REQUIRE(change.empty()); - - // should now produce a notification - r->begin_transaction(); - lv->add(target_keys[0]); - r->commit_transaction(); - advance_and_notify(*r); - REQUIRE_INDICES(change.insertions, 10); - } - - SECTION("modifying a different table does not send a change notification") { - auto token = require_no_change(); - write([&] { other_lv->add(other_target_keys[0]); }); - } - - SECTION("changes are reported correctly for multiple tables") { - List list2(r, *other_lv); - CollectionChangeSet other_changes; - auto token1 = list2.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) { - other_changes = std::move(c); - }); - auto token2 = require_change(); - - write([&] { - lv->add(target_keys[1]); - - other_origin->create_object(); - if (other_lv->size() > 0) - other_lv->insert(1, other_target_keys[0]); - - lv->add(target_keys[2]); - }); - REQUIRE_INDICES(change.insertions, 10, 11); - REQUIRE_INDICES(other_changes.insertions, 1); - - write([&] { - lv->add(target_keys[3]); - other_obj.remove(); - lv->add(target_keys[4]); - }); - REQUIRE_INDICES(change.insertions, 12, 13); - REQUIRE_INDICES(other_changes.deletions, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); - - write([&] { - lv->add(target_keys[5]); - other_origin->clear(); - lv->add(target_keys[6]); - }); - REQUIRE_INDICES(change.insertions, 14, 15); - } - - SECTION("tables-of-interest are tracked properly for multiple source versions") { - // Add notifiers for different tables at different versions to verify - // that the tables of interest are updated correctly as we process - // new notifiers - CollectionChangeSet changes1, changes2; - auto token1 = lst.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) { - changes1 = std::move(c); - }); - - r2->begin_transaction(); - r2->read_group().get_table("class_target")->get_object(target_keys[0]).set(col_value, 10); - r2->read_group().get_table("class_other_target")->get_object(other_target_keys[1]).set(other_col_value, 10); - r2->commit_transaction(); - - List list2(r2, r2->read_group().get_table("class_other_origin")->get_object(0), other_col_link); - auto token2 = list2.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) { - changes2 = std::move(c); - }); - - auto r3 = coordinator.get_realm(); - r3->begin_transaction(); - r3->read_group().get_table("class_target")->get_object(target_keys[2]).set(col_value, 10); - r3->read_group().get_table("class_other_target")->get_object(other_target_keys[3]).set(other_col_value, 10); - r3->commit_transaction(); - - advance_and_notify(*r); - advance_and_notify(*r2); - - REQUIRE_INDICES(changes1.modifications, 0, 2); - REQUIRE_INDICES(changes2.modifications, 3); - } - - SECTION("modifications are reported for rows that are moved and then moved back in a second transaction") { - auto token = require_change(); - - r2->begin_transaction(); - r2_lv->get_object(5).set(col_value, 10); - r2_lv->get_object(1).set(col_value, 10); - r2_lv->move(5, 8); - r2_lv->move(1, 2); - r2->commit_transaction(); - - coordinator.on_change(); - - r2->begin_transaction(); - if (r2_lv->size() > 8) - r2_lv->move(8, 5); - r2->commit_transaction(); - advance_and_notify(*r); - - REQUIRE_INDICES(change.deletions, 1); - REQUIRE_INDICES(change.insertions, 2); - REQUIRE_INDICES(change.modifications, 5); - REQUIRE_MOVES(change, {1, 2}); - } - - SECTION("changes are sent in initial notification") { - auto token = lst.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) { - change = c; - }); - r2->begin_transaction(); - r2_lv->remove(5); - r2->commit_transaction(); - advance_and_notify(*r); - REQUIRE_INDICES(change.deletions, 5); - } - - SECTION("changes are sent in initial notification after removing and then re-adding callback") { - auto token = lst.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { - REQUIRE(false); - }); - token = {}; - - auto write = [&] { - r2->begin_transaction(); - r2_lv->remove(5); - r2->commit_transaction(); - }; - - SECTION("add new callback before transaction") { - token = lst.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) { - change = c; - }); - - write(); - - advance_and_notify(*r); - REQUIRE_INDICES(change.deletions, 5); - } - - SECTION("add new callback after transaction") { - write(); - - token = lst.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) { - change = c; - }); - - advance_and_notify(*r); - REQUIRE_INDICES(change.deletions, 5); - } - - SECTION("add new callback after transaction and after changeset was calculated") { - write(); - coordinator.on_change(); - - token = lst.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) { - change = c; - }); - - advance_and_notify(*r); - REQUIRE_INDICES(change.deletions, 5); - } - } - } - - SECTION("sorted add_notification_block()") { - List lst(r, *lv); - Results results = lst.sort({{{col_value}}, {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(target_keys[5]); - lst.add(target_keys[5]); - lst.add(target_keys[5]); - }); - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.insertions, 5, 6, 7); - } - - SECTION("change order by modifying target") { - write([&] { - lst.get(5).set(col_value, 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(col_value, 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(target_keys[5]); - lst.add(target_keys[5]); - lst.add(target_keys[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->schema().find("target"); - List list(r, *lv); - auto results = list.sort({{{col_value}}, {false}}); - - REQUIRE(&results.get_object_schema() == objectschema); - REQUIRE(results.get_mode() == Results::Mode::LinkList); - REQUIRE(results.size() == 10); - - // Aggregates don't inherently have to convert to TableView, but do - // because aggregates aren't implemented for LinkView - REQUIRE(results.sum(col_value) == 45); - REQUIRE(results.get_mode() == Results::Mode::TableView); - - // Reset to LinkView mode to test implicit conversion to TableView on get() - results = list.sort({{{col_value}}, {false}}); - for (size_t i = 0; i < 10; ++i) - REQUIRE(results.get(i).get_key() == target_keys[9 - i]); - REQUIRE_THROWS_WITH(results.get(10), "Requested index 10 greater than max 9"); - REQUIRE(results.get_mode() == Results::Mode::TableView); - - // Zero sort columns should leave it in LinkView mode - results = list.sort(SortDescriptor()); - for (size_t i = 0; i < 10; ++i) - REQUIRE(results.get(i).get_key() == target_keys[i]); - REQUIRE_THROWS_WITH(results.get(10), "Requested index 10 greater than max 9"); - REQUIRE(results.get_mode() == Results::Mode::LinkList); - } - - SECTION("filter()") { - auto objectschema = &*r->schema().find("target"); - List list(r, *lv); - auto results = list.filter(target->where().greater(col_value, 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_key().value == i + 6); - } - } - - SECTION("snapshot()") { - auto objectschema = &*r->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_valid()); - } - for (size_t i = 0; i < 5; ++i) { - target->remove_object(target_keys[i]); - } - REQUIRE(snapshot.size() == 10); - for (size_t i = 0; i < 5; ++i) { - REQUIRE(!snapshot.get(i).is_valid()); - } - for (size_t i = 5; i < 10; ++i) { - REQUIRE(snapshot.get(i).is_valid()); - } - list.add(target_keys[5]); - REQUIRE(snapshot.size() == 10); - } - - SECTION("get_object_schema()") { - List list(r, *lv); - auto objectschema = &*r->schema().find("target"); - REQUIRE(&list.get_object_schema() == objectschema); - } - - SECTION("delete_at()") { - List list(r, *lv); - r->begin_transaction(); - auto initial_view_size = lv->size(); - auto initial_target_size = target->size(); - list.delete_at(1); - REQUIRE(lv->size() == initial_view_size - 1); - REQUIRE(target->size() == initial_target_size - 1); - r->cancel_transaction(); - } - - SECTION("delete_all()") { - List list(r, *lv); - r->begin_transaction(); - list.delete_all(); - REQUIRE(lv->size() == 0); - REQUIRE(target->size() == 0); - r->cancel_transaction(); - } - - SECTION("as_results().clear()") { - List list(r, *lv); - r->begin_transaction(); - list.as_results().clear(); - REQUIRE(lv->size() == 0); - REQUIRE(target->size() == 0); - r->cancel_transaction(); - } - - SECTION("snapshot().clear()") { - List list(r, *lv); - r->begin_transaction(); - auto snapshot = list.snapshot(); - snapshot.clear(); - REQUIRE(snapshot.size() == 10); - REQUIRE(list.size() == 0); - REQUIRE(lv->size() == 0); - REQUIRE(target->size() == 0); - r->cancel_transaction(); - } - - SECTION("add(RowExpr)") { - List list(r, *lv); - r->begin_transaction(); - SECTION("adds rows from the correct table") { - list.add(target_keys[5]); - REQUIRE(list.size() == 11); - REQUIRE(list.get(10).get_key() == target_keys[5]); - } - - SECTION("throws for rows from the wrong table") { - REQUIRE_THROWS(list.add(obj)); - } - r->cancel_transaction(); - } - - SECTION("insert(RowExpr)") { - List list(r, *lv); - r->begin_transaction(); - - SECTION("insert rows from the correct table") { - list.insert(0, target_keys[5]); - REQUIRE(list.size() == 11); - REQUIRE(list.get(0).get_key() == target_keys[5]); - } - - SECTION("throws for rows from the wrong table") { - REQUIRE_THROWS(list.insert(0, obj)); - } - - SECTION("throws for out of bounds insertions") { - REQUIRE_THROWS(list.insert(11, target_keys[5])); - REQUIRE_NOTHROW(list.insert(10, target_keys[5])); - } - r->cancel_transaction(); - } - - SECTION("set(RowExpr)") { - List list(r, *lv); - r->begin_transaction(); - - SECTION("assigns for rows from the correct table") { - list.set(0, target_keys[5]); - REQUIRE(list.size() == 10); - REQUIRE(list.get(0).get_key() == target_keys[5]); - } - - SECTION("throws for rows from the wrong table") { - REQUIRE_THROWS(list.set(0, obj)); - } - - SECTION("throws for out of bounds sets") { - REQUIRE_THROWS(list.set(10, target_keys[5])); - } - r->cancel_transaction(); - } - - SECTION("find(RowExpr)") { - List list(r, *lv); - Obj obj1 = target->get_object(target_keys[1]); - Obj obj5 = target->get_object(target_keys[5]); - - SECTION("returns index in list for values in the list") { - REQUIRE(list.find(obj5) == 5); - } - - SECTION("returns index in list and not index in table") { - r->begin_transaction(); - list.remove(1); - REQUIRE(list.find(obj5) == 4); - REQUIRE(list.as_results().index_of(obj5) == 4); - r->cancel_transaction(); - } - - SECTION("returns npos for values not in the list") { - r->begin_transaction(); - list.remove(1); - REQUIRE(list.find(obj1) == npos); - REQUIRE(list.as_results().index_of(obj1) == npos); - r->cancel_transaction(); - } - - SECTION("throws for row in wrong table") { - REQUIRE_THROWS(list.find(obj)); - REQUIRE_THROWS(list.as_results().index_of(obj)); - } - } - - SECTION("find(Query)") { - List list(r, *lv); - - SECTION("returns index in list for values in the list") { - REQUIRE(list.find(std::move(target->where().equal(col_value, 5))) == 5); - } - - SECTION("returns index in list and not index in table") { - r->begin_transaction(); - list.remove(1); - REQUIRE(list.find(std::move(target->where().equal(col_value, 5))) == 4); - r->cancel_transaction(); - } - - SECTION("returns npos for values not in the list") { - REQUIRE(list.find(std::move(target->where().equal(col_value, 11))) == npos); - } - } - - SECTION("add(Context)") { - List list(r, *lv); - CppContext ctx(r, &list.get_object_schema()); - r->begin_transaction(); - - SECTION("adds boxed RowExpr") { - list.add(ctx, util::Any(target->get_object(target_keys[5]))); - REQUIRE(list.size() == 11); - REQUIRE(list.get(10).get_key().value == 5); - } - - SECTION("adds boxed realm::Object") { - realm::Object obj(r, list.get_object_schema(), target->get_object(target_keys[5])); - list.add(ctx, util::Any(obj)); - REQUIRE(list.size() == 11); - REQUIRE(list.get(10).get_key() == target_keys[5]); - } - - SECTION("creates new object for dictionary") { - list.add(ctx, util::Any(AnyDict{{"value", INT64_C(20)}})); - REQUIRE(list.size() == 11); - REQUIRE(target->size() == 11); - REQUIRE(list.get(10).get<Int>(col_value) == 20); - } - - SECTION("throws for object in wrong table") { - REQUIRE_THROWS(list.add(ctx, util::Any(origin->get_object(0)))); - realm::Object object(r, *r->schema().find("origin"), origin->get_object(0)); - REQUIRE_THROWS(list.add(ctx, util::Any(object))); - } - - r->cancel_transaction(); - } - - SECTION("find(Context)") { - List list(r, *lv); - CppContext ctx(r, &list.get_object_schema()); - - SECTION("returns index in list for boxed RowExpr") { - REQUIRE(list.find(ctx, util::Any(target->get_object(target_keys[5]))) == 5); - } - - SECTION("returns index in list for boxed Object") { - realm::Object obj(r, *r->schema().find("origin"), target->get_object(target_keys[5])); - REQUIRE(list.find(ctx, util::Any(obj)) == 5); - } - - SECTION("does not insert new objects for dictionaries") { - REQUIRE(list.find(ctx, util::Any(AnyDict{{"value", INT64_C(20)}})) == npos); - REQUIRE(target->size() == 10); - } - - SECTION("throws for object in wrong table") { - REQUIRE_THROWS(list.find(ctx, util::Any(obj))); - } - } - - SECTION("get(Context)") { - List list(r, *lv); - CppContext ctx(r, &list.get_object_schema()); - - Object obj; - REQUIRE_NOTHROW(obj = any_cast<Object&&>(list.get(ctx, 1))); - REQUIRE(obj.is_valid()); - REQUIRE(obj.obj().get_key() == target_keys[1]); - } -} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/main.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/main.cpp deleted file mode 100644 index fbaf79d28..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/main.cpp +++ /dev/null @@ -1,52 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#define CATCH_CONFIG_RUNNER -#include "catch2/catch.hpp" - -#include <limits.h> - -#ifdef _MSC_VER -#include <Shlwapi.h> -#pragma comment(lib, "Shlwapi.lib") -#else -#include <libgen.h> -#endif - -int main(int argc, char** argv) { -#ifdef _MSC_VER - char path[MAX_PATH]; - if (GetModuleFileNameA(NULL, path, sizeof(path)) == 0) { - fprintf(stderr, "Failed to retrieve path to exectuable.\n"); - return 1; - } - PathRemoveFileSpecA(path); - SetCurrentDirectoryA(path); -#else - char executable[PATH_MAX]; - if (realpath(argv[0], executable) == NULL) { - fprintf(stderr, "Failed to resolve path to exectuable.\n"); - return 1; - } - const char* directory = dirname(executable); - chdir(directory); -#endif - - int result = Catch::Session().run(argc, argv); - return result < 0xff ? result : 0xff; -} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/migrations.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/migrations.cpp deleted file mode 100644 index 5f4ca60b5..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/migrations.cpp +++ /dev/null @@ -1,2059 +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 "catch2/catch.hpp" - -#include "util/test_file.hpp" - -#include "object_schema.hpp" -#include "object_store.hpp" -#include "property.hpp" -#include "schema.hpp" - -#include "impl/object_accessor_impl.hpp" - -#include <realm/group.hpp> -#include <realm/table.hpp> -#include <realm/util/scope_exit.hpp> - -#ifdef _WIN32 -#include <Windows.h> -#endif - -using namespace realm; - -#define VERIFY_SCHEMA(r, m) verify_schema((r), __LINE__, m) - -#define REQUIRE_UPDATE_SUCCEEDS(r, s, version) do { \ - REQUIRE_NOTHROW((r).update_schema(s, version)); \ - VERIFY_SCHEMA(r, false); \ - REQUIRE((r).schema() == s); \ -} while (0) - -#define REQUIRE_NO_MIGRATION_NEEDED(r, schema1, schema2) do { \ - REQUIRE_UPDATE_SUCCEEDS(r, schema1, 0); \ - REQUIRE_UPDATE_SUCCEEDS(r, schema2, 0); \ -} while (0) - -#define REQUIRE_MIGRATION_NEEDED(r, schema1, schema2) do { \ - REQUIRE_UPDATE_SUCCEEDS(r, schema1, 0); \ - REQUIRE_THROWS((r).update_schema(schema2)); \ - REQUIRE((r).schema() == schema1); \ - REQUIRE_UPDATE_SUCCEEDS(r, schema2, 1); \ -} while (0) - -namespace { -void verify_schema(Realm& r, int line, bool in_migration) -{ - CAPTURE(line); - for (auto&& object_schema : r.schema()) { - auto table = r.read_group().get_table(object_schema.table_key); - REQUIRE(table); - REQUIRE(std::string(table->get_name()) == ObjectStore::table_name_for_object_type(object_schema.name)); - CAPTURE(object_schema.name); - std::string primary_key; - if (!in_migration) { - primary_key = ObjectStore::get_primary_key_for_object(r.read_group(), object_schema.name); - REQUIRE(primary_key == object_schema.primary_key); - } - else { - primary_key = object_schema.primary_key; - } - for (auto&& prop : object_schema.persisted_properties) { - auto col = table->get_column_key(prop.name); - CAPTURE(prop.name); - REQUIRE(col); - REQUIRE(col == prop.column_key); - REQUIRE(to_underlying(ObjectSchema::from_core_type(*table, col)) == - to_underlying(prop.type)); - REQUIRE(table->has_search_index(col) == prop.requires_index()); - REQUIRE(bool(prop.is_primary) == (prop.name == primary_key)); - } - } -} - -TableRef get_table(std::shared_ptr<Realm> const& realm, StringData object_type) -{ - return ObjectStore::table_for_object_type(realm->read_group(), object_type); -} - -// Helper functions for modifying Schema objects, mostly for the sake of making -// it clear what exactly is different about the 2+ schema objects used in -// various tests -Schema add_table(Schema const& schema, ObjectSchema object_schema) -{ - std::vector<ObjectSchema> new_schema(schema.begin(), schema.end()); - new_schema.push_back(std::move(object_schema)); - return new_schema; -} - -Schema remove_table(Schema const& schema, StringData object_name) -{ - std::vector<ObjectSchema> new_schema; - std::remove_copy_if(schema.begin(), schema.end(), std::back_inserter(new_schema), - [&](auto&& object_schema) { return object_schema.name == object_name; }); - return new_schema; -} - -Schema add_property(Schema schema, StringData object_name, Property property) -{ - schema.find(object_name)->persisted_properties.push_back(std::move(property)); - return schema; -} - -Schema remove_property(Schema schema, StringData object_name, StringData property_name) -{ - auto& properties = schema.find(object_name)->persisted_properties; - properties.erase(find_if(begin(properties), end(properties), - [&](auto&& prop) { return prop.name == property_name; })); - return schema; -} - -Schema set_indexed(Schema schema, StringData object_name, StringData property_name, bool value) -{ - schema.find(object_name)->property_for_name(property_name)->is_indexed = value; - return schema; -} - -Schema set_optional(Schema schema, StringData object_name, StringData property_name, bool value) -{ - auto& prop = *schema.find(object_name)->property_for_name(property_name); - if (value) - prop.type |= PropertyType::Nullable; - else - prop.type &= ~PropertyType::Nullable; - return schema; -} - -Schema set_type(Schema schema, StringData object_name, StringData property_name, PropertyType value) -{ - schema.find(object_name)->property_for_name(property_name)->type = value; - return schema; -} - -Schema set_target(Schema schema, StringData object_name, StringData property_name, StringData new_target) -{ - schema.find(object_name)->property_for_name(property_name)->object_type = new_target; - return schema; -} - -Schema set_primary_key(Schema schema, StringData object_name, StringData new_primary_property) -{ - auto& object_schema = *schema.find(object_name); - if (auto old_primary = object_schema.primary_key_property()) { - old_primary->is_primary = false; - } - if (new_primary_property.size()) { - object_schema.property_for_name(new_primary_property)->is_primary = true; - } - object_schema.primary_key = new_primary_property; - return schema; -} -auto create_objects(Table& table, size_t count) { - std::vector<ObjKey> keys; - table.create_objects(count, keys); - return keys; -}; -} // anonymous namespace - -TEST_CASE("migration: Automatic") { - InMemoryTestFile config; - config.automatic_change_notifications = false; - - SECTION("no migration required") { - SECTION("add object schema") { - auto realm = Realm::get_shared_realm(config); - - Schema schema1 = {}; - Schema schema2 = add_table(schema1, {"object", { - {"value", PropertyType::Int} - }}); - Schema schema3 = add_table(schema2, {"object2", { - {"value", PropertyType::Int} - }}); - REQUIRE_UPDATE_SUCCEEDS(*realm, schema1, 0); - REQUIRE_UPDATE_SUCCEEDS(*realm, schema2, 0); - REQUIRE_UPDATE_SUCCEEDS(*realm, schema3, 0); - } - - SECTION("remove object schema") { - auto realm = Realm::get_shared_realm(config); - - Schema schema1 = { - {"object", { - {"value", PropertyType::Int} - }}, - {"object2", { - {"value", PropertyType::Int} - }}, - }; - Schema schema2 = remove_table(schema1, "object2"); - Schema schema3 = remove_table(schema2, "object"); - REQUIRE_UPDATE_SUCCEEDS(*realm, schema3, 0); - REQUIRE_UPDATE_SUCCEEDS(*realm, schema2, 0); - REQUIRE_UPDATE_SUCCEEDS(*realm, schema1, 0); - } - - SECTION("add index") { - auto realm = Realm::get_shared_realm(config); - Schema schema = { - {"object", { - {"value", PropertyType::Int} - }}, - }; - REQUIRE_NO_MIGRATION_NEEDED(*realm, schema, set_indexed(schema, "object", "value", true)); - } - - SECTION("remove index") { - auto realm = Realm::get_shared_realm(config); - Schema schema = { - {"object", { - {"value", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}} - }}, - }; - REQUIRE_NO_MIGRATION_NEEDED(*realm, schema, set_indexed(schema, "object", "value", false)); - } - - SECTION("reordering properties") { - auto realm = Realm::get_shared_realm(config); - - Schema schema1 = { - {"object", { - {"col1", PropertyType::Int}, - {"col2", PropertyType::Int}, - }}, - }; - Schema schema2 = { - {"object", { - {"col2", PropertyType::Int}, - {"col1", PropertyType::Int}, - }}, - }; - REQUIRE_NO_MIGRATION_NEEDED(*realm, schema1, schema2); - } - } - - SECTION("migration required") { - SECTION("add property to existing object schema") { - auto realm = Realm::get_shared_realm(config); - - Schema schema1 = { - {"object", { - {"col1", PropertyType::Int}, - }}, - }; - auto schema2 = add_property(schema1, "object", - {"col2", PropertyType::Int}); - REQUIRE_MIGRATION_NEEDED(*realm, schema1, schema2); - } - - SECTION("remove property from existing object schema") { - auto realm = Realm::get_shared_realm(config); - Schema schema = { - {"object", { - {"col1", PropertyType::Int}, - {"col2", PropertyType::Int}, - }}, - }; - REQUIRE_MIGRATION_NEEDED(*realm, schema, remove_property(schema, "object", "col2")); - } - - SECTION("migratation which replaces a persisted property with a computed one") { - auto realm = Realm::get_shared_realm(config); - Schema schema1 = { - {"object", { - {"value", PropertyType::Int}, - {"link", PropertyType::Object|PropertyType::Nullable, "object2"}, - }}, - {"object2", { - {"value", PropertyType::Int}, - {"inverse", PropertyType::Object|PropertyType::Nullable, "object"}, - }}, - }; - Schema schema2 = remove_property(schema1, "object", "link"); - Property new_property{"link", PropertyType::LinkingObjects|PropertyType::Array, "object2", "inverse"}; - schema2.find("object")->computed_properties.emplace_back(new_property); - - REQUIRE_UPDATE_SUCCEEDS(*realm, schema1, 0); - REQUIRE_THROWS((*realm).update_schema(schema2)); - REQUIRE((*realm).schema() == schema1); - REQUIRE_NOTHROW((*realm).update_schema(schema2, 1, - [](SharedRealm, SharedRealm, Schema&) { /* empty but present migration handler */ })); - VERIFY_SCHEMA(*realm, false); - REQUIRE((*realm).schema() == schema2); - } - - SECTION("change property type") { - auto realm = Realm::get_shared_realm(config); - Schema schema = { - {"object", { - {"value", PropertyType::Int}, - }}, - }; - REQUIRE_MIGRATION_NEEDED(*realm, schema, set_type(schema, "object", "value", PropertyType::Float)); - } - - SECTION("make property nullable") { - auto realm = Realm::get_shared_realm(config); - - Schema schema = { - {"object", { - {"value", PropertyType::Int}, - }}, - }; - REQUIRE_MIGRATION_NEEDED(*realm, schema, set_optional(schema, "object", "value", true)); - } - - SECTION("make property required") { - auto realm = Realm::get_shared_realm(config); - - Schema schema = { - {"object", { - {"value", PropertyType::Int|PropertyType::Nullable}, - }}, - }; - REQUIRE_MIGRATION_NEEDED(*realm, schema, set_optional(schema, "object", "value", false)); - } - - SECTION("change link target") { - auto realm = Realm::get_shared_realm(config); - - Schema schema = { - {"target 1", { - {"value", PropertyType::Int}, - }}, - {"target 2", { - {"value", PropertyType::Int}, - }}, - {"origin", { - {"value", PropertyType::Object|PropertyType::Nullable, "target 1"}, - }}, - }; - REQUIRE_MIGRATION_NEEDED(*realm, schema, set_target(schema, "origin", "value", "target 2")); - } - - SECTION("add pk") { - auto realm = Realm::get_shared_realm(config); - - Schema schema = { - {"object", { - {"value", PropertyType::Int}, - }}, - }; - REQUIRE_MIGRATION_NEEDED(*realm, schema, set_primary_key(schema, "object", "value")); - } - - SECTION("remove pk") { - auto realm = Realm::get_shared_realm(config); - - Schema schema = { - {"object", { - {"value", PropertyType::Int, Property::IsPrimary{true}}, - }}, - }; - REQUIRE_MIGRATION_NEEDED(*realm, schema, set_primary_key(schema, "object", "")); - } - - SECTION("adding column and table in same migration doesn't add duplicate columns") { - auto realm = Realm::get_shared_realm(config); - - Schema schema1 = { - {"object", { - {"col1", PropertyType::Int}, - }}, - }; - auto schema2 = add_table(add_property(schema1, "object", {"col2", PropertyType::Int}), - {"object2", {{"value", PropertyType::Int}}}); - REQUIRE_UPDATE_SUCCEEDS(*realm, schema1, 0); - REQUIRE_UPDATE_SUCCEEDS(*realm, schema2, 1); - - auto& table = *get_table(realm, "object2"); - REQUIRE(table.get_column_count() == 1); - } - } - - SECTION("migration block invocations") { - SECTION("not called for initial creation of schema") { - Schema schema = { - {"object", { - {"value", PropertyType::Int}, - }}, - }; - auto realm = Realm::get_shared_realm(config); - realm->update_schema(schema, 5, [](SharedRealm, SharedRealm, Schema&) { REQUIRE(false); }); - } - - SECTION("not called when schema version is unchanged even if there are schema changes") { - Schema schema1 = { - {"object", { - {"value", PropertyType::Int}, - }}, - }; - Schema schema2 = add_table(schema1, {"second object", { - {"value", PropertyType::Int}, - }}); - auto realm = Realm::get_shared_realm(config); - realm->update_schema(schema1, 1); - realm->update_schema(schema2, 1, [](SharedRealm, SharedRealm, Schema&) { REQUIRE(false); }); - } - - SECTION("called when schema version is bumped even if there are no schema changes") { - Schema schema = { - {"object", { - {"value", PropertyType::Int}, - }}, - }; - auto realm = Realm::get_shared_realm(config); - realm->update_schema(schema); - bool called = false; - realm->update_schema(schema, 5, [&](SharedRealm, SharedRealm, Schema&) { called = true; }); - REQUIRE(called); - } - } - - SECTION("migration errors") { - SECTION("schema version cannot go down") { - auto realm = Realm::get_shared_realm(config); - realm->update_schema({}, 1); - realm->update_schema({}, 2); - REQUIRE_THROWS(realm->update_schema({}, 0)); - } - - SECTION("insert duplicate keys for existing PK during migration") { - Schema schema = { - {"object", { - {"value", PropertyType::Int, Property::IsPrimary{true}}, - }}, - }; - auto realm = Realm::get_shared_realm(config); - realm->update_schema(schema, 1); - REQUIRE_THROWS(realm->update_schema(schema, 2, [](SharedRealm, SharedRealm realm, Schema&) { - auto table = ObjectStore::table_for_object_type(realm->read_group(), "object"); - create_objects(*table, 2); - })); - } - - SECTION("add pk to existing table with duplicate keys") { - Schema schema = { - {"object", { - {"value", PropertyType::Int}, - }}, - }; - auto realm = Realm::get_shared_realm(config); - realm->update_schema(schema, 1); - - realm->begin_transaction(); - auto table = ObjectStore::table_for_object_type(realm->read_group(), "object"); - create_objects(*table, 2); - realm->commit_transaction(); - - schema = set_primary_key(schema, "object", "value"); - REQUIRE_THROWS(realm->update_schema(schema, 2, nullptr)); - } - - SECTION("throwing an exception from migration function rolls back all changes") { - Schema schema1 = { - {"object", { - {"value", PropertyType::Int}, - }}, - }; - Schema schema2 = add_property(schema1, "object", - {"value2", PropertyType::Int}); - auto realm = Realm::get_shared_realm(config); - realm->update_schema(schema1, 1); - - REQUIRE_THROWS(realm->update_schema(schema2, 2, [](SharedRealm, SharedRealm realm, Schema&) { - auto table = ObjectStore::table_for_object_type(realm->read_group(), "object"); - table->create_object(); - throw 5; - })); - - auto table = ObjectStore::table_for_object_type(realm->read_group(), "object"); - REQUIRE(table->size() == 0); - REQUIRE(realm->schema_version() == 1); - REQUIRE(realm->schema() == schema1); - } - } - - SECTION("valid migrations") { - SECTION("changing all columns does not lose row count") { - Schema schema = { - {"object", { - {"value", PropertyType::Int}, - }}, - }; - auto realm = Realm::get_shared_realm(config); - realm->update_schema(schema, 1); - - realm->begin_transaction(); - auto table = ObjectStore::table_for_object_type(realm->read_group(), "object"); - create_objects(*table, 10); - realm->commit_transaction(); - - schema = set_type(schema, "object", "value", PropertyType::Float); - realm->update_schema(schema, 2); - REQUIRE(table->size() == 10); - } - - SECTION("values for required properties are copied when converitng to nullable") { - Schema schema = { - {"object", { - {"value", PropertyType::Int}, - }}, - }; - auto realm = Realm::get_shared_realm(config); - realm->update_schema(schema, 1); - - realm->begin_transaction(); - auto table = ObjectStore::table_for_object_type(realm->read_group(), "object"); - auto key = table->get_column_key("value"); - create_objects(*table, 10); - for (int i = 0; i < 10; ++i) - table->get_object(i).set(key, i); - realm->commit_transaction(); - - realm->update_schema(set_optional(schema, "object", "value", true), 2); - key = table->get_column_key("value"); - for (int i = 0; i < 10; ++i) - REQUIRE(table->get_object(i).get<util::Optional<int64_t>>(key) == i); - } - - SECTION("values for nullable properties are discarded when converitng to required") { - Schema schema = { - {"object", { - {"value", PropertyType::Int|PropertyType::Nullable}, - }}, - }; - auto realm = Realm::get_shared_realm(config); - realm->update_schema(schema, 1); - - realm->begin_transaction(); - auto table = ObjectStore::table_for_object_type(realm->read_group(), "object"); - auto key = table->get_column_key("value"); - create_objects(*table, 10); - for (int i = 0; i < 10; ++i) - table->get_object(i).set(key, i); - realm->commit_transaction(); - - realm->update_schema(set_optional(schema, "object", "value", false), 2); - key = table->get_column_key("value"); - for (size_t i = 0; i < 10; ++i) - REQUIRE(table->get_object(i).get<int64_t>(key) == 0); - } - - SECTION("deleting table removed from the schema deletes it") { - Schema schema = { - {"object", { - {"value", PropertyType::Int|PropertyType::Nullable}, - }}, - }; - auto realm = Realm::get_shared_realm(config); - realm->update_schema(schema, 1); - - realm->update_schema({}, 2, [](SharedRealm, SharedRealm realm, Schema&) { - ObjectStore::delete_data_for_object(realm->read_group(), "object"); - }); - REQUIRE_FALSE(ObjectStore::table_for_object_type(realm->read_group(), "object")); - } - - SECTION("deleting table still in the schema recreates it with no rows") { - Schema schema = { - {"object", { - {"value", PropertyType::Int|PropertyType::Nullable}, - }}, - }; - auto realm = Realm::get_shared_realm(config); - realm->update_schema(schema, 1); - - realm->begin_transaction(); - ObjectStore::table_for_object_type(realm->read_group(), "object")->create_object(); - realm->commit_transaction(); - - realm->update_schema(schema, 2, [](SharedRealm, SharedRealm realm, Schema&) { - ObjectStore::delete_data_for_object(realm->read_group(), "object"); - }); - auto table = ObjectStore::table_for_object_type(realm->read_group(), "object"); - REQUIRE(table); - REQUIRE(table->size() == 0); - } - - SECTION("deleting table which doesn't exist does nothing") { - Schema schema = { - {"object", { - {"value", PropertyType::Int|PropertyType::Nullable}, - }}, - }; - auto realm = Realm::get_shared_realm(config); - realm->update_schema(schema, 1); - - REQUIRE_NOTHROW(realm->update_schema({}, 2, [](SharedRealm, SharedRealm realm, Schema&) { - ObjectStore::delete_data_for_object(realm->read_group(), "foo"); - })); - } - } - - SECTION("schema correctness during migration") { - InMemoryTestFile config; - config.schema_mode = SchemaMode::Automatic; - auto realm = Realm::get_shared_realm(config); - - Schema schema = { - {"object", { - {"pk", PropertyType::Int, Property::IsPrimary{true}}, - {"value", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}}, - {"optional", PropertyType::Int|PropertyType::Nullable}, - }}, - {"link origin", { - {"not a pk", PropertyType::Int}, - {"object", PropertyType::Object|PropertyType::Nullable, "object"}, - {"array", PropertyType::Array|PropertyType::Object, "object"}, - }} - }; - realm->update_schema(schema); - -#define VERIFY_SCHEMA_IN_MIGRATION(target_schema) do { \ - Schema new_schema = (target_schema); \ - realm->update_schema(new_schema, 1, [&](SharedRealm old_realm, SharedRealm new_realm, Schema&) { \ - REQUIRE(old_realm->schema_version() == 0); \ - REQUIRE(old_realm->schema() == schema); \ - REQUIRE(new_realm->schema_version() == 1); \ - REQUIRE(new_realm->schema() == new_schema); \ - VERIFY_SCHEMA(*old_realm, true); \ - VERIFY_SCHEMA(*new_realm, true); \ - }); \ - REQUIRE(realm->schema() == new_schema); \ - VERIFY_SCHEMA(*realm, false); \ -} while (false) - - SECTION("add new table") { - VERIFY_SCHEMA_IN_MIGRATION(add_table(schema, {"new table", { - {"value", PropertyType::Int}, - }})); - } - SECTION("add property to table") { - VERIFY_SCHEMA_IN_MIGRATION(add_property(schema, "object", {"new", PropertyType::Int})); - } - SECTION("remove property from table") { - VERIFY_SCHEMA_IN_MIGRATION(remove_property(schema, "object", "value")); - } - SECTION("remove multiple properties from table") { - VERIFY_SCHEMA_IN_MIGRATION(remove_property(remove_property(schema, "object", "value"), "object", "optional")); - } - SECTION("add primary key to table") { - VERIFY_SCHEMA_IN_MIGRATION(set_primary_key(schema, "link origin", "not a pk")); - } - SECTION("remove primary key from table") { - VERIFY_SCHEMA_IN_MIGRATION(set_primary_key(schema, "object", "")); - } - SECTION("change primary key") { - VERIFY_SCHEMA_IN_MIGRATION(set_primary_key(schema, "object", "value")); - } - SECTION("change property type") { - VERIFY_SCHEMA_IN_MIGRATION(set_type(schema, "object", "value", PropertyType::Date)); - } - SECTION("change link target") { - VERIFY_SCHEMA_IN_MIGRATION(set_target(schema, "link origin", "object", "link origin")); - } - SECTION("change linklist target") { - VERIFY_SCHEMA_IN_MIGRATION(set_target(schema, "link origin", "array", "link origin")); - } - SECTION("make property optional") { - VERIFY_SCHEMA_IN_MIGRATION(set_optional(schema, "object", "value", true)); - } - SECTION("make property required") { - VERIFY_SCHEMA_IN_MIGRATION(set_optional(schema, "object", "optional", false)); - } - SECTION("add index") { - VERIFY_SCHEMA_IN_MIGRATION(set_indexed(schema, "object", "optional", true)); - } - SECTION("remove index") { - VERIFY_SCHEMA_IN_MIGRATION(set_indexed(schema, "object", "value", false)); - } - SECTION("reorder properties") { - auto schema2 = schema; - auto& properties = schema2.find("object")->persisted_properties; - std::swap(properties[0], properties[1]); - VERIFY_SCHEMA_IN_MIGRATION(schema2); - } - } - - SECTION("object accessors inside migrations") { - using namespace std::string_literals; - - Schema schema{ - {"all types", { - {"pk", PropertyType::Int, Property::IsPrimary{true}}, - {"bool", PropertyType::Bool}, - {"int", PropertyType::Int}, - {"float", PropertyType::Float}, - {"double", PropertyType::Double}, - {"string", PropertyType::String}, - {"data", PropertyType::Data}, - {"date", PropertyType::Date}, - {"object", PropertyType::Object|PropertyType::Nullable, "link target"}, - {"array", PropertyType::Object|PropertyType::Array, "array target"}, - }}, - {"link target", { - {"value", PropertyType::Int}, - }, { - {"origin", PropertyType::LinkingObjects|PropertyType::Array, "all types", "object"}, - }}, - {"array target", { - {"value", PropertyType::Int}, - }}, - {"int pk", { - {"pk", PropertyType::Int, Property::IsPrimary{true}}, - {"value", PropertyType::Int}, - }}, - {"string pk", { - {"pk", PropertyType::String, Property::IsPrimary{true}}, - {"value", PropertyType::Int}, - }}, - }; - - InMemoryTestFile config; - config.schema_mode = SchemaMode::Automatic; - config.schema = schema; - auto realm = Realm::get_shared_realm(config); - - CppContext ctx(realm); - util::Any values = AnyDict{ - {"pk", INT64_C(1)}, - {"bool", true}, - {"int", INT64_C(5)}, - {"float", 2.2f}, - {"double", 3.3}, - {"string", "hello"s}, - {"data", "olleh"s}, - {"date", Timestamp(10, 20)}, - {"object", AnyDict{{"value", INT64_C(10)}}}, - {"array", AnyVector{AnyDict{{"value", INT64_C(20)}}}}, - }; - realm->begin_transaction(); - Object::create(ctx, realm, *realm->schema().find("all types"), values); - realm->commit_transaction(); - - SECTION("read values from old realm") { - Schema schema{ - {"all types", { - {"pk", PropertyType::Int, Property::IsPrimary{true}}, - }}, - }; - realm->update_schema(schema, 2, [](auto old_realm, auto new_realm, Schema&) { - CppContext ctx(old_realm); - Object obj = Object::get_for_primary_key(ctx, old_realm, "all types", - util::Any(INT64_C(1))); - REQUIRE(obj.is_valid()); - - REQUIRE(any_cast<bool>(obj.get_property_value<util::Any>(ctx, "bool")) == true); - REQUIRE(any_cast<int64_t>(obj.get_property_value<util::Any>(ctx, "int")) == 5); - REQUIRE(any_cast<float>(obj.get_property_value<util::Any>(ctx, "float")) == 2.2f); - REQUIRE(any_cast<double>(obj.get_property_value<util::Any>(ctx, "double")) == 3.3); - REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(ctx, "string")) == "hello"); - REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(ctx, "data")) == "olleh"); - REQUIRE(any_cast<Timestamp>(obj.get_property_value<util::Any>(ctx, "date")) == Timestamp(10, 20)); - - auto link = any_cast<Object>(obj.get_property_value<util::Any>(ctx, "object")); - REQUIRE(link.is_valid()); - REQUIRE(any_cast<int64_t>(link.get_property_value<util::Any>(ctx, "value")) == 10); - - auto list = any_cast<List>(obj.get_property_value<util::Any>(ctx, "array")); - REQUIRE(list.size() == 1); - - CppContext list_ctx(ctx, *obj.get_object_schema().property_for_name("array")); - link = any_cast<Object>(list.get(list_ctx, 0)); - REQUIRE(link.is_valid()); - REQUIRE(any_cast<int64_t>(link.get_property_value<util::Any>(list_ctx, "value")) == 20); - - CppContext ctx2(new_realm); - obj = Object::get_for_primary_key(ctx, new_realm, "all types", - util::Any(INT64_C(1))); - REQUIRE(obj.is_valid()); - REQUIRE_THROWS(obj.get_property_value<util::Any>(ctx, "bool")); - }); - } - - SECTION("cannot mutate old realm") { - realm->update_schema(schema, 2, [](auto old_realm, auto, Schema&) { - CppContext ctx(old_realm); - Object obj = Object::get_for_primary_key(ctx, old_realm, "all types", - util::Any(INT64_C(1))); - REQUIRE(obj.is_valid()); - REQUIRE_THROWS(obj.set_property_value(ctx, "bool", util::Any(false))); - REQUIRE_THROWS(old_realm->begin_transaction()); - }); - } - - SECTION("cannot read values for removed properties from new realm") { - Schema schema{ - {"all types", { - {"pk", PropertyType::Int, Property::IsPrimary{true}}, - }}, - }; - realm->update_schema(schema, 2, [](auto, auto new_realm, Schema&) { - CppContext ctx(new_realm); - Object obj = Object::get_for_primary_key(ctx, new_realm, "all types", - util::Any(INT64_C(1))); - REQUIRE(obj.is_valid()); - REQUIRE_THROWS(obj.get_property_value<util::Any>(ctx, "bool")); - REQUIRE_THROWS(obj.get_property_value<util::Any>(ctx, "object")); - REQUIRE_THROWS(obj.get_property_value<util::Any>(ctx, "array")); - }); - } - - SECTION("read values from new object") { - realm->update_schema(schema, 2, [](auto, auto new_realm, Schema&) { - CppContext ctx(new_realm); - Object obj = Object::get_for_primary_key(ctx, new_realm, "all types", - util::Any(INT64_C(1))); - REQUIRE(obj.is_valid()); - - - auto link = any_cast<Object>(obj.get_property_value<util::Any>(ctx, "object")); - REQUIRE(link.is_valid()); - REQUIRE(any_cast<int64_t>(link.get_property_value<util::Any>(ctx, "value")) == 10); - - auto list = any_cast<List>(obj.get_property_value<util::Any>(ctx, "array")); - REQUIRE(list.size() == 1); - - CppContext list_ctx(ctx, *obj.get_object_schema().property_for_name("array")); - link = any_cast<Object>(list.get(list_ctx, 0)); - REQUIRE(link.is_valid()); - REQUIRE(any_cast<int64_t>(link.get_property_value<util::Any>(list_ctx, "value")) == 20); - }); - } - - SECTION("read and write values in new object") { - realm->update_schema(schema, 2, [](auto, auto new_realm, Schema&) { - CppContext ctx(new_realm); - Object obj = Object::get_for_primary_key(ctx, new_realm, "all types", - util::Any(INT64_C(1))); - REQUIRE(obj.is_valid()); - - REQUIRE(any_cast<bool>(obj.get_property_value<util::Any>(ctx, "bool")) == true); - obj.set_property_value(ctx, "bool", util::Any(false)); - REQUIRE(any_cast<bool>(obj.get_property_value<util::Any>(ctx, "bool")) == false); - - REQUIRE(any_cast<int64_t>(obj.get_property_value<util::Any>(ctx, "int")) == 5); - obj.set_property_value(ctx, "int", util::Any(INT64_C(6))); - REQUIRE(any_cast<int64_t>(obj.get_property_value<util::Any>(ctx, "int")) == 6); - - REQUIRE(any_cast<float>(obj.get_property_value<util::Any>(ctx, "float")) == 2.2f); - obj.set_property_value(ctx, "float", util::Any(1.23f)); - REQUIRE(any_cast<float>(obj.get_property_value<util::Any>(ctx, "float")) == 1.23f); - - REQUIRE(any_cast<double>(obj.get_property_value<util::Any>(ctx, "double")) == 3.3); - obj.set_property_value(ctx, "double", util::Any(1.23)); - REQUIRE(any_cast<double>(obj.get_property_value<util::Any>(ctx, "double")) == 1.23); - - REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(ctx, "string")) == "hello"); - obj.set_property_value(ctx, "string", util::Any("abc"s)); - REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(ctx, "string")) == "abc"); - - REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(ctx, "data")) == "olleh"); - obj.set_property_value(ctx, "data", util::Any("abc"s)); - REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(ctx, "data")) == "abc"); - - REQUIRE(any_cast<Timestamp>(obj.get_property_value<util::Any>(ctx, "date")) == Timestamp(10, 20)); - obj.set_property_value(ctx, "date", util::Any(Timestamp(1, 2))); - REQUIRE(any_cast<Timestamp>(obj.get_property_value<util::Any>(ctx, "date")) == Timestamp(1, 2)); - - Object linked_obj(new_realm, "link target", 0); - Object new_obj(new_realm, get_table(new_realm, "link target")->create_object()); - - auto linking = any_cast<Results>(linked_obj.get_property_value<util::Any>(ctx, "origin")); - REQUIRE(linking.size() == 1); - - REQUIRE(any_cast<Object>(obj.get_property_value<util::Any>(ctx, "object")).obj().get_key() - == linked_obj.obj().get_key()); - obj.set_property_value(ctx, "object", util::Any(new_obj)); - REQUIRE(any_cast<Object>(obj.get_property_value<util::Any>(ctx, "object")).obj().get_key() - == new_obj.obj().get_key()); - - REQUIRE(linking.size() == 0); - }); - } - - SECTION("create object in new realm") { - realm->update_schema(schema, 2, [&values](auto, auto new_realm, Schema&) { - REQUIRE(new_realm->is_in_transaction()); - - CppContext ctx(new_realm); - any_cast<AnyDict&>(values)["pk"] = INT64_C(2); - Object obj = Object::create(ctx, new_realm, "all types", values); - - REQUIRE(get_table(new_realm, "all types")->size() == 2); - REQUIRE(get_table(new_realm, "link target")->size() == 2); - REQUIRE(get_table(new_realm, "array target")->size() == 2); - REQUIRE(any_cast<int64_t>(obj.get_property_value<util::Any>(ctx, "pk")) == 2); - }); - } - - SECTION("upsert in new realm") { - realm->update_schema(schema, 2, [&values](auto, auto new_realm, Schema&) { - REQUIRE(new_realm->is_in_transaction()); - CppContext ctx(new_realm); - any_cast<AnyDict&>(values)["bool"] = false; - Object obj = Object::create(ctx, new_realm, "all types", values, CreatePolicy::UpdateAll); - REQUIRE(get_table(new_realm, "all types")->size() == 1); - REQUIRE(get_table(new_realm, "link target")->size() == 2); - REQUIRE(get_table(new_realm, "array target")->size() == 2); - REQUIRE(any_cast<bool>(obj.get_property_value<util::Any>(ctx, "bool")) == false); - }); - } - - SECTION("upsert in new realm after modifying primary key") { - realm->update_schema(schema, 2, [&values](auto, auto new_realm, Schema&) { - get_table(new_realm, "all types")->set_primary_key_column(ColKey()); - REQUIRE(new_realm->is_in_transaction()); - CppContext ctx(new_realm); - any_cast<AnyDict&>(values)["bool"] = false; - Object obj = Object::create(ctx, new_realm, "all types", values, CreatePolicy::UpdateAll); - REQUIRE(get_table(new_realm, "all types")->size() == 1); - REQUIRE(get_table(new_realm, "link target")->size() == 2); - REQUIRE(get_table(new_realm, "array target")->size() == 2); - REQUIRE(any_cast<bool>(obj.get_property_value<util::Any>(ctx, "bool")) == false); - }); - } - - SECTION("change primary key property type") { - schema = set_type(schema, "all types", "pk", PropertyType::String); - realm->update_schema(schema, 2, [](auto, auto new_realm, auto&) { - Object obj(new_realm, "all types", 0); - - CppContext ctx(new_realm); - obj.set_property_value(ctx, "pk", util::Any("1"s)); - }); - } - - SECTION("set primary key to duplicate values in migration") { - auto bad_migration = [&](auto, auto new_realm, Schema&) { - // shoud be able to create a new object with the same PK - REQUIRE_NOTHROW(Object::create(ctx, new_realm, "all types", values)); - REQUIRE(get_table(new_realm, "all types")->size() == 2); - - // but it'll fail at the end - }; - REQUIRE_THROWS_AS(realm->update_schema(schema, 2, bad_migration), DuplicatePrimaryKeyValueException); - REQUIRE(get_table(realm, "all types")->size() == 1); - - auto good_migration = [&](auto, auto new_realm, Schema&) { - REQUIRE_NOTHROW(Object::create(ctx, new_realm, "all types", values)); - - // Change the old object's PK to elminate the duplication - Object old_obj(new_realm, "all types", 0); - CppContext ctx(new_realm); - old_obj.set_property_value(ctx, "pk", util::Any(INT64_C(5))); - }; - REQUIRE_NOTHROW(realm->update_schema(schema, 2, good_migration)); - REQUIRE(get_table(realm, "all types")->size() == 2); - } - - SECTION("modify existing int primary key values in migration") { - // Create several more objects to increase the chance of things - // actually breaking if we're doing invalid things - CppContext ctx(realm); - auto object_schema = realm->schema().find("all types"); - realm->begin_transaction(); - for (int i = 1; i < 10; ++i) { - any_cast<AnyDict&>(values)["pk"] = INT64_C(1) + i; - any_cast<AnyDict&>(values)["int"] = INT64_C(5) + i; - Object::create(ctx, realm, *object_schema, values); - } - realm->commit_transaction(); - - // Increase the PK of each object by one in a migration - realm->update_schema(schema, 2, [](auto, auto new_realm, Schema&) { - CppContext ctx(new_realm); - Results results(new_realm, get_table(new_realm, "all types")); - for (size_t i = 0, count = results.size(); i < count; ++i) { - Object obj(new_realm, results.get<Obj>(i)); - util::Any v = 1 + any_cast<int64_t>(obj.get_property_value<util::Any>(ctx, "pk")); - obj.set_property_value(ctx, "pk", v); - } - }); - - // Create a new object with the no-longer-used pk of 1 - realm->begin_transaction(); - any_cast<AnyDict&>(values)["pk"] = INT64_C(1); - any_cast<AnyDict&>(values)["int"] = INT64_C(4); - object_schema = realm->schema().find("all types"); - Object::create(ctx, realm, *object_schema, values); - realm->commit_transaction(); - - // Verify results - auto table = get_table(realm, "all types"); - REQUIRE(table->size() == 11); - REQUIRE(table->get_primary_key_column() == table->get_column_key("pk")); - for (int i = 0; i < 10; ++i) { - auto obj = table->get_object(i); - REQUIRE(obj.get<int64_t>("pk") == i + 2); - REQUIRE(obj.get<int64_t>("int") == i + 5); - } - auto obj = table->get_object(10); - REQUIRE(obj.get<int64_t>("pk") == 1); - REQUIRE(obj.get<int64_t>("int") == 4); - } - - SECTION("modify existing string primary key values in migration") { - // Create several objects to increase the chance of things - // actually breaking if we're doing invalid things - CppContext ctx(realm); - auto object_schema = realm->schema().find("string pk"); - realm->begin_transaction(); - for (int64_t i = 0; i < 10; ++i) { - util::Any values = AnyDict{ - {"pk", util::to_string(i)}, - {"value", i + 1}, - }; - Object::create(ctx, realm, *object_schema, values); - } - realm->commit_transaction(); - - // Increase the PK of each object by one in a migration - realm->update_schema(schema, 2, [](auto, auto new_realm, Schema&) { - CppContext ctx(new_realm); - Results results(new_realm, get_table(new_realm, "string pk")); - for (size_t i = 0, count = results.size(); i < count; ++i) { - Object obj(new_realm, results.get<Obj>(i)); - util::Any v = util::to_string(any_cast<int64_t>(obj.get_property_value<util::Any>(ctx, "value"))); - obj.set_property_value(ctx, "pk", v); - } - }); - - // Create a new object with the no-longer-used pk of 0 - realm->begin_transaction(); - util::Any values = AnyDict{ - {"pk", "0"s}, - {"value", INT64_C(0)}, - }; - object_schema = realm->schema().find("string pk"); - Object::create(ctx, realm, *object_schema, values); - realm->commit_transaction(); - - // Verify results - auto table = get_table(realm, "string pk"); - REQUIRE(table->size() == 11); - REQUIRE(table->get_primary_key_column() == table->get_column_key("pk")); - for (auto& obj : *table) { - REQUIRE(util::to_string(obj.get<int64_t>("value")).c_str() == obj.get<StringData>("pk")); - } - } - - SECTION("create and modify int primary key inside migration") { - SECTION("with index") { - realm->begin_transaction(); - auto table = get_table(realm, "int pk"); - table->add_search_index(table->get_column_key("pk")); - realm->commit_transaction(); - } - SECTION("no index") { - } - - realm->update_schema(schema, 2, [](auto, auto new_realm, Schema&) { - CppContext ctx(new_realm); - for (int64_t i = 0; i < 10; ++i) { - auto obj = Object::create(ctx, new_realm, *new_realm->schema().find("int pk"), - util::Any(AnyDict{ - {"pk", INT64_C(0)}, - {"value", i} - })); - obj.set_property_value(ctx, "pk", util::Any(i)); - } - }); - - auto table = get_table(realm, "int pk"); - REQUIRE(table->size() == 10); - REQUIRE(table->get_primary_key_column() == table->get_column_key("pk")); - for (int i = 0; i < 10; ++i) { - auto obj = table->get_object(i); - REQUIRE(obj.get<int64_t>("pk") == i); - REQUIRE(obj.get<int64_t>("value") == i); - } - } - - SECTION("create and modify string primary key inside migration") { - SECTION("with index") { - realm->begin_transaction(); - auto table = get_table(realm, "string pk"); - table->add_search_index(table->get_column_key("pk")); - realm->commit_transaction(); - } - SECTION("no index") { - } - - realm->update_schema(schema, 2, [](auto, auto new_realm, Schema&) { - CppContext ctx(new_realm); - for (int64_t i = 0; i < 10; ++i) { - auto obj = Object::create(ctx, new_realm, *new_realm->schema().find("string pk"), - util::Any(AnyDict{ - {"pk", ""s}, - {"value", i} - })); - obj.set_property_value(ctx, "pk", util::Any(util::to_string(i))); - } - }); - - auto table = get_table(realm, "string pk"); - REQUIRE(table->size() == 10); - REQUIRE(table->get_primary_key_column() == table->get_column_key("pk")); - for (auto& obj : *table) - REQUIRE(obj.get<StringData>("pk") == util::to_string(obj.get<int64_t>("value")).c_str()); - } - - SECTION("create object after adding primary key") { - schema = set_primary_key(schema, "all types", ""); - realm->update_schema(schema, 2); - schema = set_primary_key(schema, "all types", "pk"); - REQUIRE_NOTHROW(realm->update_schema(schema, 3, [&](auto, auto new_realm, Schema&) { - CppContext ctx(new_realm); - any_cast<AnyDict&>(values)["pk"] = INT64_C(2); - Object::create(ctx, realm, "all types", values); - })); - } - } - - SECTION("property renaming") { - InMemoryTestFile config; - config.schema_mode = SchemaMode::Automatic; - auto realm = Realm::get_shared_realm(config); - - struct Rename { - StringData object_type; - StringData old_name; - StringData new_name; - }; - - auto apply_renames = [&](std::initializer_list<Rename> renames) -> Realm::MigrationFunction { - return [=](SharedRealm, SharedRealm realm, Schema& schema) { - for (auto rename : renames) { - ObjectStore::rename_property(realm->read_group(), schema, - rename.object_type, rename.old_name, rename.new_name); - } - }; - }; - -#define FAILED_RENAME(old_schema, new_schema, error, ...) do { \ - realm->update_schema(old_schema, 1); \ - REQUIRE_THROWS_WITH(realm->update_schema(new_schema, 2, apply_renames({__VA_ARGS__})), error); \ -} while (false) - - Schema schema = { - {"object", { - {"value", PropertyType::Int}, - }}, - }; - - SECTION("table does not exist in old schema") { - auto schema2 = add_table(schema, {"object 2", { - {"value 2", PropertyType::Int}, - }}); - FAILED_RENAME(schema, schema2, - "Cannot rename property 'object 2.value' because it does not exist.", - {"object 2", "value", "value 2"}); - } - - SECTION("table does not exist in new schema") { - FAILED_RENAME(schema, {}, - "Cannot rename properties for type 'object' because it has been removed from the Realm.", - {"object", "value", "value 2"}); - } - - SECTION("property does not exist in old schema") { - auto schema2 = add_property(schema, "object", {"new", PropertyType::Int}); - FAILED_RENAME(schema, schema2, - "Cannot rename property 'object.nonexistent' because it does not exist.", - {"object", "nonexistent", "new"}); - } - - auto rename_value = [](Schema schema) { - schema.find("object")->property_for_name("value")->name = "new"; - return schema; - }; - - SECTION("property does not exist in new schema") { - FAILED_RENAME(schema, rename_value(schema), - "Renamed property 'object.nonexistent' does not exist.", - {"object", "value", "nonexistent"}); - } - - SECTION("source propety still exists in the new schema") { - auto schema2 = add_property(schema, "object", - {"new", PropertyType::Int}); - FAILED_RENAME(schema, schema2, - "Cannot rename property 'object.value' to 'new' because the source property still exists.", - {"object", "value", "new"}); - } - - SECTION("different type") { - auto schema2 = rename_value(set_type(schema, "object", "value", PropertyType::Date)); - FAILED_RENAME(schema, schema2, - "Cannot rename property 'object.value' to 'new' because it would change from type 'int' to 'date'.", - {"object", "value", "new"}); - } - - SECTION("different link targets") { - Schema schema = { - {"target", { - {"value", PropertyType::Int}, - }}, - {"origin", { - {"link", PropertyType::Object|PropertyType::Nullable, "target"}, - }}, - }; - auto schema2 = set_target(schema, "origin", "link", "origin"); - schema2.find("origin")->property_for_name("link")->name = "new"; - FAILED_RENAME(schema, schema2, - "Cannot rename property 'origin.link' to 'new' because it would change from type '<target>' to '<origin>'.", - {"origin", "link", "new"}); - } - - SECTION("different linklist targets") { - Schema schema = { - {"target", { - {"value", PropertyType::Int}, - }}, - {"origin", { - {"link", PropertyType::Array|PropertyType::Object, "target"}, - }}, - }; - auto schema2 = set_target(schema, "origin", "link", "origin"); - schema2.find("origin")->property_for_name("link")->name = "new"; - FAILED_RENAME(schema, schema2, - "Cannot rename property 'origin.link' to 'new' because it would change from type 'array<target>' to 'array<origin>'.", - {"origin", "link", "new"}); - } - - SECTION("make required") { - schema = set_optional(schema, "object", "value", true); - auto schema2 = rename_value(set_optional(schema, "object", "value", false)); - FAILED_RENAME(schema, schema2, - "Cannot rename property 'object.value' to 'new' because it would change from optional to required.", - {"object", "value", "new"}); - } - - auto init = [&](Schema const& old_schema) { - realm->update_schema(old_schema, 1); - realm->begin_transaction(); - auto table = ObjectStore::table_for_object_type(realm->read_group(), "object"); - table->create_object().set_all(10); - realm->commit_transaction(); - }; - -#define SUCCESSFUL_RENAME(old_schema, new_schema, ...) do { \ - init(old_schema); \ - REQUIRE_NOTHROW(realm->update_schema(new_schema, 2, apply_renames({__VA_ARGS__}))); \ - REQUIRE(realm->schema() == new_schema); \ - VERIFY_SCHEMA(*realm, false); \ - auto table = ObjectStore::table_for_object_type(realm->read_group(), "object"); \ - auto key = table->get_column_keys()[0]; \ - if (table->get_column_attr(key).test(col_attr_Nullable)) \ - REQUIRE(table->begin()->get<util::Optional<int64_t>>(key) == 10); \ - else \ - REQUIRE(table->begin()->get<int64_t>(key) == 10); \ -} while (false) - - SECTION("basic valid rename") { - auto schema2 = rename_value(schema); - SUCCESSFUL_RENAME(schema, schema2, - {"object", "value", "new"}); - } - - SECTION("chained rename") { - auto schema2 = rename_value(schema); - SUCCESSFUL_RENAME(schema, schema2, - {"object", "value", "a"}, - {"object", "a", "b"}, - {"object", "b", "new"}); - } - - SECTION("old is pk, new is not") { - auto schema2 = rename_value(schema); - schema = set_primary_key(schema, "object", "value"); - SUCCESSFUL_RENAME(schema, schema2, {"object", "value", "new"}); - } - - SECTION("new is pk, old is not") { - auto schema2 = set_primary_key(rename_value(schema), "object", "new"); - SUCCESSFUL_RENAME(schema, schema2, {"object", "value", "new"}); - } - - SECTION("both are pk") { - schema = set_primary_key(schema, "object", "value"); - auto schema2 = set_primary_key(rename_value(schema), "object", "new"); - SUCCESSFUL_RENAME(schema, schema2, {"object", "value", "new"}); - } - - SECTION("make optional") { - auto schema2 = rename_value(set_optional(schema, "object", "value", true)); - SUCCESSFUL_RENAME(schema, schema2, - {"object", "value", "new"}); - } - - SECTION("add index") { - auto schema2 = rename_value(set_indexed(schema, "object", "value", true)); - SUCCESSFUL_RENAME(schema, schema2, {"object", "value", "new"}); - } - - SECTION("remove index") { - auto schema2 = rename_value(schema); - schema = set_indexed(schema, "object", "value", true); - SUCCESSFUL_RENAME(schema, schema2, {"object", "value", "new"}); - } - - SECTION("create object inside migration after renaming pk") { - schema = set_primary_key(schema, "object", "value"); - auto new_schema = set_primary_key(rename_value(schema), "object", "new"); - init(schema); - REQUIRE_NOTHROW(realm->update_schema(new_schema, 2, [](auto, auto realm, Schema& schema) { - ObjectStore::rename_property(realm->read_group(), schema, - "object", "value", "new"); - - CppContext ctx(realm); - util::Any values = AnyDict{{"new", INT64_C(11)}}; - Object::create(ctx, realm, "object", values); - })); - REQUIRE(realm->schema() == new_schema); - VERIFY_SCHEMA(*realm, false); - auto table = ObjectStore::table_for_object_type(realm->read_group(), "object"); - auto key = table->get_column_keys()[0]; - auto it = table->begin(); - REQUIRE(it->get<int64_t>(key) == 10); - REQUIRE((++it)->get<int64_t>(key) == 11); - } - } -} - -TEST_CASE("migration: Immutable") { - TestFile config; - - auto realm_with_schema = [&](Schema schema) { - { - auto realm = Realm::get_shared_realm(config); - realm->update_schema(std::move(schema)); - } - config.schema_mode = SchemaMode::Immutable; - return Realm::get_shared_realm(config); - }; - - SECTION("allowed schema mismatches") { - SECTION("index") { - auto realm = realm_with_schema({ - {"object", { - {"indexed", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}}, - {"unindexed", PropertyType::Int}, - }}, - }); - Schema schema = { - {"object", { - {"indexed", PropertyType::Int}, - {"unindexed", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}}, - }}, - }; - REQUIRE_NOTHROW(realm->update_schema(schema)); - REQUIRE(realm->schema() == schema); - } - - SECTION("extra tables") { - auto realm = realm_with_schema({ - {"object", { - {"value", PropertyType::Int}, - }}, - {"object 2", { - {"value", PropertyType::Int}, - }}, - }); - Schema schema = { - {"object", { - {"value", PropertyType::Int}, - }}, - }; - REQUIRE_NOTHROW(realm->update_schema(schema)); - } - - SECTION("missing tables") { - auto realm = realm_with_schema({ - {"object", { - {"value", PropertyType::Int}, - }}, - }); - Schema schema = { - {"object", { - {"value", PropertyType::Int}, - }}, - {"second object", { - {"value", PropertyType::Int}, - }}, - }; - REQUIRE_NOTHROW(realm->update_schema(schema)); - REQUIRE(realm->schema() == schema); - - auto object_schema = realm->schema().find("object"); - REQUIRE(object_schema->persisted_properties.size() == 1); - REQUIRE(object_schema->persisted_properties[0].column_key); - - object_schema = realm->schema().find("second object"); - REQUIRE(object_schema->persisted_properties.size() == 1); - REQUIRE(!object_schema->persisted_properties[0].column_key); - } - - SECTION("extra columns in table") { - auto realm = realm_with_schema({ - {"object", { - {"value", PropertyType::Int}, - {"value 2", PropertyType::Int}, - }}, - }); - Schema schema = { - {"object", { - {"value", PropertyType::Int}, - }}, - }; - REQUIRE_NOTHROW(realm->update_schema(schema)); - } - } - - SECTION("disallowed mismatches") { - SECTION("missing columns in table") { - auto realm = realm_with_schema({ - {"object", { - {"value", PropertyType::Int}, - }}, - }); - Schema schema = { - {"object", { - {"value", PropertyType::Int}, - {"value 2", PropertyType::Int}, - }}, - }; - REQUIRE_THROWS(realm->update_schema(schema)); - } - - SECTION("bump schema version") { - Schema schema = { - {"object", { - {"value", PropertyType::Int}, - }}, - }; - auto realm = realm_with_schema(schema); - REQUIRE_THROWS(realm->update_schema(schema, 1)); - } - } -} - -TEST_CASE("migration: ReadOnly") { - TestFile config; - - auto realm_with_schema = [&](Schema schema) { - { - auto realm = Realm::get_shared_realm(config); - realm->update_schema(std::move(schema)); - } - config.schema_mode = SchemaMode::ReadOnlyAlternative; - return Realm::get_shared_realm(config); - }; - - SECTION("allowed schema mismatches") { - SECTION("index") { - auto realm = realm_with_schema({ - {"object", { - {"indexed", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}}, - {"unindexed", PropertyType::Int}, - }}, - }); - Schema schema = { - {"object", { - {"indexed", PropertyType::Int}, - {"unindexed", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}}, - }}, - }; - REQUIRE_NOTHROW(realm->update_schema(schema)); - REQUIRE(realm->schema() == schema); - } - - SECTION("extra tables") { - auto realm = realm_with_schema({ - {"object", { - {"value", PropertyType::Int}, - }}, - {"object 2", { - {"value", PropertyType::Int}, - }}, - }); - Schema schema = { - {"object", { - {"value", PropertyType::Int}, - }}, - }; - REQUIRE_NOTHROW(realm->update_schema(schema)); - } - - SECTION("extra columns in table") { - auto realm = realm_with_schema({ - {"object", { - {"value", PropertyType::Int}, - {"value 2", PropertyType::Int}, - }}, - }); - Schema schema = { - {"object", { - {"value", PropertyType::Int}, - }}, - }; - REQUIRE_NOTHROW(realm->update_schema(schema)); - } - - SECTION("missing tables") { - auto realm = realm_with_schema({ - {"object", { - {"value", PropertyType::Int}, - }}, - }); - Schema schema = { - {"object", { - {"value", PropertyType::Int}, - }}, - {"second object", { - {"value", PropertyType::Int}, - }}, - }; - REQUIRE_NOTHROW(realm->update_schema(schema)); - } - - SECTION("bump schema version") { - Schema schema = { - {"object", { - {"value", PropertyType::Int}, - }}, - }; - auto realm = realm_with_schema(schema); - REQUIRE_NOTHROW(realm->update_schema(schema, 1)); - } - } - - SECTION("disallowed mismatches") { - - SECTION("missing columns in table") { - auto realm = realm_with_schema({ - {"object", { - {"value", PropertyType::Int}, - }}, - }); - Schema schema = { - {"object", { - {"value", PropertyType::Int}, - {"value 2", PropertyType::Int}, - }}, - }; - REQUIRE_THROWS(realm->update_schema(schema)); - } - } -} - -TEST_CASE("migration: ResetFile") { - TestFile config; - config.schema_mode = SchemaMode::ResetFile; - - Schema schema = { - {"object", { - {"value", PropertyType::Int}, - }}, - {"object 2", { - {"value", PropertyType::Int}, - }}, - }; - -// To verify that the file has actually be deleted and recreated, on -// non-Windows we need to hold an open file handle to the old file to force -// using a new inode, but on Windows we *can't* -#ifdef _WIN32 - auto get_fileid = [&] { - // this is wrong for non-ascii but it's what core does - std::wstring ws(config.path.begin(), config.path.end()); - HANDLE handle = CreateFile2(ws.c_str(), GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_EXISTING, - nullptr); - REQUIRE(handle != INVALID_HANDLE_VALUE); - auto close = util::make_scope_exit([=]() noexcept { CloseHandle(handle); }); - - BY_HANDLE_FILE_INFORMATION info{}; - REQUIRE(GetFileInformationByHandle(handle, &info)); - return (DWORDLONG)info.nFileIndexHigh + (DWORDLONG)info.nFileIndexLow; - }; -#else - auto get_fileid = [&] { - util::File::UniqueID id; - util::File::get_unique_id(config.path, id); - return id.inode; - }; - File holder(config.path, File::mode_Write); -#endif - - { - auto realm = Realm::get_shared_realm(config); - auto ino = get_fileid(); - realm->update_schema(schema); - REQUIRE(ino == get_fileid()); - realm->begin_transaction(); - ObjectStore::table_for_object_type(realm->read_group(), "object")->create_object(); - realm->commit_transaction(); - } - auto realm = Realm::get_shared_realm(config); - auto ino = get_fileid(); - - SECTION("file is reset when schema version increases") { - realm->update_schema(schema, 1); - REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->size() == 0); - REQUIRE(ino != get_fileid()); - } - - SECTION("file is reset when an existing table is modified") { - realm->update_schema(add_property(schema, "object", - {"value 2", PropertyType::Int})); - REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->size() == 0); - REQUIRE(ino != get_fileid()); - } - - SECTION("file is not reset when adding a new table") { - realm->update_schema(add_table(schema, {"object 3", { - {"value", PropertyType::Int}, - }})); - REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->size() == 1); - REQUIRE(realm->schema().size() == 3); - REQUIRE(ino == get_fileid()); - } - - SECTION("file is not reset when removing a table") { - realm->update_schema(remove_table(schema, "object 2")); - REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->size() == 1); - REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object 2")); - REQUIRE(realm->schema().size() == 1); - REQUIRE(ino == get_fileid()); - } - - SECTION("file is not reset when adding an index") { - realm->update_schema(set_indexed(schema, "object", "value", true)); - REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->size() == 1); - REQUIRE(ino == get_fileid()); - } - - SECTION("file is not reset when removing an index") { - realm->update_schema(set_indexed(schema, "object", "value", true)); - realm->update_schema(schema); - REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->size() == 1); - REQUIRE(ino == get_fileid()); - } -} - -TEST_CASE("migration: Additive") { - Schema schema = { - {"object", { - {"value", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}}, - {"value 2", PropertyType::Int|PropertyType::Nullable}, - }}, - }; - - TestFile config; - config.schema_mode = SchemaMode::Additive; - config.schema = schema; - auto realm = Realm::get_shared_realm(config); - realm->update_schema(schema); - - SECTION("can add new properties to existing tables") { - REQUIRE_NOTHROW(realm->update_schema(add_property(schema, "object", - {"value 3", PropertyType::Int}))); - REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->get_column_count() == 3); - } - - SECTION("can add new tables") { - REQUIRE_NOTHROW(realm->update_schema(add_table(schema, {"object 2", { - {"value", PropertyType::Int}, - }}))); - REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")); - REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object 2")); - } - - SECTION("indexes are updated when schema version is bumped") { - auto table = ObjectStore::table_for_object_type(realm->read_group(), "object"); - auto col_keys = table->get_column_keys(); - REQUIRE(table->has_search_index(col_keys[0])); - REQUIRE(!table->has_search_index(col_keys[1])); - - REQUIRE_NOTHROW(realm->update_schema(set_indexed(schema, "object", "value", false), 1)); - REQUIRE(!table->has_search_index(col_keys[0])); - - REQUIRE_NOTHROW(realm->update_schema(set_indexed(schema, "object", "value 2", true), 2)); - REQUIRE(table->has_search_index(col_keys[1])); - } - - SECTION("indexes are not updated when schema version is not bumped") { - auto table = ObjectStore::table_for_object_type(realm->read_group(), "object"); - auto col_keys = table->get_column_keys(); - REQUIRE(table->has_search_index(col_keys[0])); - REQUIRE(!table->has_search_index(col_keys[1])); - - REQUIRE_NOTHROW(realm->update_schema(set_indexed(schema, "object", "value", false))); - REQUIRE(table->has_search_index(col_keys[0])); - - REQUIRE_NOTHROW(realm->update_schema(set_indexed(schema, "object", "value 2", true))); - REQUIRE(!table->has_search_index(col_keys[1])); - } - - SECTION("can remove properties from existing tables, but column is not removed") { - auto table = ObjectStore::table_for_object_type(realm->read_group(), "object"); - REQUIRE_NOTHROW(realm->update_schema(remove_property(schema, "object", "value"))); - REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->get_column_count() == 2); - auto const& properties = realm->schema().find("object")->persisted_properties; - REQUIRE(properties.size() == 1); - auto col_keys = table->get_column_keys(); - REQUIRE(col_keys.size() == 2); - REQUIRE(properties[0].column_key == col_keys[1]); - } - - SECTION("cannot change existing property types") { - REQUIRE_THROWS(realm->update_schema(set_type(schema, "object", "value", PropertyType::Float))); - } - - SECTION("cannot change existing property nullability") { - REQUIRE_THROWS(realm->update_schema(set_optional(schema, "object", "value", true))); - REQUIRE_THROWS(realm->update_schema(set_optional(schema, "object", "value 2", false))); - } - - SECTION("cannot change existing link targets") { - REQUIRE_NOTHROW(realm->update_schema(add_table(schema, {"object 2", { - {"link", PropertyType::Object|PropertyType::Nullable, "object"}, - }}))); - REQUIRE_THROWS(realm->update_schema(set_target(realm->schema(), "object 2", "link", "object 2"))); - } - - SECTION("cannot change primary keys") { - REQUIRE_THROWS(realm->update_schema(set_primary_key(schema, "object", "value"))); - - REQUIRE_NOTHROW(realm->update_schema(add_table(schema, {"object 2", { - {"pk", PropertyType::Int, Property::IsPrimary{true}}, - }}))); - - REQUIRE_THROWS(realm->update_schema(set_primary_key(realm->schema(), "object 2", ""))); - } - - SECTION("schema version is allowed to go down") { - REQUIRE_NOTHROW(realm->update_schema(schema, 1)); - REQUIRE(realm->schema_version() == 1); - REQUIRE_NOTHROW(realm->update_schema(schema, 0)); - REQUIRE(realm->schema_version() == 1); - } - - SECTION("migration function is not used") { - REQUIRE_NOTHROW(realm->update_schema(schema, 1, - [&](SharedRealm, SharedRealm, Schema&) { REQUIRE(false); })); - } - - SECTION("add new columns from different SG") { - auto realm2 = Realm::get_shared_realm(config); - auto& group = realm2->read_group(); - realm2->begin_transaction(); - auto table = ObjectStore::table_for_object_type(group, "object"); - auto col_keys = table->get_column_keys(); - table->add_column(type_Int, "new column"); - realm2->commit_transaction(); - - REQUIRE_NOTHROW(realm->refresh()); - REQUIRE(realm->schema() == schema); - REQUIRE(realm->schema().find("object")->persisted_properties[0].column_key == col_keys[0]); - REQUIRE(realm->schema().find("object")->persisted_properties[1].column_key == col_keys[1]); - } - - SECTION("opening new Realms uses the correct schema after an external change") { - auto realm2 = Realm::get_shared_realm(config); - auto& group = realm2->read_group(); - realm2->begin_transaction(); - auto table = ObjectStore::table_for_object_type(group, "object"); - auto col_keys = table->get_column_keys(); - table->add_column(type_Double, "newcol"); - realm2->commit_transaction(); - - REQUIRE_NOTHROW(realm->refresh()); - REQUIRE(realm->schema() == schema); - REQUIRE(realm->schema().find("object")->persisted_properties[0].column_key == col_keys[0]); - REQUIRE(realm->schema().find("object")->persisted_properties[1].column_key == col_keys[1]); - - // Gets the schema from the RealmCoordinator - auto realm3 = Realm::get_shared_realm(config); - REQUIRE(realm->schema().find("object")->persisted_properties[0].column_key == col_keys[0]); - REQUIRE(realm->schema().find("object")->persisted_properties[1].column_key == col_keys[1]); - - // Close and re-open the file entirely so that the coordinator is recreated - realm.reset(); - realm2.reset(); - realm3.reset(); - - realm = Realm::get_shared_realm(config); - REQUIRE(realm->schema() == schema); - REQUIRE(realm->schema().find("object")->persisted_properties[0].column_key == col_keys[0]); - REQUIRE(realm->schema().find("object")->persisted_properties[1].column_key == col_keys[1]); - } - - SECTION("can have different subsets of columns in different Realm instances") { - auto config2 = config; - config2.schema = add_property(schema, "object", - {"value 3", PropertyType::Int}); - auto config3 = config; - config3.schema = remove_property(schema, "object", "value 2"); - - auto config4 = config; - config4.schema = util::none; - - auto realm2 = Realm::get_shared_realm(config2); - auto realm3 = Realm::get_shared_realm(config3); - REQUIRE(realm->schema().find("object")->persisted_properties.size() == 2); - REQUIRE(realm2->schema().find("object")->persisted_properties.size() == 3); - REQUIRE(realm3->schema().find("object")->persisted_properties.size() == 1); - - realm->refresh(); - realm2->refresh(); - REQUIRE(realm->schema().find("object")->persisted_properties.size() == 2); - REQUIRE(realm2->schema().find("object")->persisted_properties.size() == 3); - - // No schema specified; should see all of them - auto realm4 = Realm::get_shared_realm(config4); - REQUIRE(realm4->schema().find("object")->persisted_properties.size() == 3); - } - - SECTION("updating a schema to include already-present column") { - auto config2 = config; - config2.schema = add_property(schema, "object", - {"value 3", PropertyType::Int}); - auto realm2 = Realm::get_shared_realm(config2); - auto& properties2 = realm2->schema().find("object")->persisted_properties; - - REQUIRE_NOTHROW(realm->update_schema(*config2.schema)); - REQUIRE(realm->schema().find("object")->persisted_properties.size() == 3); - auto& properties = realm->schema().find("object")->persisted_properties; - REQUIRE(properties[0].column_key == properties2[0].column_key); - REQUIRE(properties[1].column_key == properties2[1].column_key); - REQUIRE(properties[2].column_key == properties2[2].column_key); - } - - SECTION("increasing schema version without modifying schema properly leaves the schema untouched") { - TestFile config1; - config1.schema = schema; - config1.schema_mode = SchemaMode::Additive; - config1.schema_version = 0; - - auto realm1 = Realm::get_shared_realm(config1); - REQUIRE(realm1->schema().size() == 1); - Schema schema1 = realm1->schema(); - realm1->close(); - - auto config2 = config1; - config2.schema_version = 1; - auto realm2 = Realm::get_shared_realm(config2); - REQUIRE(realm2->schema() == schema1); - } - - SECTION("invalid schema update leaves the schema untouched") { - auto config2 = config; - config2.schema = add_property(schema, "object", {"value 3", PropertyType::Int}); - auto realm2 = Realm::get_shared_realm(config2); - - REQUIRE_THROWS(realm->update_schema(add_property(schema, "object", {"value 3", PropertyType::Float}))); - REQUIRE(realm->schema().find("object")->persisted_properties.size() == 2); - } - - SECTION("update_schema() does not begin a write transaction when extra columns are present") { - realm->begin_transaction(); - - auto realm2 = Realm::get_shared_realm(config); - // will deadlock if it tries to start a write transaction - realm2->update_schema(remove_property(schema, "object", "value")); - } - - SECTION("update_schema() does not begin a write transaction when indexes are changed without bumping schema version") { - realm->begin_transaction(); - - auto realm2 = Realm::get_shared_realm(config); - // will deadlock if it tries to start a write transaction - realm->update_schema(set_indexed(schema, "object", "value 2", true)); - } - - SECTION("update_schema() does not begin a write transaction for invalid schema changes") { - realm->begin_transaction(); - - auto realm2 = Realm::get_shared_realm(config); - auto new_schema = add_property(remove_property(schema, "object", "value"), - "object", {"value", PropertyType::Float}); - // will deadlock if it tries to start a write transaction - REQUIRE_THROWS(realm2->update_schema(new_schema)); - } -} - -TEST_CASE("migration: Manual") { - TestFile config; - config.schema_mode = SchemaMode::Manual; - auto realm = Realm::get_shared_realm(config); - - Schema schema = { - {"object", { - {"pk", PropertyType::Int, Property::IsPrimary{true}}, - {"value", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}}, - {"optional", PropertyType::Int|PropertyType::Nullable}, - }}, - {"link origin", { - {"not a pk", PropertyType::Int}, - {"object", PropertyType::Object|PropertyType::Nullable, "object"}, - {"array", PropertyType::Array|PropertyType::Object, "object"}, - }} - }; - realm->update_schema(schema); - auto col_keys = realm->read_group().get_table("class_object")->get_column_keys(); - -#define REQUIRE_MIGRATION(schema, migration) do { \ - Schema new_schema = (schema); \ - REQUIRE_THROWS(realm->update_schema(new_schema)); \ - REQUIRE(realm->schema_version() == 0); \ - REQUIRE_THROWS(realm->update_schema(new_schema, 1, [](SharedRealm, SharedRealm, Schema&){})); \ - REQUIRE(realm->schema_version() == 0); \ - REQUIRE_NOTHROW(realm->update_schema(new_schema, 1, migration)); \ - REQUIRE(realm->schema_version() == 1); \ -} while (false) - - SECTION("add new table") { - REQUIRE_MIGRATION(add_table(schema, {"new table", { - {"value", PropertyType::Int}, - }}), [](SharedRealm, SharedRealm realm, Schema&) { - realm->read_group().add_table("class_new table")->add_column(type_Int, "value"); - }); - } - SECTION("add property to table") { - REQUIRE_MIGRATION(add_property(schema, "object", {"new", PropertyType::Int}), - [&](SharedRealm, SharedRealm realm, Schema&) { - get_table(realm, "object")->add_column(type_Int, "new"); - }); - } - SECTION("remove property from table") { - REQUIRE_MIGRATION(remove_property(schema, "object", "value"), - [&](SharedRealm, SharedRealm realm, Schema&) { - get_table(realm, "object")->remove_column(col_keys[1]); - }); - } - SECTION("add primary key to table") { - REQUIRE_MIGRATION(set_primary_key(schema, "link origin", "not a pk"), - [&](SharedRealm, SharedRealm realm, Schema&) { - ObjectStore::set_primary_key_for_object(realm->read_group(), "link origin", "not a pk"); - auto table = get_table(realm, "link origin"); - table->add_search_index(table->get_column_key("not a pk")); - }); - } - SECTION("remove primary key from table") { - REQUIRE_MIGRATION(set_primary_key(schema, "object", ""), - [&](SharedRealm, SharedRealm realm, Schema&) { - ObjectStore::set_primary_key_for_object(realm->read_group(), "object", ""); - get_table(realm, "object")->remove_search_index(col_keys[0]); - }); - } - SECTION("change primary key") { - REQUIRE_MIGRATION(set_primary_key(schema, "object", "value"), - [&](SharedRealm, SharedRealm realm, Schema&) { - ObjectStore::set_primary_key_for_object(realm->read_group(), "object", "value"); - auto table = get_table(realm, "object"); - table->remove_search_index(col_keys[0]); - table->add_search_index(col_keys[1]); - }); - } - SECTION("change property type") { - REQUIRE_MIGRATION(set_type(schema, "object", "value", PropertyType::Date), - [&](SharedRealm, SharedRealm realm, Schema&) { - auto table = get_table(realm, "object"); - table->remove_column(col_keys[1]); - auto col = table->add_column(type_Timestamp, "value"); - table->add_search_index(col); - }); - } - SECTION("change link target") { - REQUIRE_MIGRATION(set_target(schema, "link origin", "object", "link origin"), - [&](SharedRealm, SharedRealm realm, Schema&) { - auto table = get_table(realm, "link origin"); - table->remove_column(table->get_column_keys()[1]); - table->add_column_link(type_Link, "object", *table); - }); - } - SECTION("change linklist target") { - REQUIRE_MIGRATION(set_target(schema, "link origin", "array", "link origin"), - [&](SharedRealm, SharedRealm realm, Schema&) { - auto table = get_table(realm, "link origin"); - table->remove_column(table->get_column_keys()[2]); - table->add_column_link(type_LinkList, "array", *table); - }); - } - SECTION("make property optional") { - REQUIRE_MIGRATION(set_optional(schema, "object", "value", true), - [&](SharedRealm, SharedRealm realm, Schema&) { - auto table = get_table(realm, "object"); - table->remove_column(col_keys[1]); - auto col = table->add_column(type_Int, "value", true); - table->add_search_index(col); - }); - } - SECTION("make property required") { - REQUIRE_MIGRATION(set_optional(schema, "object", "optional", false), - [&](SharedRealm, SharedRealm realm, Schema&) { - auto table = get_table(realm, "object"); - table->remove_column(col_keys[2]); - table->add_column(type_Int, "optional", false); - }); - } - SECTION("add index") { - REQUIRE_MIGRATION(set_indexed(schema, "object", "optional", true), - [&](SharedRealm, SharedRealm realm, Schema&) { - get_table(realm, "object")->add_search_index(col_keys[2]); - }); - } - SECTION("remove index") { - REQUIRE_MIGRATION(set_indexed(schema, "object", "value", false), - [&](SharedRealm, SharedRealm realm, Schema&) { - get_table(realm, "object")->remove_search_index(col_keys[1]); - }); - } - SECTION("reorder properties") { - auto schema2 = schema; - auto& properties = schema2.find("object")->persisted_properties; - std::swap(properties[0], properties[1]); - REQUIRE_NOTHROW(realm->update_schema(schema2)); - } - - SECTION("cannot lower schema version") { - REQUIRE_NOTHROW(realm->update_schema(schema, 1, [](SharedRealm, SharedRealm, Schema&){})); - REQUIRE(realm->schema_version() == 1); - REQUIRE_THROWS(realm->update_schema(schema, 0, [](SharedRealm, SharedRealm, Schema&){})); - REQUIRE(realm->schema_version() == 1); - } - - SECTION("update_schema() does not begin a write transaction when schema version is unchanged") { - realm->begin_transaction(); - - auto realm2 = Realm::get_shared_realm(config); - // will deadlock if it tries to start a write transaction - REQUIRE_NOTHROW(realm2->update_schema(schema)); - REQUIRE_THROWS(realm2->update_schema(remove_property(schema, "object", "value"))); - } - - SECTION("null migration callback should throw SchemaMismatchException") { - Schema new_schema = remove_property(schema, "object", "value"); - REQUIRE_THROWS_AS(realm->update_schema(new_schema, 1, nullptr), SchemaMismatchException); - } -} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/CMakeLists.txt b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/CMakeLists.txt deleted file mode 100644 index a23d7c163..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/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/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/command_file.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/command_file.cpp deleted file mode 100644 index 0c8af161f..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/command_file.cpp +++ /dev/null @@ -1,246 +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 "command_file.hpp" - -#include "impl/realm_coordinator.hpp" -#include "shared_realm.hpp" - -#include <realm/link_view.hpp> -#include <realm/table.hpp> - -#include <istream> - -using namespace fuzzer; -using namespace realm; - -#if 0 -#define log(...) fprintf(stderr, __VA_ARGS__) -#else -#define log(...) -#endif - -template<typename T> -static T read_value(std::istream& input) -{ - T ret; - input >> ret; - return ret; -} - -template<typename... Args> -static auto make_reader(void (*fn)(RealmState&, Args...)) { - return [=](std::istream& input) { - return std::bind(fn, std::placeholders::_1, read_value<Args>(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<char, std::function<std::function<void (RealmState&)>(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<typename T> -static std::vector<T> read_int_list(std::istream& input_stream) -{ - std::vector<T> 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 const&) { - // not an error - } - catch (std::out_of_range const&) { - // not an error - } - } - log("\n"); - return ret; -} - -CommandFile::CommandFile(std::istream& input) -: initial_values(read_int_list<int64_t>(input)) -, initial_list_indices(read_int_list<size_t>(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/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/command_file.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/command_file.hpp deleted file mode 100644 index 7c7321454..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/command_file.hpp +++ /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 <realm/link_view_fwd.hpp> - -#include <iosfwd> -#include <functional> -#include <memory> -#include <vector> - -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<int64_t> modified; -}; - -struct CommandFile { - std::vector<int64_t> initial_values; - std::vector<size_t> initial_list_indices; - std::vector<std::function<void (RealmState&)>> commands; - - CommandFile(std::istream& input); - - void import(RealmState& state); - void run(RealmState& state); -}; -}
\ No newline at end of file diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/fuzz-sorted-linkview.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/fuzz-sorted-linkview.cpp deleted file mode 100644 index f54078534..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/fuzz-sorted-linkview.cpp +++ /dev/null @@ -1,21 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#define FUZZ_SORTED 1 -#define FUZZ_LINKVIEW 1 -#include "fuzzer.cpp" diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/fuzz-sorted-query.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/fuzz-sorted-query.cpp deleted file mode 100644 index 7a719d20a..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/fuzz-sorted-query.cpp +++ /dev/null @@ -1,21 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#define FUZZ_SORTED 1 -#define FUZZ_LINKVIEW 0 -#include "fuzzer.cpp" diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/fuzz-unsorted-linkview.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/fuzz-unsorted-linkview.cpp deleted file mode 100644 index a7f20380e..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/fuzz-unsorted-linkview.cpp +++ /dev/null @@ -1,21 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#define FUZZ_SORTED 0 -#define FUZZ_LINKVIEW 1 -#include "fuzzer.cpp" diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/fuzz-unsorted-query.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/fuzz-unsorted-query.cpp deleted file mode 100644 index 18dcb45ed..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/fuzz-unsorted-query.cpp +++ /dev/null @@ -1,21 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#define FUZZ_SORTED 0 -#define FUZZ_LINKVIEW 0 -#include "fuzzer.cpp" diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/fuzzer.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/fuzzer.cpp deleted file mode 100644 index 7bc9089ec..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/fuzzer.cpp +++ /dev/null @@ -1,308 +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 "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 <realm/commit_log.hpp> -#include <realm/disable_sync_to_disk.hpp> -#include <realm/group_shared.hpp> -#include <realm/link_view.hpp> - -#include <iostream> -#include <sstream> -#include <sys/types.h> -#include <sys/uio.h> -#include <unistd.h> - -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<int64_t> 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<int64_t> 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<int64_t> 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.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>(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/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/input-lv/0 b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/input-lv/0 deleted file mode 100644 index cb6a3bd99..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/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/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/input/0 b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/input/0 deleted file mode 100644 index 675ab1573..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/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/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/input/1 b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/input/1 deleted file mode 100644 index 1cbf4855b..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/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/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/object.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/object.cpp deleted file mode 100644 index 10de55f30..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/object.cpp +++ /dev/null @@ -1,1017 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 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 "catch2/catch.hpp" - -#include "util/event_loop.hpp" -#include "util/index_helpers.hpp" -#include "util/test_file.hpp" - -#include "feature_checks.hpp" -#include "collection_notifications.hpp" -#include "object_accessor.hpp" -#include "property.hpp" -#include "schema.hpp" - -#include "impl/realm_coordinator.hpp" -#include "impl/object_accessor_impl.hpp" - -#include <realm/group.hpp> -#include <realm/util/any.hpp> - -#include <cstdint> - -using namespace realm; - -namespace { -using AnyDict = std::map<std::string, util::Any>; -using AnyVec = std::vector<util::Any>; -template <class T> -std::vector<T> get_vector(std::initializer_list<T> list) -{ - return std::vector<T>(list); -} -} - -struct TestContext : CppContext { - std::map<std::string, AnyDict> defaults; - - using CppContext::CppContext; - TestContext(TestContext& parent, realm::Property const& prop) - : CppContext(parent, prop) - , defaults(parent.defaults) - { } - - util::Optional<util::Any> - default_value_for_property(ObjectSchema const& object, Property const& prop) - { - auto obj_it = defaults.find(object.name); - if (obj_it == defaults.end()) - return util::none; - auto prop_it = obj_it->second.find(prop.name); - if (prop_it == obj_it->second.end()) - return util::none; - return prop_it->second; - } - - void will_change(Object const&, Property const&) {} - void did_change() {} - std::string print(util::Any) { return "not implemented"; } - bool allow_missing(util::Any) { return false; } -}; - -TEST_CASE("object") { - using namespace std::string_literals; - _impl::RealmCoordinator::assert_no_open_realms(); - - InMemoryTestFile config; - config.automatic_change_notifications = false; - config.schema = Schema{ - {"table", { - {"value 1", PropertyType::Int}, - {"value 2", PropertyType::Int}, - }}, - {"all types", { - {"pk", PropertyType::Int, Property::IsPrimary{true}}, - {"bool", PropertyType::Bool}, - {"int", PropertyType::Int}, - {"float", PropertyType::Float}, - {"double", PropertyType::Double}, - {"string", PropertyType::String}, - {"data", PropertyType::Data}, - {"date", PropertyType::Date}, - {"object", PropertyType::Object|PropertyType::Nullable, "link target"}, - - {"bool array", PropertyType::Array|PropertyType::Bool}, - {"int array", PropertyType::Array|PropertyType::Int}, - {"float array", PropertyType::Array|PropertyType::Float}, - {"double array", PropertyType::Array|PropertyType::Double}, - {"string array", PropertyType::Array|PropertyType::String}, - {"data array", PropertyType::Array|PropertyType::Data}, - {"date array", PropertyType::Array|PropertyType::Date}, - {"object array", PropertyType::Array|PropertyType::Object, "array target"}, - }}, - {"all optional types", { - {"pk", PropertyType::Int|PropertyType::Nullable, Property::IsPrimary{true}}, - {"bool", PropertyType::Bool|PropertyType::Nullable}, - {"int", PropertyType::Int|PropertyType::Nullable}, - {"float", PropertyType::Float|PropertyType::Nullable}, - {"double", PropertyType::Double|PropertyType::Nullable}, - {"string", PropertyType::String|PropertyType::Nullable}, - {"data", PropertyType::Data|PropertyType::Nullable}, - {"date", PropertyType::Date|PropertyType::Nullable}, - - {"bool array", PropertyType::Array|PropertyType::Bool|PropertyType::Nullable}, - {"int array", PropertyType::Array|PropertyType::Int|PropertyType::Nullable}, - {"float array", PropertyType::Array|PropertyType::Float|PropertyType::Nullable}, - {"double array", PropertyType::Array|PropertyType::Double|PropertyType::Nullable}, - {"string array", PropertyType::Array|PropertyType::String|PropertyType::Nullable}, - {"data array", PropertyType::Array|PropertyType::Data|PropertyType::Nullable}, - {"date array", PropertyType::Array|PropertyType::Date|PropertyType::Nullable}, - }}, - {"link target", { - {"value", PropertyType::Int}, - }, { - {"origin", PropertyType::LinkingObjects|PropertyType::Array, "all types", "object"}, - }}, - {"array target", { - {"value", PropertyType::Int}, - }}, - {"pk after list", { - {"array 1", PropertyType::Array|PropertyType::Object, "array target"}, - {"int 1", PropertyType::Int}, - {"pk", PropertyType::Int, Property::IsPrimary{true}}, - {"int 2", PropertyType::Int}, - {"array 2", PropertyType::Array|PropertyType::Object, "array target"}, - }}, - {"nullable int pk", { - {"pk", PropertyType::Int|PropertyType::Nullable, Property::IsPrimary{true}}, - }}, - {"nullable string pk", { - {"pk", PropertyType::String|PropertyType::Nullable, Property::IsPrimary{true}}, - }}, - {"person", { - {"name", PropertyType::String, Property::IsPrimary{true}}, - {"age", PropertyType::Int}, - {"scores", PropertyType::Array|PropertyType::Int}, - {"assistant", PropertyType::Object|PropertyType::Nullable, "person"}, - {"team", PropertyType::Array|PropertyType::Object, "person"}, - }}, - }; - config.schema_version = 0; - auto r = Realm::get_shared_realm(config); - auto& coordinator = *_impl::RealmCoordinator::get_coordinator(config.path); - - SECTION("add_notification_callback()") { - auto table = r->read_group().get_table("class_table"); - auto col_keys = table->get_column_keys(); - ObjKeys object_keys({3, 4, 7, 9, 10, 21, 24, 34, 42, 50}); - r->begin_transaction(); - for (int i = 0; i < 10; ++i) - table->create_object(object_keys[i]).set_all(i); - r->commit_transaction(); - - auto r2 = coordinator.get_realm(); - - CollectionChangeSet change; - auto obj = *table->begin(); - Object object(r, obj); - - auto write = [&](auto&& f) { - r->begin_transaction(); - f(); - r->commit_transaction(); - - advance_and_notify(*r); - }; - - auto require_change = [&] { - auto token = object.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 = object.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { - REQUIRE(first); - first = false; - }); - advance_and_notify(*r); - return token; - }; - - SECTION("deleting the object sends a change notification") { - auto token = require_change(); - write([&] { obj.remove(); }); - REQUIRE_INDICES(change.deletions, 0); - } - - SECTION("clearing the table sends a change notification") { - auto token = require_change(); - write([&] { table->clear(); }); - REQUIRE_INDICES(change.deletions, 0); - } - - SECTION("clearing the table sends a change notification to the last object") { - obj = table->get_object(table->size() - 1); - object = Object(r, obj); - - auto token = require_change(); - write([&] { table->clear(); }); - REQUIRE_INDICES(change.deletions, 0); - } - - SECTION("modifying the object sends a change notification") { - auto token = require_change(); - - write([&] { obj.set(col_keys[0], 10); }); - REQUIRE_INDICES(change.modifications, 0); - REQUIRE(change.columns.size() == 1); - REQUIRE_INDICES(change.columns[col_keys[0].value], 0); - - write([&] { obj.set(col_keys[1], 10); }); - REQUIRE_INDICES(change.modifications, 0); - REQUIRE(change.columns.size() == 1); - REQUIRE_INDICES(change.columns[col_keys[1].value], 0); - } - - SECTION("modifying a different object") { - auto token = require_no_change(); - write([&] { table->get_object(1).set(col_keys[0], 10); }); - } - - SECTION("multiple write transactions") { - auto token = require_change(); - - auto r2row = r2->read_group().get_table("class_table")->get_object(0); - r2->begin_transaction(); - r2row.set(col_keys[0], 1); - r2->commit_transaction(); - r2->begin_transaction(); - r2row.set(col_keys[1], 2); - r2->commit_transaction(); - - advance_and_notify(*r); - REQUIRE(change.columns.size() == 2); - REQUIRE_INDICES(change.columns[col_keys[0].value], 0); - REQUIRE_INDICES(change.columns[col_keys[1].value], 0); - } - - SECTION("skipping a notification") { - auto token = require_no_change(); - write([&] { - obj.set(col_keys[0], 1); - token.suppress_next(); - }); - } - - SECTION("skipping only effects the current transaction even if no notification would occur anyway") { - auto token = require_change(); - - // would not produce a notification even if it wasn't skipped because no changes were made - write([&] { - token.suppress_next(); - }); - REQUIRE(change.empty()); - - // should now produce a notification - write([&] { obj.set(col_keys[0], 1); }); - REQUIRE_INDICES(change.modifications, 0); - } - - SECTION("add notification callback, remove it, then add another notification callback") { - { - auto token = object.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { - FAIL("This should never happen"); - }); - } - auto token = require_change(); - write([&] { obj.remove(); }); - REQUIRE_INDICES(change.deletions, 0); - } - - SECTION("observing deleted object throws") { - write([&] { - obj.remove(); - }); - REQUIRE_THROWS(require_change()); - } - } - - TestContext d(r); - auto create = [&](util::Any&& value, CreatePolicy policy = CreatePolicy::ForceCreate) { - r->begin_transaction(); - auto obj = Object::create(d, r, *r->schema().find("all types"), value, policy); - r->commit_transaction(); - return obj; - }; - auto create_sub = [&](util::Any&& value, CreatePolicy policy = CreatePolicy::ForceCreate) { - r->begin_transaction(); - auto obj = Object::create(d, r, *r->schema().find("link target"), value, policy); - r->commit_transaction(); - return obj; - }; - auto create_company = [&](util::Any&& value, CreatePolicy policy = CreatePolicy::ForceCreate) { - r->begin_transaction(); - auto obj = Object::create(d, r, *r->schema().find("person"), value, policy); - r->commit_transaction(); - return obj; - }; - - SECTION("create object") { - auto obj = create(AnyDict{ - {"pk", INT64_C(1)}, - {"bool", true}, - {"int", INT64_C(5)}, - {"float", 2.2f}, - {"double", 3.3}, - {"string", "hello"s}, - {"data", "olleh"s}, - {"date", Timestamp(10, 20)}, - {"object", AnyDict{{"value", INT64_C(10)}}}, - - {"bool array", AnyVec{true, false}}, - {"int array", AnyVec{INT64_C(5), INT64_C(6)}}, - {"float array", AnyVec{1.1f, 2.2f}}, - {"double array", AnyVec{3.3, 4.4}}, - {"string array", AnyVec{"a"s, "b"s, "c"s}}, - {"data array", AnyVec{"d"s, "e"s, "f"s}}, - {"date array", AnyVec{Timestamp(10, 20), Timestamp(30, 40)}}, - {"object array", AnyVec{AnyDict{{"value", INT64_C(20)}}}}, - }); - - auto row = obj.obj(); - auto link_target = *r->read_group().get_table("class_link target")->begin(); - auto table = row.get_table(); - auto target_table = link_target.get_table(); - auto array_target_table = r->read_group().get_table("class_array target"); - REQUIRE(row.get<Int>(table->get_column_key("pk")) == 1); - REQUIRE(row.get<Bool>(table->get_column_key("bool")) == true); - REQUIRE(row.get<Int>(table->get_column_key("int")) == 5); - REQUIRE(row.get<float>(table->get_column_key("float")) == 2.2f); - REQUIRE(row.get<double>(table->get_column_key("double")) == 3.3); - REQUIRE(row.get<String>(table->get_column_key("string")) == "hello"); - REQUIRE(row.get<Binary>(table->get_column_key("data")) == BinaryData("olleh", 5)); - REQUIRE(row.get<Timestamp>(table->get_column_key("date")) == Timestamp(10, 20)); - REQUIRE(row.get<ObjKey>(table->get_column_key("object")) == link_target.get_key()); - - REQUIRE(link_target.get<Int>(target_table->get_column_key("value")) == 10); - - auto check_array = [&](ColKey col, auto... values) { - auto vec = get_vector({values...}); - using U = typename decltype(vec)::value_type; - auto list = row.get_list<U>(col); - size_t i = 0; - for (const auto& value : vec) { - CAPTURE(i); - REQUIRE(i < list.size()); - REQUIRE(value == list.get(i)); - ++i; - } - }; - check_array(table->get_column_key("bool array"), true, false); - check_array(table->get_column_key("int array"), INT64_C(5), INT64_C(6)); - check_array(table->get_column_key("float array"), 1.1f, 2.2f); - check_array(table->get_column_key("double array"), 3.3, 4.4); - check_array(table->get_column_key("string array"), StringData("a"), StringData("b"), StringData("c")); - check_array(table->get_column_key("data array"), BinaryData("d", 1), BinaryData("e", 1), BinaryData("f", 1)); - check_array(table->get_column_key("date array"), Timestamp(10, 20), Timestamp(30, 40)); - - auto list = row.get_linklist_ptr(table->get_column_key("object array")); - REQUIRE(list->size() == 1); - REQUIRE(list->get_object(0).get<Int>(array_target_table->get_column_key("value")) == 20); - } - - SECTION("create uses defaults for missing values") { - d.defaults["all types"] = { - {"bool", true}, - {"int", INT64_C(5)}, - {"float", 2.2f}, - {"double", 3.3}, - {"string", "hello"s}, - {"data", "olleh"s}, - {"date", Timestamp(10, 20)}, - {"object", AnyDict{{"value", INT64_C(10)}}}, - - {"bool array", AnyVec{true, false}}, - {"int array", AnyVec{INT64_C(5), INT64_C(6)}}, - {"float array", AnyVec{1.1f, 2.2f}}, - {"double array", AnyVec{3.3, 4.4}}, - {"string array", AnyVec{"a"s, "b"s, "c"s}}, - {"data array", AnyVec{"d"s, "e"s, "f"s}}, - {"date array", AnyVec{}}, - {"object array", AnyVec{AnyDict{{"value", INT64_C(20)}}}}, - }; - - auto obj = create(AnyDict{ - {"pk", INT64_C(1)}, - {"float", 6.6f}, - }); - - auto row = obj.obj(); - auto table = row.get_table(); - REQUIRE(row.get<Int>(table->get_column_key("pk")) == 1); - REQUIRE(row.get<Bool>(table->get_column_key("bool")) == true); - REQUIRE(row.get<Int>(table->get_column_key("int")) == 5); - REQUIRE(row.get<float>(table->get_column_key("float")) == 6.6f); - REQUIRE(row.get<double>(table->get_column_key("double")) == 3.3); - REQUIRE(row.get<String>(table->get_column_key("string")) == "hello"); - REQUIRE(row.get<Binary>(table->get_column_key("data")) == BinaryData("olleh", 5)); - REQUIRE(row.get<Timestamp>(table->get_column_key("date")) == Timestamp(10, 20)); - - REQUIRE(row.get_listbase_ptr(table->get_column_key("bool array"))->size() == 2); - REQUIRE(row.get_listbase_ptr(table->get_column_key("int array"))->size() == 2); - REQUIRE(row.get_listbase_ptr(table->get_column_key("float array"))->size() == 2); - REQUIRE(row.get_listbase_ptr(table->get_column_key("double array"))->size() == 2); - REQUIRE(row.get_listbase_ptr(table->get_column_key("string array"))->size() == 3); - REQUIRE(row.get_listbase_ptr(table->get_column_key("data array"))->size() == 3); - REQUIRE(row.get_listbase_ptr(table->get_column_key("date array"))->size() == 0); - REQUIRE(row.get_listbase_ptr(table->get_column_key("object array"))->size() == 1); - } - - SECTION("create can use defaults for primary key") { - d.defaults["all types"] = { - {"pk", INT64_C(10)}, - }; - auto obj = create(AnyDict{ - {"bool", true}, - {"int", INT64_C(5)}, - {"float", 2.2f}, - {"double", 3.3}, - {"string", "hello"s}, - {"data", "olleh"s}, - {"date", Timestamp(10, 20)}, - {"object", AnyDict{{"value", INT64_C(10)}}}, - {"array", AnyVector{AnyDict{{"value", INT64_C(20)}}}}, - }); - - auto row = obj.obj(); - REQUIRE(row.get<Int>(row.get_table()->get_column_key("pk")) == 10); - } - - SECTION("create does not complain about missing values for nullable fields") { - r->begin_transaction(); - realm::Object obj; - REQUIRE_NOTHROW(obj = Object::create(d, r, *r->schema().find("all optional types"), util::Any(AnyDict{}))); - r->commit_transaction(); - - REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "pk").has_value()); - REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "bool").has_value()); - REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "int").has_value()); - REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "float").has_value()); - REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "double").has_value()); - REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "string").has_value()); - REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "data").has_value()); - REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "date").has_value()); - - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "bool array")).size() == 0); - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "int array")).size() == 0); - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "float array")).size() == 0); - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "double array")).size() == 0); - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "string array")).size() == 0); - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "data array")).size() == 0); - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "date array")).size() == 0); - } - - SECTION("create throws for missing values if there is no default") { - REQUIRE_THROWS(create(AnyDict{ - {"pk", INT64_C(1)}, - {"float", 6.6f}, - })); - } - - SECTION("create always sets the PK first") { - AnyDict value{ - {"array 1", AnyVector{AnyDict{{"value", INT64_C(1)}}}}, - {"array 2", AnyVector{AnyDict{{"value", INT64_C(2)}}}}, - {"int 1", INT64_C(0)}, - {"int 2", INT64_C(0)}, - {"pk", INT64_C(7)}, - }; - // Core will throw if the list is populated before the PK is set - r->begin_transaction(); - REQUIRE_NOTHROW(Object::create(d, r, *r->schema().find("pk after list"), util::Any(value))); - } - - SECTION("create with update") { - CollectionChangeSet change; - bool callback_called; - Object obj = create(AnyDict{ - {"pk", INT64_C(1)}, - {"bool", true}, - {"int", INT64_C(5)}, - {"float", 2.2f}, - {"double", 3.3}, - {"string", "hello"s}, - {"data", "olleh"s}, - {"date", Timestamp(10, 20)}, - {"object", AnyDict{{"value", INT64_C(10)}}}, - - {"bool array", AnyVec{true, false}}, - {"int array", AnyVec{INT64_C(5), INT64_C(6)}}, - {"float array", AnyVec{1.1f, 2.2f}}, - {"double array", AnyVec{3.3, 4.4}}, - {"string array", AnyVec{"a"s, "b"s, "c"s}}, - {"data array", AnyVec{"d"s, "e"s, "f"s}}, - {"date array", AnyVec{}}, - {"object array", AnyVec{AnyDict{{"value", INT64_C(20)}}}}, - }); - - auto token = obj.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) { - change = c; - callback_called = true; - }); - advance_and_notify(*r); - - create(AnyDict{ - {"pk", INT64_C(1)}, - {"int", INT64_C(6)}, - {"string", "a"s}, - }, CreatePolicy::UpdateAll); - - callback_called = false; - advance_and_notify(*r); - REQUIRE(callback_called); - REQUIRE_INDICES(change.modifications, 0); - - auto row = obj.obj(); - auto table = row.get_table(); - REQUIRE(row.get<Int>(table->get_column_key("pk")) == 1); - REQUIRE(row.get<Bool>(table->get_column_key("bool")) == true); - REQUIRE(row.get<Int>(table->get_column_key("int")) == 6); - REQUIRE(row.get<float>(table->get_column_key("float")) == 2.2f); - REQUIRE(row.get<double>(table->get_column_key("double")) == 3.3); - REQUIRE(row.get<String>(table->get_column_key("string")) == "a"); - REQUIRE(row.get<Binary>(table->get_column_key("data")) == BinaryData("olleh", 5)); - REQUIRE(row.get<Timestamp>(table->get_column_key("date")) == Timestamp(10, 20)); - } - - SECTION("create with update - only with diffs") { - CollectionChangeSet change; - bool callback_called; - AnyDict adam { - {"name", "Adam"s}, - {"age", INT64_C(32)}, - {"scores", AnyVec{INT64_C(1), INT64_C(2)}}, - }; - AnyDict brian { - {"name", "Brian"s}, - {"age", INT64_C(33)}, - }; - AnyDict charley { - {"name", "Charley"s}, - {"age", INT64_C(34)}, - {"team", AnyVec{adam, brian}} - }; - AnyDict donald { - {"name", "Donald"s}, - {"age", INT64_C(35)}, - }; - AnyDict eddie { - {"name", "Eddie"s}, - {"age", INT64_C(36)}, - {"assistant", donald}, - {"team", AnyVec{donald, charley}} - }; - Object obj = create_company(eddie, CreatePolicy::UpdateAll); - - auto table = r->read_group().get_table("class_person"); - REQUIRE(table->size() == 5); - Results result(r, table); - auto token = result.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) { - change = c; - callback_called = true; - }); - advance_and_notify(*r); - - // First update unconditionally - create_company(eddie, CreatePolicy::UpdateAll); - - callback_called = false; - advance_and_notify(*r); - REQUIRE(callback_called); - REQUIRE_INDICES(change.modifications, 0, 1, 2, 3, 4); - - // Now, only update where differences (there should not be any diffs - so no update) - create_company(eddie, CreatePolicy::UpdateModified); - - REQUIRE(table->size() == 5); - callback_called = false; - advance_and_notify(*r); - REQUIRE(!callback_called); - - // Now, only update sub-object) - donald["scores"] = AnyVec{INT64_C(3), INT64_C(4), INT64_C(5)}; - // Insert the new donald - eddie["assistant"] = donald; - create_company(eddie, CreatePolicy::UpdateModified); - - REQUIRE(table->size() == 5); - callback_called = false; - advance_and_notify(*r); - REQUIRE(callback_called); - REQUIRE_INDICES(change.modifications, 1); - - // Shorten list - donald["scores"] = AnyVec{INT64_C(3), INT64_C(4)}; - eddie["assistant"] = donald; - create_company(eddie, CreatePolicy::UpdateModified); - - REQUIRE(table->size() == 5); - callback_called = false; - advance_and_notify(*r); - REQUIRE(callback_called); - REQUIRE_INDICES(change.modifications, 1); - } - - SECTION("create with update - identical sub-object") { - Object sub_obj = create_sub(AnyDict{{"value", INT64_C(10)}}); - Object obj = create(AnyDict{ - {"pk", INT64_C(1)}, - {"bool", true}, - {"int", INT64_C(5)}, - {"float", 2.2f}, - {"double", 3.3}, - {"string", "hello"s}, - {"data", "olleh"s}, - {"date", Timestamp(10, 20)}, - {"object", sub_obj}, - }); - - auto obj_table = r->read_group().get_table("class_all types"); - Results result(r, obj_table); - bool callback_called; - bool results_callback_called; - bool sub_callback_called; - auto token1 = obj.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { - callback_called = true; - }); - auto token2 = result.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { - results_callback_called = true; - }); - auto token3 = sub_obj.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { - sub_callback_called = true; - }); - advance_and_notify(*r); - - auto table = r->read_group().get_table("class_link target"); - REQUIRE(table->size() == 1); - - create(AnyDict{ - {"pk", INT64_C(1)}, - {"bool", true}, - {"int", INT64_C(5)}, - {"float", 2.2f}, - {"double", 3.3}, - {"string", "hello"s}, - {"data", "olleh"s}, - {"date", Timestamp(10, 20)}, - {"object", AnyDict{{"value", INT64_C(10)}}}, - }, CreatePolicy::UpdateModified); - - REQUIRE(table->size() == 1); - callback_called = false; - results_callback_called = false; - sub_callback_called = false; - advance_and_notify(*r); - REQUIRE(!callback_called); - REQUIRE(!results_callback_called); - REQUIRE(!sub_callback_called); - - // Now change sub object - create(AnyDict{ - {"pk", INT64_C(1)}, - {"bool", true}, - {"int", INT64_C(5)}, - {"float", 2.2f}, - {"double", 3.3}, - {"string", "hello"s}, - {"data", "olleh"s}, - {"date", Timestamp(10, 20)}, - {"object", AnyDict{{"value", INT64_C(11)}}}, - }, CreatePolicy::UpdateModified); - - callback_called = false; - results_callback_called = false; - sub_callback_called = false; - advance_and_notify(*r); - REQUIRE(!callback_called); - REQUIRE(results_callback_called); - REQUIRE(sub_callback_called); - } - - SECTION("create with update - identical array of sub-objects") { - bool callback_called; - auto dict = AnyDict{ - {"pk", INT64_C(1)}, - {"bool", true}, - {"int", INT64_C(5)}, - {"float", 2.2f}, - {"double", 3.3}, - {"string", "hello"s}, - {"data", "olleh"s}, - {"date", Timestamp(10, 20)}, - {"object array", AnyVec{AnyDict{{"value", INT64_C(20)}}, AnyDict{{"value", INT64_C(21)}}}}, - }; - Object obj = create(dict); - - auto obj_table = r->read_group().get_table("class_all types"); - Results result(r, obj_table); - auto token1 = result.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { - callback_called = true; - }); - advance_and_notify(*r); - - create(dict, CreatePolicy::UpdateModified); - - callback_called = false; - advance_and_notify(*r); - REQUIRE(!callback_called); - - // Now change list - dict["object array"] = AnyVec{AnyDict{{"value", INT64_C(23)}}}; - create(dict, CreatePolicy::UpdateModified); - - callback_called = false; - advance_and_notify(*r); - REQUIRE(callback_called); - } - - for (auto policy : {CreatePolicy::UpdateAll, CreatePolicy::UpdateModified}) { - SECTION("set existing fields to null with update "s + (policy == CreatePolicy::UpdateModified ? "(diffed)" : "(all)")) { - AnyDict initial_values{ - {"pk", INT64_C(1)}, - {"bool", true}, - {"int", INT64_C(5)}, - {"float", 2.2f}, - {"double", 3.3}, - {"string", "hello"s}, - {"data", "olleh"s}, - {"date", Timestamp(10, 20)}, - - {"bool array", AnyVec{true, false}}, - {"int array", AnyVec{INT64_C(5), INT64_C(6)}}, - {"float array", AnyVec{1.1f, 2.2f}}, - {"double array", AnyVec{3.3, 4.4}}, - {"string array", AnyVec{"a"s, "b"s, "c"s}}, - {"data array", AnyVec{"d"s, "e"s, "f"s}}, - {"date array", AnyVec{}}, - {"object array", AnyVec{AnyDict{{"value", INT64_C(20)}}}}, - }; - r->begin_transaction(); - auto obj = Object::create(d, r, *r->schema().find("all optional types"), util::Any(initial_values)); - - // Missing fields in dictionary do not update anything - Object::create(d, r, *r->schema().find("all optional types"), - util::Any(AnyDict{{"pk", INT64_C(1)}}), policy); - - REQUIRE(any_cast<bool>(obj.get_property_value<util::Any>(d, "bool")) == true); - REQUIRE(any_cast<int64_t>(obj.get_property_value<util::Any>(d, "int")) == 5); - REQUIRE(any_cast<float>(obj.get_property_value<util::Any>(d, "float")) == 2.2f); - REQUIRE(any_cast<double>(obj.get_property_value<util::Any>(d, "double")) == 3.3); - REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(d, "string")) == "hello"); - REQUIRE(any_cast<Timestamp>(obj.get_property_value<util::Any>(d, "date")) == Timestamp(10, 20)); - - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "bool array")).get<util::Optional<bool>>(0) == true); - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "int array")).get<util::Optional<int64_t>>(0) == 5); - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "float array")).get<util::Optional<float>>(0) == 1.1f); - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "double array")).get<util::Optional<double>>(0) == 3.3); - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "string array")).get<StringData>(0) == "a"); - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "date array")).size() == 0); - - // Set all properties to null - AnyDict null_values{ - {"pk", INT64_C(1)}, - {"bool", util::Any()}, - {"int", util::Any()}, - {"float", util::Any()}, - {"double", util::Any()}, - {"string", util::Any()}, - {"data", util::Any()}, - {"date", util::Any()}, - - {"bool array", AnyVec{util::Any()}}, - {"int array", AnyVec{util::Any()}}, - {"float array", AnyVec{util::Any()}}, - {"double array", AnyVec{util::Any()}}, - {"string array", AnyVec{util::Any()}}, - {"data array", AnyVec{util::Any()}}, - {"date array", AnyVec{Timestamp()}}, - }; - Object::create(d, r, *r->schema().find("all optional types"), util::Any(null_values), policy); - - REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "bool").has_value()); - REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "int").has_value()); - REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "float").has_value()); - REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "double").has_value()); - REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "string").has_value()); - REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "data").has_value()); - REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "date").has_value()); - - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "bool array")).get<util::Optional<bool>>(0) == util::none); - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "int array")).get<util::Optional<int64_t>>(0) == util::none); - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "float array")).get<util::Optional<float>>(0) == util::none); - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "double array")).get<util::Optional<double>>(0) == util::none); - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "string array")).get<StringData>(0) == StringData()); - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "data array")).get<BinaryData>(0) == BinaryData()); - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "date array")).get<Timestamp>(0) == Timestamp()); - - // Set all properties back to non-null - Object::create(d, r, *r->schema().find("all optional types"), util::Any(initial_values), policy); - REQUIRE(any_cast<bool>(obj.get_property_value<util::Any>(d, "bool")) == true); - REQUIRE(any_cast<int64_t>(obj.get_property_value<util::Any>(d, "int")) == 5); - REQUIRE(any_cast<float>(obj.get_property_value<util::Any>(d, "float")) == 2.2f); - REQUIRE(any_cast<double>(obj.get_property_value<util::Any>(d, "double")) == 3.3); - REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(d, "string")) == "hello"); - REQUIRE(any_cast<Timestamp>(obj.get_property_value<util::Any>(d, "date")) == Timestamp(10, 20)); - - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "bool array")).get<util::Optional<bool>>(0) == true); - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "int array")).get<util::Optional<int64_t>>(0) == 5); - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "float array")).get<util::Optional<float>>(0) == 1.1f); - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "double array")).get<util::Optional<double>>(0) == 3.3); - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "string array")).get<StringData>(0) == "a"); - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "date array")).size() == 0); - } - } - - SECTION("create throws for duplicate pk if update is not specified") { - create(AnyDict{ - {"pk", INT64_C(1)}, - {"bool", true}, - {"int", INT64_C(5)}, - {"float", 2.2f}, - {"double", 3.3}, - {"string", "hello"s}, - {"data", "olleh"s}, - {"date", Timestamp(10, 20)}, - {"object", AnyDict{{"value", INT64_C(10)}}}, - {"array", AnyVector{AnyDict{{"value", INT64_C(20)}}}}, - }); - REQUIRE_THROWS(create(AnyDict{ - {"pk", INT64_C(1)}, - {"bool", true}, - {"int", INT64_C(5)}, - {"float", 2.2f}, - {"double", 3.3}, - {"string", "hello"s}, - {"data", "olleh"s}, - {"date", Timestamp(10, 20)}, - {"object", AnyDict{{"value", INT64_C(10)}}}, - {"array", AnyVector{AnyDict{{"value", INT64_C(20)}}}}, - })); - } - - SECTION("create with explicit null pk does not fall back to default") { - d.defaults["nullable int pk"] = { - {"pk", INT64_C(10)}, - }; - d.defaults["nullable string pk"] = { - {"pk", "value"s}, - }; - auto create = [&](util::Any&& value, StringData type) { - r->begin_transaction(); - auto obj = Object::create(d, r, *r->schema().find(type), value); - r->commit_transaction(); - return obj; - }; - - auto obj = create(AnyDict{{"pk", d.null_value()}}, "nullable int pk"); - auto col_pk_int = r->read_group().get_table("class_nullable int pk")->get_column_key("pk"); - auto col_pk_str = r->read_group().get_table("class_nullable string pk")->get_column_key("pk"); - REQUIRE(obj.obj().is_null(col_pk_int)); - obj = create(AnyDict{{"pk", d.null_value()}}, "nullable string pk"); - REQUIRE(obj.obj().is_null(col_pk_str)); - - obj = create(AnyDict{{}}, "nullable int pk"); - REQUIRE(obj.obj().get<util::Optional<Int>>(col_pk_int) == 10); - obj = create(AnyDict{{}}, "nullable string pk"); - REQUIRE(obj.obj().get<String>(col_pk_str) == "value"); - } - - SECTION("getters and setters") { - r->begin_transaction(); - - auto table = r->read_group().get_table("class_all types"); - table->create_object(); - Object obj(r, *r->schema().find("all types"), *table->begin()); - - auto link_table = r->read_group().get_table("class_link target"); - link_table->create_object(); - Object linkobj(r, *r->schema().find("link target"), *link_table->begin()); - - obj.set_property_value(d, "bool", util::Any(true)); - REQUIRE(any_cast<bool>(obj.get_property_value<util::Any>(d, "bool")) == true); - - obj.set_property_value(d, "int", util::Any(INT64_C(5))); - REQUIRE(any_cast<int64_t>(obj.get_property_value<util::Any>(d, "int")) == 5); - - obj.set_property_value(d, "float", util::Any(1.23f)); - REQUIRE(any_cast<float>(obj.get_property_value<util::Any>(d, "float")) == 1.23f); - - obj.set_property_value(d, "double", util::Any(1.23)); - REQUIRE(any_cast<double>(obj.get_property_value<util::Any>(d, "double")) == 1.23); - - obj.set_property_value(d, "string", util::Any("abc"s)); - REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(d, "string")) == "abc"); - - obj.set_property_value(d, "data", util::Any("abc"s)); - REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(d, "data")) == "abc"); - - obj.set_property_value(d, "date", util::Any(Timestamp(1, 2))); - REQUIRE(any_cast<Timestamp>(obj.get_property_value<util::Any>(d, "date")) == Timestamp(1, 2)); - - REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "object").has_value()); - obj.set_property_value(d, "object", util::Any(linkobj)); - REQUIRE(any_cast<Object>(obj.get_property_value<util::Any>(d, "object")).obj().get_key() == linkobj.obj().get_key()); - - auto linking = any_cast<Results>(linkobj.get_property_value<util::Any>(d, "origin")); - REQUIRE(linking.size() == 1); - - REQUIRE_THROWS(obj.set_property_value(d, "pk", util::Any(INT64_C(5)))); - REQUIRE_THROWS(obj.set_property_value(d, "not a property", util::Any(INT64_C(5)))); - - r->commit_transaction(); - - REQUIRE_THROWS(obj.get_property_value<util::Any>(d, "not a property")); - REQUIRE_THROWS(obj.set_property_value(d, "int", util::Any(INT64_C(5)))); - } - - SECTION("list property self-assign is a no-op") { - auto obj = create(AnyDict{ - {"pk", INT64_C(1)}, - {"bool", true}, - {"int", INT64_C(5)}, - {"float", 2.2f}, - {"double", 3.3}, - {"string", "hello"s}, - {"data", "olleh"s}, - {"date", Timestamp(10, 20)}, - - {"bool array", AnyVec{true, false}}, - {"object array", AnyVec{AnyDict{{"value", INT64_C(20)}}}}, - }); - - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "bool array")).size() == 2); - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "object array")).size() == 1); - - r->begin_transaction(); - obj.set_property_value(d, "bool array", obj.get_property_value<util::Any>(d, "bool array")); - obj.set_property_value(d, "object array", obj.get_property_value<util::Any>(d, "object array")); - r->commit_transaction(); - - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "bool array")).size() == 2); - REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "object array")).size() == 1); - } - -#if REALM_ENABLE_SYNC - if (!util::EventLoop::has_implementation()) - return; - - SyncServer server(false); - SyncTestFile config1(server, "shared"); - config1.schema = config.schema; - SyncTestFile config2(server, "shared"); - config2.schema = config.schema; - - SECTION("defaults do not override values explicitly passed to create()") { - AnyDict v1{ - {"pk", INT64_C(7)}, - {"array 1", AnyVector{AnyDict{{"value", INT64_C(1)}}}}, - {"array 2", AnyVector{AnyDict{{"value", INT64_C(2)}}}}, - }; - auto v2 = v1; - v1["int 1"] = INT64_C(1); - v2["int 2"] = INT64_C(2); - - auto r1 = Realm::get_shared_realm(config1); - auto r2 = Realm::get_shared_realm(config2); - - TestContext c1(r1); - TestContext c2(r2); - - c1.defaults["pk after list"] = { - {"int 1", INT64_C(10)}, - {"int 2", INT64_C(10)}, - }; - c2.defaults = c1.defaults; - - r1->begin_transaction(); - r2->begin_transaction(); - auto object1 = Object::create(c1, r1, *r1->schema().find("pk after list"), util::Any(v1)); - auto object2 = Object::create(c2, r2, *r2->schema().find("pk after list"), util::Any(v2)); - r2->commit_transaction(); - r1->commit_transaction(); - - server.start(); - util::EventLoop::main().run_until([&] { - return r1->read_group().get_table("class_array target")->size() == 4; - }); - - Obj obj = object1.obj(); - REQUIRE(obj.get<Int>("pk") == 7); // pk - REQUIRE(obj.get_linklist("array 1").size() == 2); - REQUIRE(obj.get<Int>("int 1") == 1); // non-default from r1 - REQUIRE(obj.get<Int>("int 2") == 2); // non-default from r2 - REQUIRE(obj.get_linklist("array 2").size() == 2); - - } -#endif -} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/object_store.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/object_store.cpp deleted file mode 100644 index 8a0fed935..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/object_store.cpp +++ /dev/null @@ -1,77 +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 "catch2/catch.hpp" - -#include "util/test_file.hpp" - -#include "object_schema.hpp" -#include "object_store.hpp" -#include "property.hpp" -#include "schema.hpp" - -#include <realm/string_data.hpp> -#include <realm/table.hpp> - -using namespace realm; - -TEST_CASE("ObjectStore: table_name_for_object_type()") { - SECTION("should work with strings that aren't null-terminated") { - auto input = StringData("good_no_bad", 4); - auto result = ObjectStore::table_name_for_object_type(input); - REQUIRE(result == "class_good"); - } -} - -TEST_CASE("ObjectStore:: property_for_column_index()") { - SECTION("Property should match the schema") { - Schema schema = { - {"object", { - {"int", PropertyType::Int}, - {"boolNullable", PropertyType::Bool | PropertyType::Nullable}, - {"stringPK", PropertyType::String, true}, - {"dateNullableIndexed", PropertyType::Date | PropertyType::Nullable, false, true}, - {"floatNullableArray", PropertyType::Float | PropertyType::Nullable | PropertyType::Array}, - {"doubleArray", PropertyType::Double | PropertyType::Array}, - {"object", PropertyType::Object | PropertyType::Nullable, "object"}, - {"objectArray", PropertyType::Object | PropertyType::Array, "object"}, - }} - }; - - TestFile config; - config.schema = schema; - config.schema_version = 1; - - auto realm = Realm::get_shared_realm(config); - ConstTableRef table = ObjectStore::table_for_object_type(realm->read_group(), "object"); - auto it = realm->schema().find("object"); - REQUIRE_FALSE(it == realm->schema().end()); - ObjectSchema object_schema = *it; - - auto all_columns = table->get_column_keys(); - for (auto col : all_columns) { - auto property = ObjectStore::property_for_column_index(table, col); - if (!property) { - FAIL(); - continue; - } - auto actual_property = *object_schema.property_for_name(property->name); - REQUIRE(property.value() == actual_property); - } - } -} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/primitive_list.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/primitive_list.cpp deleted file mode 100644 index bfec4ef09..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/primitive_list.cpp +++ /dev/null @@ -1,857 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 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 "catch2/catch.hpp" - -#include "util/event_loop.hpp" -#include "util/index_helpers.hpp" -#include "util/test_file.hpp" - -#include "binding_context.hpp" -#include "list.hpp" -#include "object.hpp" -#include "object_schema.hpp" -#include "property.hpp" -#include "results.hpp" -#include "schema.hpp" -#include "thread_safe_reference.hpp" - -#include "impl/realm_coordinator.hpp" -#include "impl/object_accessor_impl.hpp" - -#include <realm/db.hpp> -#include <realm/query_expression.hpp> -#include <realm/version.hpp> - -#include <numeric> - -using namespace realm; - - -template<PropertyType prop_type, typename T> -struct Base { - using Type = T; - using Wrapped = T; - using Boxed = T; - enum { is_optional = false }; - - static PropertyType property_type() { return prop_type; } - static util::Any to_any(T value) { return value; } - - template<typename Fn> - static auto unwrap(T value, Fn&& fn) { return fn(value); } - - static T min() { abort(); } - static T max() { abort(); } - static T sum() { abort(); } - static double average() { abort(); } - - static bool can_sum() { return std::is_arithmetic<T>::value; } - static bool can_average() { return std::is_arithmetic<T>::value; } - static bool can_minmax() { return std::is_arithmetic<T>::value; } -}; - -struct Int : Base<PropertyType::Int, int64_t> { - static std::vector<int64_t> values() { return {3, 1, 2}; } - static int64_t min() { return 1; } - static int64_t max() { return 3; } - static int64_t sum() { return 6; } - static double average() { return 2.0; } -}; - -struct Bool : Base<PropertyType::Bool, bool> { - static std::vector<bool> values() { return {true, false}; } - static bool can_sum() { return false; } - static bool can_average() { return false; } - static bool can_minmax() { return false; } -}; - -struct Float : Base<PropertyType::Float, float> { - static std::vector<float> values() { return {3.3f, 1.1f, 2.2f}; } - static float min() { return 1.1f; } - static float max() { return 3.3f; } - static auto sum() { return Approx(6.6f); } - static auto average() { return Approx(2.2f); } -}; - -struct Double : Base<PropertyType::Double, double> { - static std::vector<double> values() { return {3.3, 1.1, 2.2}; } - static double min() { return 1.1; } - static double max() { return 3.3; } - static auto sum() { return Approx(6.6); } - static auto average() { return Approx(2.2); } -}; - -struct String : Base<PropertyType::String, StringData> { - using Boxed = std::string; - static std::vector<StringData> values() { return {"c", "a", "b"}; } - static util::Any to_any(StringData value) { return value ? std::string(value) : util::Any(); } -}; - -struct Binary : Base<PropertyType::Data, BinaryData> { - using Boxed = std::string; - static util::Any to_any(BinaryData value) { return value ? std::string(value) : util::Any(); } - static std::vector<BinaryData> values() - { - return {BinaryData("c", 1), BinaryData("a", 1), BinaryData("b", 1)}; - } -}; - -struct Date : Base<PropertyType::Date, Timestamp> { - static std::vector<Timestamp> values() - { - return {Timestamp(3, 3), Timestamp(1, 1), Timestamp(2, 2)}; - } - static bool can_minmax() { return true; } - static Timestamp min() { return Timestamp(1, 1); } - static Timestamp max() { return Timestamp(3, 3); } -}; - -template<typename BaseT> -struct BoxedOptional : BaseT { - using Type = util::Optional<typename BaseT::Type>; - using Boxed = Type; - enum { is_optional = true }; - - static PropertyType property_type() { return BaseT::property_type()|PropertyType::Nullable; } - static std::vector<Type> values() - { - std::vector<Type> ret; - for (auto v : BaseT::values()) - ret.push_back(Type(v)); - ret.push_back(util::none); - return ret; - } - static auto unwrap(Type value) { return *value; } - static util::Any to_any(Type value) { return value ? util::Any(*value) : util::Any(); } - - template<typename Fn> - static auto unwrap(Type value, Fn&& fn) { return value ? fn(*value) : fn(null()); } -}; - -template<typename BaseT> -struct UnboxedOptional : BaseT { - enum { is_optional = true }; - static PropertyType property_type() { return BaseT::property_type()|PropertyType::Nullable; } - static auto values() -> decltype(BaseT::values()) - { - auto ret = BaseT::values(); - ret.push_back(typename BaseT::Type()); - return ret; - } -}; - -template<typename T> -T get(Mixed) { abort(); } - -template<> int64_t get(Mixed m) { return m.get_int(); } -template<> float get(Mixed m) { return m.get_type() == type_Float ? m.get_float() : static_cast<float>(m.get_double()); } -template<> double get(Mixed m) { return m.get_double(); } -template<> Timestamp get(Mixed m) { return m.get_timestamp(); } - -namespace realm { -template<typename T> -bool operator==(List const& list, std::vector<T> const& values) { - if (list.size() != values.size()) - return false; - for (size_t i = 0; i < values.size(); ++i) { - if (list.get<T>(i) != values[i]) - return false; - } - return true; -} - -template<typename T> -bool operator==(Results const& results, std::vector<T> const& values) { - // FIXME: this is only necessary because Results::size() and ::get() are not const - Results copy{results}; - if (copy.size() != values.size()) - return false; - for (size_t i = 0; i < values.size(); ++i) { - if (copy.get<T>(i) != values[i]) - return false; - } - return true; -} - -} - -struct StringifyingContext { - template<typename T> - std::string box(T value) - { - std::stringstream ss; - ss << value; - return ss.str(); - } - - std::string box(Obj obj) { return util::to_string(obj.get_key().value); } -}; - -namespace Catch { -template<> -struct StringMaker<List> { - static std::string convert(List const& list) - { - std::stringstream ss; - auto type = list.get_type(); - ss << string_for_property_type(type & ~PropertyType::Flags); - if (is_nullable(type)) - ss << "?"; - ss << "{"; - - StringifyingContext ctx; - for (size_t i = 0, count = list.size(); i < count; ++i) - ss << list.get(ctx, i) << ", "; - auto str = ss.str(); - str.pop_back(); - str.back() = '}'; - return str; - } -}; -template<> -struct StringMaker<Results> { - static std::string convert(Results const& r) - { - auto& results = const_cast<Results&>(r); - std::stringstream ss; - auto type = results.get_type(); - ss << string_for_property_type(type & ~PropertyType::Flags); - if (is_nullable(type)) - ss << "?"; - ss << "{"; - - StringifyingContext ctx; - for (size_t i = 0, count = results.size(); i < count; ++i) - ss << results.get(ctx, i) << ", "; - auto str = ss.str(); - str.pop_back(); - str.back() = '}'; - return str; - } -}; -template<> -struct StringMaker<util::None> { - static std::string convert(util::None) - { - return "[none]"; - } -}; -} // namespace Catch - -struct less { - template<class T, class U> - auto operator()(T&& a, U&& b) const noexcept { return a < b; } -}; -struct greater { - template<class T, class U> - auto operator()(T&& a, U&& b) const noexcept { return a > b; } -}; - -template<> -auto less::operator()<Timestamp&, Timestamp&>(Timestamp& a, Timestamp& b) const noexcept -{ - if (b.is_null()) - return false; - if (a.is_null()) - return true; - return a < b; -} - -template<> -auto greater::operator()<Timestamp&, Timestamp&>(Timestamp& a, Timestamp& b) const noexcept -{ - if (a.is_null()) - return false; - if (b.is_null()) - return true; - return a > b; -} - -TEMPLATE_TEST_CASE("primitive list", "[primitives]", ::Int, ::Bool, ::Float, ::Double, ::String, ::Binary, ::Date, - BoxedOptional<::Int>, BoxedOptional<::Bool>, BoxedOptional<::Float>, BoxedOptional<::Double>, - UnboxedOptional<::String>, UnboxedOptional<::Binary>, UnboxedOptional<::Date>) -{ - auto values = TestType::values(); - using T = typename TestType::Type; - using W = typename TestType::Wrapped; - using Boxed = typename TestType::Boxed; - - InMemoryTestFile config; - config.automatic_change_notifications = false; - config.schema = Schema{ - {"object", { - {"value", PropertyType::Array|TestType::property_type()} - }}, - }; - auto r = Realm::get_shared_realm(config); - auto r2 = Realm::get_shared_realm(config); - - auto table = r->read_group().get_table("class_object"); - auto table2 = r2->read_group().get_table("class_object"); - r->begin_transaction(); - Obj obj = table->create_object(); - ColKey col = table->get_column_key("value"); - - List list(r, obj, col); - auto results = list.as_results(); - CppContext ctx(r); - - SECTION("get_realm()") { - REQUIRE(list.get_realm() == r); - REQUIRE(results.get_realm() == r); - } -#if 0 - SECTION("get_query()") { - REQUIRE(list.get_query().count() == 0); - REQUIRE(results.get_query().count() == 0); - list.add(static_cast<T>(values[0])); - REQUIRE(list.get_query().count() == 1); - REQUIRE(results.get_query().count() == 1); - } -#endif - SECTION("get_origin_row_index()") { - REQUIRE(list.get_parent_object_key() == obj.get_key()); - table->create_object(); - REQUIRE(list.get_parent_object_key() == obj.get_key()); - } - - SECTION("get_type()") { - REQUIRE(list.get_type() == TestType::property_type()); - REQUIRE(results.get_type() == TestType::property_type()); - } - - SECTION("get_object_type()") { - REQUIRE(results.get_object_type() == StringData()); - } - - SECTION("is_valid()") { - REQUIRE(list.is_valid()); - REQUIRE(results.is_valid()); - - SECTION("invalidate") { - r->invalidate(); - REQUIRE_FALSE(list.is_valid()); - REQUIRE_FALSE(results.is_valid()); - } - - SECTION("close") { - r->close(); - REQUIRE_FALSE(list.is_valid()); - REQUIRE_FALSE(results.is_valid()); - } - - SECTION("delete row") { - obj.remove(); - REQUIRE_FALSE(list.is_valid()); - REQUIRE_FALSE(results.is_valid()); - } - - SECTION("rollback transaction creating list") { - r->cancel_transaction(); - REQUIRE_FALSE(list.is_valid()); - REQUIRE_FALSE(results.is_valid()); - } - } - - SECTION("verify_attached()") { - REQUIRE_NOTHROW(list.verify_attached()); - - SECTION("invalidate") { - r->invalidate(); - REQUIRE_THROWS(list.verify_attached()); - } - - SECTION("close") { - r->close(); - REQUIRE_THROWS(list.verify_attached()); - } - - SECTION("delete row") { - obj.remove(); - REQUIRE_THROWS(list.verify_attached()); - } - - SECTION("rollback transaction creating list") { - r->cancel_transaction(); - REQUIRE_THROWS(list.verify_attached()); - } - } - - SECTION("verify_in_transaction()") { - REQUIRE_NOTHROW(list.verify_in_transaction()); - - SECTION("invalidate") { - r->invalidate(); - REQUIRE_THROWS(list.verify_in_transaction()); - } - - SECTION("close") { - r->close(); - REQUIRE_THROWS(list.verify_in_transaction()); - } - - SECTION("delete row") { - obj.remove(); - REQUIRE_THROWS(list.verify_in_transaction()); - } - - SECTION("end write") { - r->commit_transaction(); - REQUIRE_THROWS(list.verify_in_transaction()); - } - } - - if (!list.is_valid() || !r->is_in_transaction()) - return; - - for (T value : values) - list.add(value); - - SECTION("move()") { - if (list.size() < 3) - return; - - list.move(1, 2); - std::swap(values[1], values[2]); - REQUIRE(list == values); - REQUIRE(results == values); - - list.move(2, 1); - std::swap(values[1], values[2]); - REQUIRE(list == values); - REQUIRE(results == values); - - list.move(0, 2); - std::rotate(values.begin(), values.begin() + 1, values.begin() + 3); - REQUIRE(list == values); - REQUIRE(results == values); - - list.move(2, 0); - std::rotate(values.begin(), values.begin() + 2, values.begin() + 3); - REQUIRE(list == values); - REQUIRE(results == values); - } - - SECTION("remove()") { - if (list.size() < 3) - return; - - list.remove(1); - values.erase(values.begin() + 1); - REQUIRE(list == values); - REQUIRE(results == values); - } - - SECTION("remove_all()") { - list.remove_all(); - REQUIRE(list.size() == 0); - REQUIRE(results.size() == 0); - } - - SECTION("swap()") { - if (list.size() < 3) - return; - - list.swap(0, 2); - std::swap(values[0], values[2]); - REQUIRE(list == values); - REQUIRE(results == values); - } - - SECTION("delete_all()") { - list.delete_all(); - REQUIRE(list.size() == 0); - REQUIRE(results.size() == 0); - } - - SECTION("clear()") { - results.clear(); - REQUIRE(list.size() == 0); - REQUIRE(results.size() == 0); - } - - SECTION("get()") { - for (size_t i = 0; i < values.size(); ++i) { - CAPTURE(i); - REQUIRE(list.get<T>(i) == values[i]); - REQUIRE(results.get<T>(i) == values[i]); - REQUIRE(any_cast<Boxed>(list.get(ctx, i)) == Boxed(values[i])); - REQUIRE(any_cast<Boxed>(results.get(ctx, i)) == Boxed(values[i])); - } - REQUIRE_THROWS(list.get<T>(values.size())); - REQUIRE_THROWS(results.get<T>(values.size())); - REQUIRE_THROWS(list.get(ctx, values.size())); - REQUIRE_THROWS(results.get(ctx, values.size())); - } - - SECTION("first()") { - REQUIRE(*results.first<T>() == values.front()); - REQUIRE(any_cast<Boxed>(*results.first(ctx)) == Boxed(values.front())); - list.remove_all(); - REQUIRE(results.first<T>() == util::none); - } - - SECTION("last()") { - REQUIRE(*results.last<T>() == values.back()); - list.remove_all(); - REQUIRE(results.last<T>() == util::none); - } - - SECTION("set()") { - for (size_t i = 0; i < values.size(); ++i) { - CAPTURE(i); - auto rev = values.size() - i - 1; - list.set(i, static_cast<T>(values[rev])); - REQUIRE(list.get<T>(i) == values[rev]); - REQUIRE(results.get<T>(i) == values[rev]); - } - for (size_t i = 0; i < values.size(); ++i) { - CAPTURE(i); - list.set(ctx, i, TestType::to_any(values[i])); - REQUIRE(list.get<T>(i) == values[i]); - REQUIRE(results.get<T>(i) == values[i]); - } - - REQUIRE_THROWS(list.set(list.size(), static_cast<T>(values[0]))); - } - - SECTION("find()") { - for (size_t i = 0; i < values.size(); ++i) { - CAPTURE(i); - REQUIRE(list.find<T>(values[i]) == i); - REQUIRE(results.index_of<T>(values[i]) == i); - - REQUIRE(list.find(ctx, TestType::to_any(values[i])) == i); - REQUIRE(results.index_of(ctx, TestType::to_any(values[i])) == i); -#if 0 - auto q = TestType::unwrap(values[i], [&] (auto v) { return table->get_subtable(0, 0)->column<W>(0) == v; }); - REQUIRE(list.find(Query(q)) == i); - REQUIRE(results.index_of(std::move(q)) == i); -#endif - } - - list.remove(0); - REQUIRE(list.find(static_cast<T>(values[0])) == npos); - REQUIRE(results.index_of(static_cast<T>(values[0])) == npos); - - REQUIRE(list.find(ctx, TestType::to_any(values[0])) == npos); - REQUIRE(results.index_of(ctx, TestType::to_any(values[0])) == npos); - } - SECTION("sorted index_of()") { - auto sorted = list.sort({{"self", true}}); - std::sort(begin(values), end(values), less()); - for (size_t i = 0; i < values.size(); ++i) { - CAPTURE(i); - REQUIRE(sorted.index_of<T>(values[i]) == i); - } - - sorted = list.sort({{"self", false}}); - std::sort(begin(values), end(values), greater()); - for (size_t i = 0; i < values.size(); ++i) { - CAPTURE(i); - REQUIRE(sorted.index_of<T>(values[i]) == i); - } - } - -#if 0 - SECTION("filtered index_of()") { - REQUIRE_THROWS(results.index_of(table->get(0))); - auto q = TestType::unwrap(values[0], [&] (auto v) { return table->get_subtable(0, 0)->column<W>(0) != v; }); - auto filtered = list.filter(std::move(q)); - for (size_t i = 1; i < values.size(); ++i) { - CAPTURE(i); - REQUIRE(filtered.index_of(static_cast<T>(values[i])) == i - 1); - } - } -#endif - SECTION("sort()") { - auto unsorted = list.sort(std::vector<std::pair<std::string, bool>>{}); - REQUIRE(unsorted == values); - - auto sorted = list.sort(SortDescriptor({{col}}, {true})); - auto sorted2 = list.sort({{"self", true}}); - std::sort(begin(values), end(values), less()); - REQUIRE(sorted == values); - REQUIRE(sorted2 == values); - - sorted = list.sort(SortDescriptor({{col}}, {false})); - sorted2 = list.sort({{"self", false}}); - std::sort(begin(values), end(values), greater()); - REQUIRE(sorted == values); - REQUIRE(sorted2 == values); - - auto execption_string = - util::format("Cannot sort on key path 'not self': arrays of '%1' can only be sorted on 'self'", - string_for_property_type(TestType::property_type() & ~PropertyType::Flags)); - REQUIRE_THROWS_WITH(list.sort({{"not self", true}}), execption_string); - REQUIRE_THROWS_WITH(list.sort({{"self", true}, {"self", false}}), - util::format("Cannot sort array of '%1' on more than one key path", - string_for_property_type(TestType::property_type() & ~PropertyType::Flags))); - } - - SECTION("distinct()") { - for (T value : values) - list.add(value); - auto values2 = values; - values2.insert(values2.end(), values.begin(), values.end()); - - auto undistinct = list.as_results().distinct(std::vector<std::string>{}); - REQUIRE(undistinct == values2); - - auto distinct = results.distinct(DistinctDescriptor({{col}})); - auto distinct2 = results.distinct({"self"}); - REQUIRE(distinct == values); - REQUIRE(distinct2 == values); - - REQUIRE_THROWS_WITH(results.distinct({{"not self"}}), - util::format("Cannot sort on key path 'not self': arrays of '%1' can only be sorted on 'self'", - string_for_property_type(TestType::property_type() & ~PropertyType::Flags))); - REQUIRE_THROWS_WITH(results.distinct({{"self"}, {"self"}}), - util::format("Cannot sort array of '%1' on more than one key path", - string_for_property_type(TestType::property_type() & ~PropertyType::Flags))); - } - -#if 0 - SECTION("filter()") { - T v = values.front(); - values.erase(values.begin()); - - auto q = TestType::unwrap(v, [&] (auto v) { return table->get_subtable(0, 0)->column<W>(0) != v; }); - Results filtered = list.filter(std::move(q)); - REQUIRE(filtered == values); - - q = TestType::unwrap(v, [&] (auto v) { return table->get_subtable(0, 0)->column<W>(0) == v; }); - filtered = list.filter(std::move(q)); - REQUIRE(filtered.size() == 1); - REQUIRE(*filtered.first<T>() == v); - } -#endif - - SECTION("min()") { - if (!TestType::can_minmax()) { - REQUIRE_THROWS(list.min()); - REQUIRE_THROWS(results.min()); - return; - } - - REQUIRE(get<W>(*list.min()) == TestType::min()); - REQUIRE(get<W>(*results.min()) == TestType::min()); - list.remove_all(); - REQUIRE(list.min() == util::none); - REQUIRE(results.min() == util::none); - } - - SECTION("max()") { - if (!TestType::can_minmax()) { - REQUIRE_THROWS(list.max()); - REQUIRE_THROWS(results.max()); - return; - } - - REQUIRE(get<W>(*list.max()) == TestType::max()); - REQUIRE(get<W>(*results.max()) == TestType::max()); - list.remove_all(); - REQUIRE(list.max() == util::none); - REQUIRE(results.max() == util::none); - } - - SECTION("sum()") { - if (!TestType::can_sum()) { - REQUIRE_THROWS(list.sum()); - return; - } - - REQUIRE(get<W>(list.sum()) == TestType::sum()); - REQUIRE(get<W>(*results.sum()) == TestType::sum()); - list.remove_all(); - REQUIRE(get<W>(list.sum()) == W{}); - REQUIRE(get<W>(*results.sum()) == W{}); - } - - SECTION("average()") { - if (!TestType::can_average()) { - REQUIRE_THROWS(list.average()); - return; - } - - REQUIRE(*list.average() == TestType::average()); - REQUIRE(*results.average() == TestType::average()); - list.remove_all(); - REQUIRE(list.average() == util::none); - REQUIRE(results.average() == util::none); - } - - SECTION("operator==()") { - Obj obj1 = table->create_object(); - REQUIRE(list == List(r, obj, col)); - REQUIRE_FALSE(list == List(r, obj1, col)); - } - - SECTION("hash") { - Obj obj1 = table->create_object(); - std::hash<List> h; - REQUIRE(h(list) == h(List(r, obj, col))); - REQUIRE_FALSE(h(list) == h(List(r, obj1, col))); - } - - SECTION("handover") { - r->commit_transaction(); - - auto list2 = ThreadSafeReference(list).resolve<List>(r); - REQUIRE(list == list2); - auto results2 = ThreadSafeReference(results).resolve<Results>(r); - REQUIRE(results2 == values); - } - - SECTION("notifications") { - r->commit_transaction(); - - auto sorted = results.sort({{"self", true}}); - - size_t calls = 0; - CollectionChangeSet change, rchange, srchange; - auto token = list.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) { - change = c; - ++calls; - }); - auto rtoken = results.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) { - rchange = c; - ++calls; - }); - auto srtoken = sorted.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) { - srchange = c; - ++calls; - }); - - SECTION("add value to list") { - // Remove the existing copy of this value so that the sorted list - // doesn't have dupes resulting in an unstable order - advance_and_notify(*r); - r->begin_transaction(); - list.remove(0); - r->commit_transaction(); - - advance_and_notify(*r); - r->begin_transaction(); - list.insert(0, static_cast<T>(values[0])); - r->commit_transaction(); - - advance_and_notify(*r); - REQUIRE_INDICES(change.insertions, 0); - REQUIRE_INDICES(rchange.insertions, 0); - // values[0] is max(), so it ends up at the end of the sorted list - REQUIRE_INDICES(srchange.insertions, values.size() - 1); - } - - SECTION("remove value from list") { - advance_and_notify(*r); - r->begin_transaction(); - list.remove(1); - r->commit_transaction(); - - advance_and_notify(*r); - REQUIRE_INDICES(change.deletions, 1); - REQUIRE_INDICES(rchange.deletions, 1); - // values[1] is min(), so it's index 0 for non-optional and 1 for - // optional (as nulls sort to the front) - REQUIRE_INDICES(srchange.deletions, TestType::is_optional); - } - - SECTION("clear list") { - advance_and_notify(*r); - - r->begin_transaction(); - list.remove_all(); - r->commit_transaction(); - advance_and_notify(*r); - REQUIRE(change.deletions.count() == values.size()); - REQUIRE(rchange.deletions.count() == values.size()); - REQUIRE(srchange.deletions.count() == values.size()); - } - - SECTION("delete containing row") { - advance_and_notify(*r); - REQUIRE(calls == 3); - - r->begin_transaction(); - obj.remove(); - r->commit_transaction(); - advance_and_notify(*r); - REQUIRE(calls == 6); - REQUIRE(change.deletions.count() == values.size()); - REQUIRE(rchange.deletions.count() == values.size()); - REQUIRE(srchange.deletions.count() == values.size()); - - r->begin_transaction(); - table->create_object(); - r->commit_transaction(); - advance_and_notify(*r); - REQUIRE(calls == 6); - } - - SECTION("deleting containing row before first run of notifier") { - r2->begin_transaction(); - table2->begin()->remove(); - r2->commit_transaction(); - advance_and_notify(*r); - REQUIRE(change.deletions.count() == values.size()); - } - } - -#if REALM_ENABLE_SYNC && REALM_HAVE_SYNC_STABLE_IDS - SECTION("sync compatibility") { - if (!util::EventLoop::has_implementation()) - return; - - SyncServer server; - SyncTestFile sync_config(server, "shared"); - sync_config.schema = config.schema; - sync_config.schema_version = 0; - - { - auto r = Realm::get_shared_realm(sync_config); - r->begin_transaction(); - - CppContext ctx(r); - auto obj = Object::create(ctx, r, *r->schema().find("object"), util::Any(AnyDict{})); - auto list = any_cast<List>(obj.get_property_value<util::Any>(ctx, "value")); - list.add(static_cast<T>(values[0])); - - r->commit_transaction(); - wait_for_upload(*r); - } - - util::File::remove(sync_config.path); - - { - auto r = Realm::get_shared_realm(sync_config); - auto table = r->read_group().get_table("class_object"); - - util::EventLoop::main().run_until([&] { - return table->size() == 1; - }); - - CppContext ctx(r); - Object obj(r, "object", 0); - auto list = any_cast<List>(obj.get_property_value<util::Any>(ctx, "value")); - REQUIRE(list.get<T>(0) == values[0]); - } - } -#endif -} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/query.json b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/query.json deleted file mode 100644 index 6cbbee55a..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/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/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/realm.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/realm.cpp deleted file mode 100644 index cb6e88697..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/realm.cpp +++ /dev/null @@ -1,1863 +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 "catch2/catch.hpp" - -#include "util/event_loop.hpp" -#include "util/test_file.hpp" -#include "util/test_utils.hpp" - -#include "binding_context.hpp" -#include "impl/realm_coordinator.hpp" -#include "object_schema.hpp" -#include "object_store.hpp" -#include "property.hpp" -#include "results.hpp" -#include "schema.hpp" -#include "thread_safe_reference.hpp" -#include "util/scheduler.hpp" - -#include <realm/db.hpp> - -#if REALM_ENABLE_SYNC -#include "sync/async_open_task.hpp" -#endif - -#include <realm/util/scope_exit.hpp> - -namespace realm { -class TestHelper { -public: - static DBRef& get_db(SharedRealm const& shared_realm) - { - return Realm::Internal::get_db(*shared_realm); - } - - static void begin_read(SharedRealm const& shared_realm, VersionID version) - { - Realm::Internal::begin_read(*shared_realm, version); - } -}; -} - -using namespace realm; - -TEST_CASE("SharedRealm: get_shared_realm()") { - TestFile config; - config.schema_version = 1; - config.schema = Schema{ - {"object", { - {"value", PropertyType::Int} - }}, - }; - - SECTION("should validate that the config is sensible") { - SECTION("bad encryption key") { - config.encryption_key = std::vector<char>(2, 0); - REQUIRE_THROWS(Realm::get_shared_realm(config)); - } - - SECTION("schema without schema version") { - config.schema_version = ObjectStore::NotVersioned; - REQUIRE_THROWS(Realm::get_shared_realm(config)); - } - - SECTION("migration function for immutable") { - config.schema_mode = SchemaMode::Immutable; - config.migration_function = [](auto, auto, auto) { }; - REQUIRE_THROWS(Realm::get_shared_realm(config)); - } - - SECTION("migration function for read-only") { - config.schema_mode = SchemaMode::ReadOnlyAlternative; - config.migration_function = [](auto, auto, auto) { }; - REQUIRE_THROWS(Realm::get_shared_realm(config)); - } - - SECTION("migration function for additive-only") { - config.schema_mode = SchemaMode::Additive; - config.migration_function = [](auto, auto, auto) { }; - REQUIRE_THROWS(Realm::get_shared_realm(config)); - } - - SECTION("initialization function for immutable") { - config.schema_mode = SchemaMode::Immutable; - config.initialization_function = [](auto) { }; - REQUIRE_THROWS(Realm::get_shared_realm(config)); - } - - SECTION("initialization function for read-only") { - config.schema_mode = SchemaMode::ReadOnlyAlternative; - config.initialization_function = [](auto) { }; - REQUIRE_THROWS(Realm::get_shared_realm(config)); - } - } - - SECTION("should reject mismatched config") { - SECTION("schema version") { - auto realm = Realm::get_shared_realm(config); - config.schema_version = 2; - REQUIRE_THROWS(Realm::get_shared_realm(config)); - - config.schema = util::none; - config.schema_version = ObjectStore::NotVersioned; - REQUIRE_NOTHROW(Realm::get_shared_realm(config)); - } - - SECTION("schema mode") { - auto realm = Realm::get_shared_realm(config); - config.schema_mode = SchemaMode::Manual; - REQUIRE_THROWS(Realm::get_shared_realm(config)); - } - - SECTION("durability") { - auto realm = Realm::get_shared_realm(config); - config.in_memory = true; - REQUIRE_THROWS(Realm::get_shared_realm(config)); - } - - SECTION("schema") { - auto realm = Realm::get_shared_realm(config); - config.schema = Schema{ - {"object", { - {"value", PropertyType::Int}, - {"value2", PropertyType::Int} - }}, - }; - REQUIRE_THROWS(Realm::get_shared_realm(config)); - } - } - - -// Windows doesn't use fifos -#ifndef _WIN32 - SECTION("should be able to set a FIFO fallback path") { - std::string fallback_dir = tmp_dir() + "/fallback/"; - realm::util::try_make_dir(fallback_dir); - TestFile config; - config.fifo_files_fallback_path = fallback_dir; - config.schema_version = 1; - config.schema = Schema{ - {"object", { - {"value", PropertyType::Int} - }}, - }; - - realm::util::make_dir(config.path + ".note"); - auto realm = Realm::get_shared_realm(config); - auto fallback_file = util::format("%1realm_%2.note", fallback_dir, std::hash<std::string>()(config.path)); // Mirror internal implementation - REQUIRE(File::exists(fallback_file)); - realm::util::remove_dir(config.path + ".note"); - realm::util::remove_dir_recursive(fallback_dir); - } - - SECTION("automatically append dir separator to end of fallback path") { - std::string fallback_dir = tmp_dir() + "/fallback"; - realm::util::try_make_dir(fallback_dir); - TestFile config; - config.fifo_files_fallback_path = fallback_dir; - config.schema_version = 1; - config.schema = Schema{ - {"object", { - {"value", PropertyType::Int} - }}, - }; - - realm::util::make_dir(config.path + ".note"); - auto realm = Realm::get_shared_realm(config); - auto fallback_file = util::format("%1/realm_%2.note", fallback_dir, std::hash<std::string>()(config.path)); // Mirror internal implementation - REQUIRE(File::exists(fallback_file)); - realm::util::remove_dir(config.path + ".note"); - realm::util::remove_dir_recursive(fallback_dir); - } -#endif - - SECTION("should verify that the schema is valid") { - config.schema = Schema{ - {"object", - {{"value", PropertyType::Int}}, - {{"invalid backlink", PropertyType::LinkingObjects|PropertyType::Array, "object", "value"}} - } - }; - REQUIRE_THROWS_WITH(Realm::get_shared_realm(config), - Catch::Matchers::Contains("origin of linking objects property")); - } - - SECTION("should apply the schema if one is supplied") { - Realm::get_shared_realm(config); - - { - Group g(config.path); - auto table = ObjectStore::table_for_object_type(g, "object"); - REQUIRE(table); - REQUIRE(table->get_column_count() == 1); - REQUIRE(table->get_column_name(*table->get_column_keys().begin()) == "value"); - } - - config.schema_version = 2; - config.schema = Schema{ - {"object", { - {"value", PropertyType::Int}, - {"value2", PropertyType::Int} - }}, - }; - bool migration_called = false; - config.migration_function = [&](SharedRealm old_realm, SharedRealm new_realm, Schema&) { - migration_called = true; - REQUIRE(ObjectStore::table_for_object_type(old_realm->read_group(), "object")->get_column_count() == 1); - REQUIRE(ObjectStore::table_for_object_type(new_realm->read_group(), "object")->get_column_count() == 2); - }; - Realm::get_shared_realm(config); - REQUIRE(migration_called); - } - - SECTION("should properly roll back from migration errors") { - Realm::get_shared_realm(config); - - config.schema_version = 2; - config.schema = Schema{ - {"object", { - {"value", PropertyType::Int}, - {"value2", PropertyType::Int} - }}, - }; - bool migration_called = false; - config.migration_function = [&](SharedRealm old_realm, SharedRealm new_realm, Schema&) { - REQUIRE(ObjectStore::table_for_object_type(old_realm->read_group(), "object")->get_column_count() == 1); - REQUIRE(ObjectStore::table_for_object_type(new_realm->read_group(), "object")->get_column_count() == 2); - if (!migration_called) { - migration_called = true; - throw "error"; - } - }; - REQUIRE_THROWS_WITH(Realm::get_shared_realm(config), "error"); - REQUIRE(migration_called); - REQUIRE_NOTHROW(Realm::get_shared_realm(config)); - } - - SECTION("should read the schema from the file if none is supplied") { - Realm::get_shared_realm(config); - - config.schema = util::none; - auto realm = Realm::get_shared_realm(config); - REQUIRE(realm->schema().size() == 1); - auto it = realm->schema().find("object"); - auto table = realm->read_group().get_table("class_object"); - REQUIRE(it != realm->schema().end()); - REQUIRE(it->table_key == table->get_key()); - REQUIRE(it->persisted_properties.size() == 1); - REQUIRE(it->persisted_properties[0].name == "value"); - REQUIRE(it->persisted_properties[0].column_key == table->get_column_key("value")); - } - - SECTION("should read the proper schema from the file if a custom version is supplied") { - Realm::get_shared_realm(config); - - config.schema = util::none; - config.schema_mode = SchemaMode::Additive; - config.schema_version = 0; - - auto realm = Realm::get_shared_realm(config); - REQUIRE(realm->schema().size() == 1); - - auto& db = TestHelper::get_db(realm); - auto rt = db->start_read(); - VersionID old_version = rt->get_version_of_current_transaction(); - rt = nullptr; - realm->close(); - - config.schema = Schema{ - {"object", { - {"value", PropertyType::Int} - }}, - {"object1", { - {"value", PropertyType::Int} - }}, - }; - config.schema_version = 1; - realm = Realm::get_shared_realm(config); - REQUIRE(realm->schema().size() == 2); - - config.schema = util::none; - auto old_realm = Realm::get_shared_realm(config); - TestHelper::begin_read(old_realm, old_version); - REQUIRE(old_realm->schema().size() == 1); - } - - SECTION("should sensibly handle opening an uninitialized file without a schema specified") { - // create an empty file - File(config.path, File::mode_Write); - - // open the empty file, but don't initialize the schema - Realm::Config config_without_schema = config; - config_without_schema.schema = util::none; - config_without_schema.schema_version = ObjectStore::NotVersioned; - auto realm = Realm::get_shared_realm(config_without_schema); - REQUIRE(realm->schema().empty()); - REQUIRE(realm->schema_version() == ObjectStore::NotVersioned); - // verify that we can get another Realm instance - REQUIRE_NOTHROW(Realm::get_shared_realm(config_without_schema)); - - // verify that we can also still open the file with a proper schema - auto realm2 = Realm::get_shared_realm(config); - REQUIRE_FALSE(realm2->schema().empty()); - REQUIRE(realm2->schema_version() == 1); - } - - SECTION("should populate the table columns in the schema when opening as immutable") { - Realm::get_shared_realm(config); - - config.schema_mode = SchemaMode::Immutable; - auto realm = Realm::get_shared_realm(config); - auto it = realm->schema().find("object"); - auto table = realm->read_group().get_table("class_object"); - REQUIRE(it != realm->schema().end()); - REQUIRE(it->table_key == table->get_key()); - REQUIRE(it->persisted_properties.size() == 1); - REQUIRE(it->persisted_properties[0].name == "value"); - REQUIRE(it->persisted_properties[0].column_key == table->get_column_key("value")); - } - - SECTION("should support using different table subsets on different threads") { - auto realm1 = Realm::get_shared_realm(config); - - config.schema = Schema{ - {"object 2", { - {"value", PropertyType::Int} - }}, - }; - auto realm2 = Realm::get_shared_realm(config); - - config.schema = util::none; - auto realm3 = Realm::get_shared_realm(config); - - config.schema = Schema{ - {"object", { - {"value", PropertyType::Int} - }}, - }; - auto realm4 = Realm::get_shared_realm(config); - - realm1->refresh(); - realm2->refresh(); - - REQUIRE(realm1->schema().size() == 1); - REQUIRE(realm1->schema().find("object") != realm1->schema().end()); - REQUIRE(realm2->schema().size() == 1); - REQUIRE(realm2->schema().find("object 2") != realm2->schema().end()); - REQUIRE(realm3->schema().size() == 2); - REQUIRE(realm3->schema().find("object") != realm3->schema().end()); - REQUIRE(realm3->schema().find("object 2") != realm3->schema().end()); - REQUIRE(realm4->schema().size() == 1); - REQUIRE(realm4->schema().find("object") != realm4->schema().end()); - } - -// The ExternalCommitHelper implementation on Windows doesn't rely on files -#ifndef _WIN32 - SECTION("should throw when creating the notification pipe fails") { - util::try_make_dir(config.path + ".note"); - auto sys_fallback_file = util::format("%1realm_%2.note", DBOptions::get_sys_tmp_dir(), std::hash<std::string>()(config.path)); // Mirror internal implementation - util::try_make_dir(sys_fallback_file); - REQUIRE_THROWS(Realm::get_shared_realm(config)); - util::remove_dir(config.path + ".note"); - util::remove_dir(sys_fallback_file); - } -#endif - - SECTION("should detect use of Realm on incorrect thread") { - auto realm = Realm::get_shared_realm(config); - std::thread([&]{ - REQUIRE_THROWS_AS(realm->verify_thread(), IncorrectThreadException); - }).join(); - } - - SECTION("should not modify the schema when fetching from the cache") { - auto realm = Realm::get_shared_realm(config); - auto object_schema = &*realm->schema().find("object"); - Realm::get_shared_realm(config); - REQUIRE(object_schema == &*realm->schema().find("object")); - } -} - -#if REALM_ENABLE_SYNC -TEST_CASE("Get Realm using Async Open", "[asyncOpen]") { - TestFile local_config; - local_config.schema_version = 1; - local_config.schema = Schema{ - {"object", { - {"value", PropertyType::Int} - }}, - }; - - if (!util::EventLoop::has_implementation()) - return; - - TestSyncManager init_sync_manager; - - SyncServer server; - SyncTestFile config(server, "default"); - config.schema = Schema{ - {"object", { - {"value", PropertyType::Int}, - }}, - }; - SyncTestFile config2(server, "default"); - config2.schema = config.schema; - - std::mutex mutex; - SECTION("can open synced Realms that don't already exist") { - std::atomic<bool> called{false}; - auto task = Realm::get_synchronized_realm(config); - task->start([&](auto ref, auto error) { - std::lock_guard<std::mutex> lock(mutex); - REQUIRE(!error); - called = true; - - REQUIRE(Realm::get_shared_realm(std::move(ref))->read_group().get_table("class_object")); - }); - util::EventLoop::main().run_until([&]{ return called.load(); }); - std::lock_guard<std::mutex> lock(mutex); - REQUIRE(called); - } - - SECTION("downloads Realms which exist on the server") { - { - auto realm = Realm::get_shared_realm(config2); - realm->begin_transaction(); - realm->read_group().get_table("class_object")->create_object(); - realm->commit_transaction(); - wait_for_upload(*realm); - } - - std::atomic<bool> called{false}; - auto task = Realm::get_synchronized_realm(config); - task->start([&](auto ref, auto error) { - std::lock_guard<std::mutex> lock(mutex); - REQUIRE(!error); - called = true; - - REQUIRE(Realm::get_shared_realm(std::move(ref))->read_group().get_table("class_object")); - }); - util::EventLoop::main().run_until([&]{ return called.load(); }); - std::lock_guard<std::mutex> lock(mutex); - REQUIRE(called); - } - - SECTION("downloads latest state for Realms which already exist locally") { - wait_for_upload(*Realm::get_shared_realm(config)); - - { - auto realm = Realm::get_shared_realm(config2); - realm->begin_transaction(); - realm->read_group().get_table("class_object")->create_object(); - realm->commit_transaction(); - wait_for_upload(*realm); - } - - std::atomic<bool> called{false}; - auto task = Realm::get_synchronized_realm(config); - task->start([&](auto ref, auto error) { - std::lock_guard<std::mutex> lock(mutex); - REQUIRE(!error); - called = true; - - REQUIRE(Realm::get_shared_realm(std::move(ref))->read_group().get_table("class_object")->size() == 1); - }); - util::EventLoop::main().run_until([&]{ return called.load(); }); - std::lock_guard<std::mutex> lock(mutex); - REQUIRE(called); - } - - SECTION("can download partial Realms") { - config.sync_config->is_partial = true; - config2.sync_config->is_partial = true; - { - auto realm = Realm::get_shared_realm(config2); - realm->begin_transaction(); - realm->read_group().get_table("class_object")->create_object(); - realm->commit_transaction(); - wait_for_upload(*realm); - } - - std::atomic<bool> called{false}; - auto task = Realm::get_synchronized_realm(config); - task->start([&](auto, auto error) { - std::lock_guard<std::mutex> lock(mutex); - REQUIRE(!error); - called = true; - }); - util::EventLoop::main().run_until([&]{ return called.load(); }); - std::lock_guard<std::mutex> lock(mutex); - REQUIRE(called); - - // No subscriptions, so no objects - REQUIRE(Realm::get_shared_realm(config)->read_group().get_table("class_object")->size() == 0); - } - - SECTION("can download multiple Realms at a time") { - SyncTestFile config1(server, "realm1"); - SyncTestFile config2(server, "realm2"); - SyncTestFile config3(server, "realm3"); - SyncTestFile config4(server, "realm4"); - - std::vector<std::shared_ptr<AsyncOpenTask>> tasks = { - Realm::get_synchronized_realm(config1), - Realm::get_synchronized_realm(config2), - Realm::get_synchronized_realm(config3), - Realm::get_synchronized_realm(config4), - }; - - std::atomic<int> completed{0}; - for (auto& task : tasks) { - task->start([&](auto, auto) { - ++completed; - }); - } - util::EventLoop::main().run_until([&]{ return completed == 4; }); - } -} -#endif - -TEST_CASE("SharedRealm: notifications") { - if (!util::EventLoop::has_implementation()) - return; - - TestFile config; - config.schema_version = 0; - config.schema = Schema{ - {"object", { - {"value", PropertyType::Int} - }}, - }; - - struct Context : BindingContext { - size_t* change_count; - Context(size_t* out) : change_count(out) { } - - void did_change(std::vector<ObserverState> const&, std::vector<void*> const&, bool) override - { - ++*change_count; - } - }; - - size_t change_count = 0; - auto realm = Realm::get_shared_realm(config); - realm->read_group(); - realm->m_binding_context.reset(new Context{&change_count}); - realm->m_binding_context->realm = realm; - - SECTION("local notifications are sent synchronously") { - realm->begin_transaction(); - REQUIRE(change_count == 0); - realm->commit_transaction(); - REQUIRE(change_count == 1); - } - - SECTION("remote notifications are sent asynchronously") { - auto r2 = Realm::get_shared_realm(config); - r2->begin_transaction(); - r2->commit_transaction(); - REQUIRE(change_count == 0); - util::EventLoop::main().run_until([&]{ return change_count > 0; }); - REQUIRE(change_count == 1); - } - - SECTION("refresh() from within changes_available() refreshes") { - struct Context : BindingContext { - Realm& realm; - Context(Realm& realm) : realm(realm) { } - - void changes_available() override - { - REQUIRE(realm.refresh()); - } - }; - realm->m_binding_context.reset(new Context{*realm}); - realm->set_auto_refresh(false); - - auto r2 = Realm::get_shared_realm(config); - r2->begin_transaction(); - r2->commit_transaction(); - realm->notify(); - // Should return false as the realm was already advanced - REQUIRE_FALSE(realm->refresh()); - } - - SECTION("refresh() from within did_change() is a no-op") { - struct Context : BindingContext { - Realm& realm; - Context(Realm& realm) : realm(realm) { } - - void did_change(std::vector<ObserverState> const&, std::vector<void*> const&, bool) override - { - // Create another version so that refresh() could do something - auto r2 = Realm::get_shared_realm(realm.config()); - r2->begin_transaction(); - r2->commit_transaction(); - - // Should be a no-op - REQUIRE_FALSE(realm.refresh()); - } - }; - realm->m_binding_context.reset(new Context{*realm}); - - auto r2 = Realm::get_shared_realm(config); - r2->begin_transaction(); - r2->commit_transaction(); - REQUIRE(realm->refresh()); - - auto ver = realm->current_transaction_version(); - realm->m_binding_context.reset(); - // Should advance to the version created in the previous did_change() - REQUIRE(realm->refresh()); - auto new_ver = realm->current_transaction_version(); - REQUIRE(*new_ver > *ver); - // No more versions, so returns false - REQUIRE_FALSE(realm->refresh()); - } - - SECTION("begin_write() from within did_change() produces recursive notifications") { - struct Context : BindingContext { - Realm& realm; - size_t calls = 0; - Context(Realm& realm) : realm(realm) { } - - void did_change(std::vector<ObserverState> const&, std::vector<void*> const&, bool) override - { - ++calls; - if (realm.is_in_transaction()) - return; - - // Create another version so that begin_write() advances the version - auto r2 = Realm::get_shared_realm(realm.config()); - r2->begin_transaction(); - r2->commit_transaction(); - - realm.begin_transaction(); - realm.cancel_transaction(); - } - }; - auto context = new Context{*realm}; - realm->m_binding_context.reset(context); - - auto r2 = Realm::get_shared_realm(config); - r2->begin_transaction(); - r2->commit_transaction(); - REQUIRE(realm->refresh()); - REQUIRE(context->calls == 2); - - // Despite not sending a new notification we did advance the version, so - // no more versions to refresh to - REQUIRE_FALSE(realm->refresh()); - } -} - -TEST_CASE("SharedRealm: schema updating from external changes") { - TestFile config; - config.schema_version = 0; - config.schema_mode = SchemaMode::Additive; - config.schema = Schema{ - {"object", { - {"value", PropertyType::Int, Property::IsPrimary{true}}, - {"value 2", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}}, - }}, - }; - - SECTION("newly added columns update table columns but are not added to properties") { - // Does this test add any value when column keys are stable? - auto r1 = Realm::get_shared_realm(config); - auto r2 = Realm::get_shared_realm(config); - auto test = [&] { - r2->begin_transaction(); - r2->read_group().get_table("class_object")->add_column(type_String, "new col"); - r2->commit_transaction(); - - auto& object_schema = *r1->schema().find("object"); - REQUIRE(object_schema.persisted_properties.size() == 2); - ColKey col = object_schema.persisted_properties[0].column_key; - r1->refresh(); - REQUIRE(object_schema.persisted_properties[0].column_key == col); - }; - SECTION("with an active read transaction") { - r1->read_group(); - test(); - } - SECTION("without an active read transaction") { - r1->invalidate(); - test(); - } - } - - SECTION("beginning a read transaction checks for incompatible changes") { - auto r = Realm::get_shared_realm(config); - r->invalidate(); - - auto& db = TestHelper::get_db(r); - WriteTransaction wt(db); - auto& table = *wt.get_table("class_object"); - - SECTION("removing a property") { - table.remove_column(table.get_column_key("value")); - wt.commit(); - REQUIRE_THROWS_WITH(r->refresh(), - Catch::Matchers::Contains("Property 'object.value' has been removed.")); - } - - SECTION("change property type") { - table.remove_column(table.get_column_key("value 2")); - table.add_column(type_Float, "value 2"); - wt.commit(); - REQUIRE_THROWS_WITH(r->refresh(), - Catch::Matchers::Contains("Property 'object.value 2' has been changed from 'int' to 'float'")); - } - - SECTION("make property optional") { - table.remove_column(table.get_column_key("value 2")); - table.add_column(type_Int, "value 2", true); - wt.commit(); - REQUIRE_THROWS_WITH(r->refresh(), - Catch::Matchers::Contains("Property 'object.value 2' has been made optional")); - } - - SECTION("recreate column with no changes") { - table.remove_column(table.get_column_key("value 2")); - table.add_column(type_Int, "value 2"); - wt.commit(); - REQUIRE_NOTHROW(r->refresh()); - } - - SECTION("remove index from non-PK") { - table.remove_search_index(table.get_column_key("value 2")); - wt.commit(); - REQUIRE_NOTHROW(r->refresh()); - } - } -} - -TEST_CASE("SharedRealm: close()") { - TestFile config; - config.schema_version = 1; - config.schema = Schema{ - {"object", { - {"value", PropertyType::Int} - }}, - {"list", { - {"list", PropertyType::Object|PropertyType::Array, "object"} - }}, - }; - - auto realm = Realm::get_shared_realm(config); - - SECTION("all functions throw ClosedRealmException after close") { - realm->close(); - - REQUIRE(realm->is_closed()); - - REQUIRE_THROWS_AS(realm->read_group(), ClosedRealmException); - REQUIRE_THROWS_AS(realm->begin_transaction(), ClosedRealmException); - REQUIRE(!realm->is_in_transaction()); - REQUIRE_THROWS_AS(realm->commit_transaction(), InvalidTransactionException); - REQUIRE_THROWS_AS(realm->cancel_transaction(), InvalidTransactionException); - - REQUIRE_THROWS_AS(realm->refresh(), ClosedRealmException); - REQUIRE_THROWS_AS(realm->invalidate(), ClosedRealmException); - REQUIRE_THROWS_AS(realm->compact(), ClosedRealmException); - } - - SECTION("fully closes database file even with live notifiers") { - auto& group = realm->read_group(); - realm->begin_transaction(); - auto obj = ObjectStore::table_for_object_type(group, "list")->create_object(); - realm->commit_transaction(); - - Results results(realm, ObjectStore::table_for_object_type(group, "object")); - List list(realm, obj.get_linklist("list")); - Object object(realm, obj); - - auto obj_token = object.add_notification_callback([](CollectionChangeSet, std::exception_ptr) {}); - auto list_token = list.add_notification_callback([](CollectionChangeSet, std::exception_ptr) {}); - auto results_token = results.add_notification_callback([](CollectionChangeSet, std::exception_ptr) {}); - - // Perform a dummy transaction to ensure the notifiers actually acquire - // resources that need to be closed - realm->begin_transaction(); - realm->commit_transaction(); - - realm->close(); - - // Verify that we're able to acquire an exclusive lock - REQUIRE(DB::call_with_lock(config.path, [](auto) {})); - } -} - -TEST_CASE("ShareRealm: in-memory mode from buffer") { - TestFile config; - config.schema_version = 1; - config.schema = Schema{ - {"object", { - {"value", PropertyType::Int} - }}, - }; - - SECTION("Save and open Realm from in-memory buffer") { - // Write in-memory copy of Realm to a buffer - auto realm = Realm::get_shared_realm(config); - OwnedBinaryData realm_buffer = realm->write_copy(); - - // Open the buffer as a new (immutable in-memory) Realm - realm::Realm::Config config2; - config2.in_memory = true; - config2.schema_mode = SchemaMode::Immutable; - config2.realm_data = realm_buffer.get(); - - auto realm2 = Realm::get_shared_realm(config2); - - // Verify that it can read the schema and that it is the same - REQUIRE(realm->schema().size() == 1); - auto it = realm->schema().find("object"); - auto table = realm->read_group().get_table("class_object"); - REQUIRE(it != realm->schema().end()); - REQUIRE(it->table_key == table->get_key()); - REQUIRE(it->persisted_properties.size() == 1); - REQUIRE(it->persisted_properties[0].name == "value"); - REQUIRE(it->persisted_properties[0].column_key == table->get_column_key("value")); - - // Test invalid configs - realm::Realm::Config config3; - config3.realm_data = realm_buffer.get(); - REQUIRE_THROWS(Realm::get_shared_realm(config3)); // missing in_memory and immutable - - config3.in_memory = true; - config3.schema_mode = SchemaMode::Immutable; - config3.path = "path"; - REQUIRE_THROWS(Realm::get_shared_realm(config3)); // both buffer and path - - config3.path = ""; - config3.encryption_key = {'a'}; - REQUIRE_THROWS(Realm::get_shared_realm(config3)); // both buffer and encryption - } -} - -TEST_CASE("ShareRealm: realm closed in did_change callback") { - TestFile config; - config.schema_version = 1; - config.schema = Schema{ - {"object", { - {"value", PropertyType::Int} - }}, - }; - config.automatic_change_notifications = false; - auto r1 = Realm::get_shared_realm(config); - - r1->begin_transaction(); - auto table = r1->read_group().get_table("class_object"); - table->create_object(); - r1->commit_transaction(); - - // Cannot be a member var of Context since Realm.close will free the context. - static SharedRealm* shared_realm; - shared_realm = &r1; - struct Context : public BindingContext { - void did_change(std::vector<ObserverState> const&, std::vector<void*> const&, bool) override - { - (*shared_realm)->close(); - (*shared_realm).reset(); - } - }; - - SECTION("did_change") { - r1->m_binding_context.reset(new Context()); - r1->invalidate(); - - auto r2 = Realm::get_shared_realm(config); - r2->begin_transaction(); - r2->read_group().get_table("class_object")->create_object(); - r2->commit_transaction(); - r2.reset(); - - r1->notify(); - } - - SECTION("did_change with async results") { - r1->m_binding_context.reset(new Context()); - Results results(r1, table->where()); - auto token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { - // Should not be called. - REQUIRE(false); - }); - - auto r2 = Realm::get_shared_realm(config); - r2->begin_transaction(); - r2->read_group().get_table("class_object")->create_object(); - r2->commit_transaction(); - r2.reset(); - - auto coordinator = _impl::RealmCoordinator::get_coordinator(config.path); - coordinator->on_change(); - - r1->notify(); - } - - SECTION("refresh") { - r1->m_binding_context.reset(new Context()); - - auto r2 = Realm::get_shared_realm(config); - r2->begin_transaction(); - r2->read_group().get_table("class_object")->create_object(); - r2->commit_transaction(); - r2.reset(); - - REQUIRE_FALSE(r1->refresh()); - } - - shared_realm = nullptr; -} - -TEST_CASE("RealmCoordinator: schema cache") { - TestFile config; - auto coordinator = _impl::RealmCoordinator::get_coordinator(config.path); - - Schema cache_schema; - uint64_t cache_sv = -1, cache_tv = -1; - - Schema schema{ - {"object", { - {"value", PropertyType::Int} - }}, - }; - Schema schema2{ - {"object", { - {"value", PropertyType::Int}, - }}, - {"object 2", { - {"value", PropertyType::Int}, - }}, - }; - - SECTION("valid initial schema sets cache") { - coordinator->cache_schema(schema, 5, 10); - REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv)); - REQUIRE(cache_schema == schema); - REQUIRE(cache_sv == 5); - REQUIRE(cache_tv == 10); - } - - SECTION("cache can be updated with newer schema") { - coordinator->cache_schema(schema, 5, 10); - coordinator->cache_schema(schema2, 6, 11); - REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv)); - REQUIRE(cache_schema == schema2); - REQUIRE(cache_sv == 6); - REQUIRE(cache_tv == 11); - } - - SECTION("empty schema is ignored") { - coordinator->cache_schema(Schema{}, 5, 10); - REQUIRE_FALSE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv)); - - coordinator->cache_schema(schema, 5, 10); - coordinator->cache_schema(Schema{}, 5, 10); - REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv)); - REQUIRE(cache_schema == schema); - REQUIRE(cache_sv == 5); - REQUIRE(cache_tv == 10); - } - - SECTION("schema for older transaction is ignored") { - coordinator->cache_schema(schema, 5, 10); - coordinator->cache_schema(schema2, 4, 8); - - REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv)); - REQUIRE(cache_schema == schema); - REQUIRE(cache_sv == 5); - REQUIRE(cache_tv == 10); - - coordinator->advance_schema_cache(10, 20); - coordinator->cache_schema(schema, 6, 15); - REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv)); - REQUIRE(cache_tv == 20); // should not have dropped to 15 - } - - SECTION("advance_schema() from transaction version bumps transaction version") { - coordinator->cache_schema(schema, 5, 10); - coordinator->advance_schema_cache(10, 12); - REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv)); - REQUIRE(cache_schema == schema); - REQUIRE(cache_sv == 5); - REQUIRE(cache_tv == 12); - } - - SECTION("advance_schema() ending before transaction version does nothing") { - coordinator->cache_schema(schema, 5, 10); - coordinator->advance_schema_cache(8, 9); - REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv)); - REQUIRE(cache_schema == schema); - REQUIRE(cache_sv == 5); - REQUIRE(cache_tv == 10); - } - - SECTION("advance_schema() extending over transaction version bumps version") { - coordinator->cache_schema(schema, 5, 10); - coordinator->advance_schema_cache(3, 15); - REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv)); - REQUIRE(cache_schema == schema); - REQUIRE(cache_sv == 5); - REQUIRE(cache_tv == 15); - } - - SECTION("advance_schema() with no cahced schema does nothing") { - coordinator->advance_schema_cache(3, 15); - REQUIRE_FALSE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv)); - } -} - -TEST_CASE("SharedRealm: coordinator schema cache") { - TestFile config; - auto r = Realm::get_shared_realm(config); - auto coordinator = _impl::RealmCoordinator::get_coordinator(config.path); - - Schema cache_schema; - uint64_t cache_sv = -1, cache_tv = -1; - - Schema schema{ - {"object", { - {"value", PropertyType::Int} - }}, - }; - Schema schema2{ - {"object", { - {"value", PropertyType::Int}, - }}, - {"object 2", { - {"value", PropertyType::Int}, - }}, - }; - - class ExternalWriter { - private: - std::shared_ptr<Realm> m_realm; - public: - WriteTransaction wt; - ExternalWriter(Realm::Config const& config) - : m_realm([&] { - auto c = config; - c.scheduler = util::Scheduler::get_frozen(); - return _impl::RealmCoordinator::get_coordinator(c.path)->get_realm(c, util::none); - }()) - , wt(TestHelper::get_db(m_realm)) - { - } - }; - - auto external_write = [&](Realm::Config const& config, auto&& fn) { - ExternalWriter wt(config); - fn(wt.wt); - wt.wt.commit(); - }; - - SECTION("is initially empty for uninitialized file") { - REQUIRE_FALSE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv)); - } - r->update_schema(schema); - - SECTION("is populated after calling update_schema()") { - REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv)); - REQUIRE(cache_sv == 0); - REQUIRE(cache_schema == schema); - REQUIRE(cache_schema.begin()->persisted_properties[0].column_key != ColKey{}); - } - - coordinator = nullptr; - r = nullptr; - r = Realm::get_shared_realm(config); - coordinator = _impl::RealmCoordinator::get_coordinator(config.path); - REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv)); - - SECTION("is populated after opening an initialized file") { - REQUIRE(cache_sv == 0); - REQUIRE(cache_tv == 2); // with in-realm history the version doesn't reset - REQUIRE(cache_schema == schema); - REQUIRE(cache_schema.begin()->persisted_properties[0].column_key != ColKey{}); - } - - SECTION("transaction version is bumped after a local write") { - auto tv = cache_tv; - r->begin_transaction(); - r->commit_transaction(); - REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv)); - REQUIRE(cache_tv == tv + 1); - } - - SECTION("notify() without a read transaction does not bump transaction version") { - auto tv = cache_tv; - - SECTION("non-schema change") { - external_write(config, [](auto& wt) { - wt.get_table("class_object")->create_object(); - }); - } - SECTION("schema change") { - external_write(config, [](auto& wt) { - wt.add_table("class_object 2"); - }); - } - - r->notify(); - REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv)); - REQUIRE(cache_tv == tv); - REQUIRE(cache_schema == schema); - } - - SECTION("notify() with a read transaction bumps transaction version") { - r->read_group(); - external_write(config, [](auto& wt) { - wt.get_table("class_object")->create_object(); - }); - - r->notify(); - auto tv = cache_tv; - REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv)); - REQUIRE(cache_tv == tv + 1); - } - - SECTION("notify() with a read transaction updates schema folloing external schema change") { - r->read_group(); - external_write(config, [](auto& wt) { - wt.add_table("class_object 2"); - }); - - r->notify(); - auto tv = cache_tv; - REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv)); - REQUIRE(cache_tv == tv + 1); - REQUIRE(cache_schema.size() == 2); - REQUIRE(cache_schema.find("object 2") != cache_schema.end()); - } - - SECTION("transaction version is bumped after refresh() following external non-schema write") { - external_write(config, [](auto& wt) { - wt.get_table("class_object")->create_object(); - }); - - r->refresh(); - auto tv = cache_tv; - REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv)); - REQUIRE(cache_tv == tv + 1); - } - - SECTION("schema is reread following refresh() over external schema change") { - external_write(config, [](auto& wt) { - wt.add_table("class_object 2"); - }); - - r->refresh(); - auto tv = cache_tv; - REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv)); - REQUIRE(cache_tv == tv + 1); - REQUIRE(cache_schema.size() == 2); - REQUIRE(cache_schema.find("object 2") != cache_schema.end()); - } - - SECTION("update_schema() to version already on disk updates cache") { - r->read_group(); - external_write(config, [](auto& wt) { - auto table = wt.add_table("class_object 2"); - table->add_column(type_Int, "value"); - }); - - auto tv = cache_tv; - r->update_schema(schema2); - - REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv)); - REQUIRE(cache_tv == tv + 1); // only +1 because update_schema() did not perform a write - REQUIRE(cache_schema.size() == 2); - REQUIRE(cache_schema.find("object 2") != cache_schema.end()); - } - - SECTION("update_schema() to version already on disk updates cache") { - r->read_group(); - external_write(config, [](auto& wt) { - auto table = wt.add_table("class_object 2"); - table->add_column(type_Int, "value"); - }); - - auto tv = cache_tv; - r->update_schema(schema2); - - REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv)); - REQUIRE(cache_tv == tv + 1); // only +1 because update_schema() did not perform a write - REQUIRE(cache_schema.size() == 2); - REQUIRE(cache_schema.find("object 2") != cache_schema.end()); - } - - SECTION("update_schema() to version populated on disk while waiting for the write lock updates cache") { - r->read_group(); - - // We want to commit the write while we're waiting on the write lock on - // this thread, which can't really be done in a properly synchronized manner - std::chrono::microseconds wait_time{5000}; -#if REALM_ANDROID - // When running on device or in an emulator we need to wait longer due - // to them being slow - wait_time *= 10; -#endif - - bool did_run = false; - JoiningThread thread([&] { - ExternalWriter writer(config); - if (writer.wt.get_table("class_object 2")) - return; - did_run = true; - - auto table = writer.wt.add_table("class_object 2"); - table->add_column(type_Int, "value"); - std::this_thread::sleep_for(wait_time * 2); - writer.wt.commit(); - }); - std::this_thread::sleep_for(wait_time); - - auto tv = cache_tv; - r->update_schema(Schema{ - {"object", {{"value", PropertyType::Int}}}, - {"object 2", {{"value", PropertyType::Int}}}, - }); - - // just skip the test if the timing was wrong to avoid spurious failures - if (!did_run) - return; - - REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv)); - REQUIRE(cache_tv == tv + 1); // only +1 because update_schema()'s write was rolled back - REQUIRE(cache_schema.size() == 2); - REQUIRE(cache_schema.find("object 2") != cache_schema.end()); - } -} - -TEST_CASE("SharedRealm: dynamic schema mode doesn't invalidate object schema pointers when schema hasn't changed") { - TestFile config; - - // Prepopulate the Realm with the schema. - Realm::Config config_with_schema = config; - config_with_schema.schema_version = 1; - config_with_schema.schema_mode = SchemaMode::Automatic; - config_with_schema.schema = Schema{ - {"object", { - {"value", PropertyType::Int, Property::IsPrimary{true}}, - {"value 2", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}}, - }} - }; - auto r1 = Realm::get_shared_realm(config_with_schema); - - // Retrieve the object schema in dynamic mode. - auto r2 = Realm::get_shared_realm(config); - auto* object_schema = &*r2->schema().find("object"); - - // Perform an empty write to create a new version, resulting in the other Realm needing to re-read the schema. - r1->begin_transaction(); - r1->commit_transaction(); - - // Advance to the latest version, and verify the object schema is at the same location in memory. - r2->read_group(); - REQUIRE(object_schema == &*r2->schema().find("object")); -} - -TEST_CASE("SharedRealm: SchemaChangedFunction") { - struct Context : BindingContext { - size_t* change_count; - Schema* schema; - Context(size_t* count_out, Schema* schema_out) : change_count(count_out), schema(schema_out) { } - - void schema_did_change(Schema const& changed_schema) override - { - ++*change_count; - *schema = changed_schema; - } - }; - - size_t schema_changed_called = 0; - Schema changed_fixed_schema; - TestFile config; - auto dynamic_config = config; - - config.schema = Schema{ - {"object1", { - {"value", PropertyType::Int}, - }}, - {"object2", { - {"value", PropertyType::Int}, - }} - }; - config.schema_version = 1; - auto r1 = Realm::get_shared_realm(config); - r1->read_group(); - r1->m_binding_context.reset(new Context(&schema_changed_called, &changed_fixed_schema)); - - SECTION("Fixed schema") { - SECTION("update_schema") { - auto new_schema = Schema{ - {"object3", { - {"value", PropertyType::Int}, - }} - }; - r1->update_schema(new_schema, 2); - REQUIRE(schema_changed_called == 1); - REQUIRE(changed_fixed_schema.find("object3")->property_for_name("value")->column_key != ColKey{}); - } - - SECTION("Open a new Realm instance with same config won't trigger") { - auto r2 = Realm::get_shared_realm(config); - REQUIRE(schema_changed_called == 0); - } - - SECTION("Non schema related transaction doesn't trigger") { - auto r2 = Realm::get_shared_realm(config); - r2->begin_transaction(); - r2->commit_transaction(); - r1->refresh(); - REQUIRE(schema_changed_called == 0); - } - - SECTION("Schema is changed by another Realm") { - auto r2 = Realm::get_shared_realm(config); - r2->begin_transaction(); - r2->read_group().get_table("class_object1")->add_column(type_String, "new col"); - r2->commit_transaction(); - r1->refresh(); - REQUIRE(schema_changed_called == 1); - REQUIRE(changed_fixed_schema.find("object1")->property_for_name("value")->column_key != ColKey{}); - } - - // This is not a valid use case. m_schema won't be refreshed. - SECTION("Schema is changed by this Realm won't trigger") { - r1->begin_transaction(); - r1->read_group().get_table("class_object1")->add_column(type_String, "new col"); - r1->commit_transaction(); - REQUIRE(schema_changed_called == 0); - } - } - - SECTION("Dynamic schema") { - size_t dynamic_schema_changed_called = 0; - Schema changed_dynamic_schema; - auto r2 = Realm::get_shared_realm(dynamic_config); - r2->m_binding_context.reset(new Context(&dynamic_schema_changed_called, &changed_dynamic_schema)); - - SECTION("set_schema_subset") { - auto new_schema = Schema{ - {"object1", { - {"value", PropertyType::Int}, - }} - }; - r2->set_schema_subset(new_schema); - REQUIRE(schema_changed_called == 0); - REQUIRE(dynamic_schema_changed_called == 1); - REQUIRE(changed_dynamic_schema.find("object1")->property_for_name("value")->column_key != ColKey{}); - } - - SECTION("Non schema related transaction will always trigger in dynamic mode") { - auto r1 = Realm::get_shared_realm(config); - // An empty transaction will trigger the schema changes always in dynamic mode. - r1->begin_transaction(); - r1->commit_transaction(); - r2->refresh(); - REQUIRE(dynamic_schema_changed_called == 1); - REQUIRE(changed_dynamic_schema.find("object1")->property_for_name("value")->column_key != ColKey{}); - } - - SECTION("Schema is changed by another Realm") { - r1->begin_transaction(); - r1->read_group().get_table("class_object1")->add_column(type_String, "new col"); - r1->commit_transaction(); - r2->refresh(); - REQUIRE(dynamic_schema_changed_called == 1); - REQUIRE(changed_dynamic_schema.find("object1")->property_for_name("value")->column_key != ColKey{}); - } - } -} - -#ifndef _WIN32 -TEST_CASE("SharedRealm: compact on launch") { - // Make compactable Realm - TestFile config; - config.automatic_change_notifications = false; - int num_opens = 0; - config.should_compact_on_launch_function = [&](size_t total_bytes, size_t used_bytes) { - REQUIRE(total_bytes > used_bytes); - num_opens++; - return num_opens != 2; - }; - config.schema = Schema{ - {"object", { - {"value", PropertyType::String} - }}, - }; - REQUIRE(num_opens == 0); - auto r = Realm::get_shared_realm(config); - REQUIRE(num_opens == 1); - r->begin_transaction(); - auto table = r->read_group().get_table("class_object"); - size_t count = 1000; - for (size_t i = 0; i < count; ++i) - table->create_object().set_all(util::format("Foo_%1", i % 10).c_str()); - r->commit_transaction(); - REQUIRE(table->size() == count); - r->close(); - - SECTION("compact reduces the file size") { - // Confirm expected sizes before and after opening the Realm - size_t size_before = size_t(File(config.path).get_size()); - r = Realm::get_shared_realm(config); - REQUIRE(num_opens == 2); - r->close(); - REQUIRE(size_t(File(config.path).get_size()) == size_before); // File size after returning false - r = Realm::get_shared_realm(config); - REQUIRE(num_opens == 3); - REQUIRE(size_t(File(config.path).get_size()) < size_before); // File size after returning true - - // Validate that the file still contains what it should - REQUIRE(r->read_group().get_table("class_object")->size() == count); - - // Registering for a collection notification shouldn't crash when compact on launch is used. - Results results(r, r->read_group().get_table("class_object")); - results.add_notification_callback([](CollectionChangeSet const&, std::exception_ptr) { }); - r->close(); - } - - SECTION("compact function does not get invoked if realm is open on another thread") { - config.scheduler = util::Scheduler::get_frozen(); - r = Realm::get_shared_realm(config); - REQUIRE(num_opens == 2); - std::thread([&]{ - auto r2 = Realm::get_shared_realm(config); - REQUIRE(num_opens == 2); - }).join(); - r->close(); - std::thread([&]{ - auto r3 = Realm::get_shared_realm(config); - REQUIRE(num_opens == 3); - }).join(); - } -} -#endif - -struct ModeAutomatic { - static SchemaMode mode() { return SchemaMode::Automatic; } - static bool should_call_init_on_version_bump() { return false; } -}; -struct ModeAdditive { - static SchemaMode mode() { return SchemaMode::Additive; } - static bool should_call_init_on_version_bump() { return false; } -}; -struct ModeManual { - static SchemaMode mode() { return SchemaMode::Manual; } - static bool should_call_init_on_version_bump() { return false; } -}; -struct ModeResetFile { - static SchemaMode mode() { return SchemaMode::ResetFile; } - static bool should_call_init_on_version_bump() { return true; } -}; - -TEMPLATE_TEST_CASE("SharedRealm: update_schema with initialization_function", "[init][update_schema]", - ModeAutomatic, ModeAdditive, ModeManual, ModeResetFile) { - TestFile config; - config.schema_mode = TestType::mode(); - bool initialization_function_called = false; - uint64_t schema_version_in_callback = -1; - Schema schema_in_callback; - auto initialization_function = [&initialization_function_called, &schema_version_in_callback, - &schema_in_callback](auto shared_realm) { - REQUIRE(shared_realm->is_in_transaction()); - initialization_function_called = true; - schema_version_in_callback = shared_realm->schema_version(); - schema_in_callback = shared_realm->schema(); - }; - - Schema schema{ - {"object", { - {"value", PropertyType::String} - }}, - }; - - SECTION("call initialization function directly by update_schema") { - // Open in dynamic mode with no schema specified - auto realm = Realm::get_shared_realm(config); - REQUIRE_FALSE(initialization_function_called); - - realm->update_schema(schema, 0, nullptr, initialization_function); - REQUIRE(initialization_function_called); - REQUIRE(schema_version_in_callback == 0); - REQUIRE(schema_in_callback.compare(schema).size() == 0); - } - - config.schema_version = 0; - config.schema = schema; - - SECTION("initialization function should be called for unversioned realm") { - config.initialization_function = initialization_function; - Realm::get_shared_realm(config); - REQUIRE(initialization_function_called); - REQUIRE(schema_version_in_callback == 0); - REQUIRE(schema_in_callback.compare(schema).size() == 0); - } - - SECTION("initialization function for versioned realm") { - // Initialize v0 - Realm::get_shared_realm(config); - - config.schema_version = 1; - config.initialization_function = initialization_function; - Realm::get_shared_realm(config); - REQUIRE(initialization_function_called == TestType::should_call_init_on_version_bump()); - if (TestType::should_call_init_on_version_bump()) { - REQUIRE(schema_version_in_callback == 1); - REQUIRE(schema_in_callback.compare(schema).size() == 0); - } - } -} - -TEST_CASE("BindingContext is notified about delivery of change notifications") { - _impl::RealmCoordinator::assert_no_open_realms(); - - InMemoryTestFile config; - config.automatic_change_notifications = false; - - auto r = Realm::get_shared_realm(config); - r->update_schema({ - {"object", { - {"value", PropertyType::Int} - }}, - }); - - auto coordinator = _impl::RealmCoordinator::get_coordinator(config.path); - auto table = r->read_group().get_table("class_object"); - - SECTION("BindingContext notified even if no callbacks are registered") { - static int binding_context_start_notify_calls = 0; - static int binding_context_end_notify_calls = 0; - struct Context : BindingContext { - void will_send_notifications() override - { - ++binding_context_start_notify_calls; - } - - void did_send_notifications() override - { - ++binding_context_end_notify_calls; - } - }; - r->m_binding_context.reset(new Context()); - - SECTION("local commit") { - binding_context_start_notify_calls = 0; - binding_context_end_notify_calls = 0; - coordinator->on_change(); - r->begin_transaction(); - REQUIRE(binding_context_start_notify_calls == 1); - REQUIRE(binding_context_end_notify_calls == 1); - r->cancel_transaction(); - } - - SECTION("remote commit") { - binding_context_start_notify_calls = 0; - binding_context_end_notify_calls = 0; - JoiningThread([&] { - auto r2 = coordinator->get_realm(util::Scheduler::get_frozen()); - r2->begin_transaction(); - auto table2 = r2->read_group().get_table("class_object"); - table2->create_object(); - r2->commit_transaction(); - }); - advance_and_notify(*r); - REQUIRE(binding_context_start_notify_calls == 1); - REQUIRE(binding_context_end_notify_calls == 1); - } - } - - SECTION("notify BindingContext before and after sending notifications") { - static int binding_context_start_notify_calls = 0; - static int binding_context_end_notify_calls = 0; - static int notification_calls = 0; - - auto col = table->get_column_key("value"); - Results results1(r, table->where().greater_equal(col, 0)); - Results results2(r, table->where().less(col, 10)); - - auto token1 = results1.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) { - REQUIRE_FALSE(err); - ++notification_calls; - }); - - auto token2 = results2.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) { - REQUIRE_FALSE(err); - ++notification_calls; - }); - - struct Context : BindingContext { - void will_send_notifications() override - { - REQUIRE(notification_calls == 0); - REQUIRE(binding_context_end_notify_calls == 0); - ++binding_context_start_notify_calls; - } - - void did_send_notifications() override - { - REQUIRE(notification_calls == 2); - REQUIRE(binding_context_start_notify_calls == 1); - ++binding_context_end_notify_calls; - } - }; - r->m_binding_context.reset(new Context()); - - SECTION("local commit") { - binding_context_start_notify_calls = 0; - binding_context_end_notify_calls = 0; - notification_calls = 0; - coordinator->on_change(); - r->begin_transaction(); - table->create_object(); - r->commit_transaction(); - REQUIRE(binding_context_start_notify_calls == 1); - REQUIRE(binding_context_end_notify_calls == 1); - } - - SECTION("remote commit") { - binding_context_start_notify_calls = 0; - binding_context_end_notify_calls = 0; - notification_calls = 0; - JoiningThread([&] { - auto r2 = coordinator->get_realm(util::Scheduler::get_frozen()); - r2->begin_transaction(); - auto table2 = r2->read_group().get_table("class_object"); - table2->create_object(); - r2->commit_transaction(); - }); - advance_and_notify(*r); - REQUIRE(binding_context_start_notify_calls == 1); - REQUIRE(binding_context_end_notify_calls == 1); - } - } - - SECTION("did_send() is skipped if the Realm is closed first") { - Results results(r, table->where()); - bool do_close = true; - auto token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { - if (do_close) - r->close(); - }); - - struct FailOnDidSend : BindingContext { - void did_send_notifications() override - { - FAIL("did_send_notifications() should not have been called"); - } - }; - struct CloseOnWillChange : FailOnDidSend { - Realm& realm; - CloseOnWillChange(Realm& realm) : realm(realm) {} - - void will_send_notifications() override - { - realm.close(); - } - }; - - SECTION("closed in notification callback for notify()") { - r->m_binding_context.reset(new FailOnDidSend); - coordinator->on_change(); - r->notify(); - } - - SECTION("closed in notification callback for refresh()") { - do_close = false; - coordinator->on_change(); - r->notify(); - do_close = true; - - JoiningThread([&] { - auto r = coordinator->get_realm(util::Scheduler::get_frozen()); - r->begin_transaction(); - r->read_group().get_table("class_object")->create_object(); - r->commit_transaction(); - }); - - r->m_binding_context.reset(new FailOnDidSend); - coordinator->on_change(); - r->refresh(); - } - - SECTION("closed in will_send() for notify()") { - r->m_binding_context.reset(new CloseOnWillChange(*r)); - coordinator->on_change(); - r->notify(); - } - - SECTION("closed in will_send() for refresh()") { - do_close = false; - coordinator->on_change(); - r->notify(); - do_close = true; - - JoiningThread([&] { - auto r = coordinator->get_realm(util::Scheduler::get_frozen()); - r->begin_transaction(); - r->read_group().get_table("class_object")->create_object(); - r->commit_transaction(); - }); - - r->m_binding_context.reset(new CloseOnWillChange(*r)); - coordinator->on_change(); - r->refresh(); - } - } -} - -TEST_CASE("Statistics on Realms") { - _impl::RealmCoordinator::assert_no_open_realms(); - - InMemoryTestFile config; - // config.cache = false; - config.automatic_change_notifications = false; - - auto r = Realm::get_shared_realm(config); - r->update_schema({ - {"object", { - {"value", PropertyType::Int} - }}, - }); - - SECTION("compute_size") { - auto s = r->read_group().compute_aggregated_byte_size(); - REQUIRE(s > 0); - } -} - -#if REALM_PLATFORM_APPLE && NOTIFIER_BACKGROUND_ERRORS -TEST_CASE("BindingContext is notified in case of notifier errors") { - _impl::RealmCoordinator::assert_no_open_realms(); - - 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; - }; - - InMemoryTestFile config; - config.automatic_change_notifications = false; - - auto r = Realm::get_shared_realm(config); - r->update_schema({ - {"object", { - {"value", PropertyType::Int} - }}, - }); - - auto coordinator = _impl::RealmCoordinator::get_coordinator(config.path); - auto table = r->read_group().get_table("class_object"); - Results results(r, *r->read_group().get_table("class_object")); - static int binding_context_start_notify_calls = 0; - static int binding_context_end_notify_calls = 0; - static bool error_called = false; - struct Context : BindingContext { - void will_send_notifications() override - { - REQUIRE_FALSE(error_called); - ++binding_context_start_notify_calls; - } - - void did_send_notifications() override - { - REQUIRE(error_called); - ++binding_context_end_notify_calls; - } - }; - r->m_binding_context.reset(new Context()); - - SECTION("realm on background thread could not be opened") { - OpenFileLimiter limiter; - - auto token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) { - REQUIRE(err); - REQUIRE_FALSE(error_called); - error_called = true; - }); - advance_and_notify(*r); - REQUIRE(error_called); - REQUIRE(binding_context_start_notify_calls == 1); - REQUIRE(binding_context_end_notify_calls == 1); - } -} -#endif - -TEST_CASE("RealmCoordinator: get_unbound_realm()") { - TestFile config; - config.schema = Schema{ - {"object", { - {"value", PropertyType::Int} - }}, - }; - - ThreadSafeReference ref; - std::thread([&] { ref = _impl::RealmCoordinator::get_coordinator(config)->get_unbound_realm(); }).join(); - - SECTION("checks thread after being resolved") { - auto realm = Realm::get_shared_realm(std::move(ref)); - REQUIRE_NOTHROW(realm->verify_thread()); - std::thread([&] { - REQUIRE_THROWS(realm->verify_thread()); - }).join(); - } - - SECTION("delivers notifications to the thread it is resolved on") { - if (!util::EventLoop::has_implementation()) - return; - auto realm = Realm::get_shared_realm(std::move(ref)); - Results results(realm, ObjectStore::table_for_object_type(realm->read_group(), "object")->where()); - bool called = false; - auto token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { - called = true; - }); - util::EventLoop::main().run_until([&] { return called; }); - } - - SECTION("resolves to a new Realm if caching is disabled") { - auto r1 = Realm::get_shared_realm(config); - auto r2 = Realm::get_shared_realm(std::move(ref)); - REQUIRE(r1 != r2); - - // New unbound with cache disabled - std::thread([&] { ref = _impl::RealmCoordinator::get_coordinator(config)->get_unbound_realm(); }).join(); - auto r3 = Realm::get_shared_realm(std::move(ref)); - REQUIRE(r1 != r3); - REQUIRE(r2 != r3); - } -} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/results.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/results.cpp deleted file mode 100644 index 5327a050c..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/results.cpp +++ /dev/null @@ -1,3261 +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 "catch2/catch.hpp" - -#include "util/event_loop.hpp" -#include "util/index_helpers.hpp" -#include "util/test_file.hpp" - -#include "impl/object_accessor_impl.hpp" -#include "impl/realm_coordinator.hpp" -#include "binding_context.hpp" -#include "object_schema.hpp" -#include "property.hpp" -#include "results.hpp" -#include "schema.hpp" -#include "util/scheduler.hpp" - -#include <realm/db.hpp> -#include <realm/group.hpp> -#include <realm/query_engine.hpp> -#include <realm/query_expression.hpp> - -#if REALM_ENABLE_SYNC -#include "sync/sync_manager.hpp" -#include "sync/sync_session.hpp" -#endif - -namespace realm { -class TestHelper { -public: - static DBRef& get_shared_group(SharedRealm const& shared_realm) - { - return Realm::Internal::get_db(*shared_realm); - } -}; -} - -using namespace realm; -using namespace std::string_literals; - -namespace { - using AnyDict = std::map<std::string, util::Any>; - using AnyVec = std::vector<util::Any>; -} - -struct TestContext : CppContext { - std::map<std::string, AnyDict> defaults; - - using CppContext::CppContext; - TestContext(TestContext& parent, realm::Property const& prop) - : CppContext(parent, prop) - , defaults(parent.defaults) - { } - - void will_change(Object const&, Property const&) {} - void did_change() {} - std::string print(util::Any) { return "not implemented"; } - bool allow_missing(util::Any) { return false; } -}; - - -TEST_CASE("notifications: async delivery") { - _impl::RealmCoordinator::assert_no_open_realms(); - - InMemoryTestFile config; - config.automatic_change_notifications = false; - - auto r = Realm::get_shared_realm(config); - r->update_schema({ - {"object", { - {"value", PropertyType::Int} - }}, - }); - - auto coordinator = _impl::RealmCoordinator::get_coordinator(config.path); - auto table = r->read_group().get_table("class_object"); - auto col = table->get_column_key("value"); - - r->begin_transaction(); - for (int i = 0; i < 10; ++i) - table->create_object().set_all(i * 2); - r->commit_transaction(); - - Results results(r, table->where().greater(col, 0).less(col, 10)); - - int notification_calls = 0; - auto token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) { - REQUIRE_FALSE(err); - ++notification_calls; - }); - - auto make_local_change = [&] { - r->begin_transaction(); - table->begin()->set(col, 4); - r->commit_transaction(); - }; - - auto make_remote_change = [&] { - auto r2 = coordinator->get_realm(util::Scheduler::get_frozen()); - r2->begin_transaction(); - r2->read_group().get_table("class_object")->begin()->set(col, 5); - r2->commit_transaction(); - }; - - SECTION("initial notification") { - SECTION("is delivered on notify()") { - REQUIRE(notification_calls == 0); - advance_and_notify(*r); - REQUIRE(notification_calls == 1); - } - - SECTION("is delivered on refresh()") { - coordinator->on_change(); - REQUIRE(notification_calls == 0); - r->refresh(); - REQUIRE(notification_calls == 1); - } - - SECTION("is delivered on begin_transaction()") { - coordinator->on_change(); - REQUIRE(notification_calls == 0); - r->begin_transaction(); - REQUIRE(notification_calls == 1); - r->cancel_transaction(); - } - - SECTION("is delivered on notify() even with autorefresh disabled") { - r->set_auto_refresh(false); - REQUIRE(notification_calls == 0); - advance_and_notify(*r); - REQUIRE(notification_calls == 1); - } - - SECTION("refresh() blocks due to initial results not being ready") { - REQUIRE(notification_calls == 0); - JoiningThread thread([&] { - std::this_thread::sleep_for(std::chrono::microseconds(5000)); - coordinator->on_change(); - }); - r->refresh(); - REQUIRE(notification_calls == 1); - } - - SECTION("begin_transaction() blocks due to initial results not being ready") { - REQUIRE(notification_calls == 0); - JoiningThread thread([&] { - std::this_thread::sleep_for(std::chrono::microseconds(5000)); - coordinator->on_change(); - }); - r->begin_transaction(); - REQUIRE(notification_calls == 1); - r->cancel_transaction(); - } - - SECTION("notify() does not block due to initial results not being ready") { - REQUIRE(notification_calls == 0); - r->notify(); - REQUIRE(notification_calls == 0); - } - - SECTION("is delivered after invalidate()") { - r->invalidate(); - - SECTION("notify()") { - coordinator->on_change(); - REQUIRE_FALSE(r->is_in_read_transaction()); - r->notify(); - REQUIRE(notification_calls == 1); - } - - SECTION("notify() without autorefresh") { - r->set_auto_refresh(false); - coordinator->on_change(); - REQUIRE_FALSE(r->is_in_read_transaction()); - r->notify(); - REQUIRE(notification_calls == 1); - } - - SECTION("refresh()") { - coordinator->on_change(); - REQUIRE_FALSE(r->is_in_read_transaction()); - r->refresh(); - REQUIRE(notification_calls == 1); - } - - SECTION("begin_transaction()") { - coordinator->on_change(); - REQUIRE_FALSE(r->is_in_read_transaction()); - r->begin_transaction(); - REQUIRE(notification_calls == 1); - r->cancel_transaction(); - } - } - - SECTION("is delivered by notify() even if there are later versions") { - REQUIRE(notification_calls == 0); - coordinator->on_change(); - make_remote_change(); - r->notify(); - REQUIRE(notification_calls == 1); - } - } - - advance_and_notify(*r); - - SECTION("notifications for local changes") { - make_local_change(); - coordinator->on_change(); - REQUIRE(notification_calls == 1); - - SECTION("notify()") { - r->notify(); - REQUIRE(notification_calls == 2); - } - - SECTION("notify() without autorefresh") { - r->set_auto_refresh(false); - r->notify(); - REQUIRE(notification_calls == 2); - } - - SECTION("refresh()") { - r->refresh(); - REQUIRE(notification_calls == 2); - } - - SECTION("begin_transaction()") { - r->begin_transaction(); - REQUIRE(notification_calls == 2); - r->cancel_transaction(); - } - } - - SECTION("notifications for remote changes") { - make_remote_change(); - coordinator->on_change(); - REQUIRE(notification_calls == 1); - - SECTION("notify()") { - r->notify(); - REQUIRE(notification_calls == 2); - } - - SECTION("notify() without autorefresh") { - r->set_auto_refresh(false); - r->notify(); - REQUIRE(notification_calls == 1); - r->refresh(); - REQUIRE(notification_calls == 2); - } - - SECTION("refresh()") { - r->refresh(); - REQUIRE(notification_calls == 2); - } - - SECTION("begin_transaction()") { - r->begin_transaction(); - REQUIRE(notification_calls == 2); - r->cancel_transaction(); - } - } - - SECTION("notifications are not delivered when the token is destroyed before they are calculated") { - make_remote_change(); - 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") { - make_remote_change(); - REQUIRE(notification_calls == 1); - coordinator->on_change(); - token = {}; - r->notify(); - REQUIRE(notification_calls == 1); - } - - SECTION("notifications are delivered on the next cycle 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) { - token2 = {}; - token3 = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { - called = true; - }); - }); - - advance_and_notify(*r); - REQUIRE_FALSE(called); - advance_and_notify(*r); - REQUIRE(called); - } - - SECTION("notifications are delivered on the next cycle when a new callback is added from within a callback") { - auto results2 = results; - auto results3 = results; - NotificationToken token2, token3, token4; - - bool called = false; - auto check = [&](Results& outer, Results& inner) { - token2 = outer.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { - token2 = {}; - token3 = inner.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { - called = true; - }); - }); - - advance_and_notify(*r); - REQUIRE_FALSE(called); - advance_and_notify(*r); - REQUIRE(called); - }; - - SECTION("same Results") { - check(results, results); - } - - SECTION("Results which has never had a notifier") { - check(results, results2); - } - - SECTION("Results which used to have callbacks but no longer does") { - SECTION("notifier before active") { - token3 = results2.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { - token3 = {}; - }); - check(results3, results2); - } - SECTION("notifier after active") { - token3 = results2.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { - token3 = {}; - }); - check(results, results2); - } - } - - SECTION("Results which already has callbacks") { - SECTION("notifier before active") { - token4 = results2.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { }); - check(results3, results2); - } - SECTION("notifier after active") { - token4 = results2.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { }); - check(results, results2); - } - } - } - - SECTION("remote changes made before adding a callback from within a callback are not reported") { - NotificationToken token2, token3; - bool called = false; - token2 = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { - token2 = {}; - make_remote_change(); - coordinator->on_change(); - token3 = results.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) { - called = true; - REQUIRE(c.empty()); - REQUIRE(table->begin()->get<int64_t>(col) == 5); - }); - }); - - advance_and_notify(*r); - REQUIRE_FALSE(called); - 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("the first call of a notification can include changes if it previously ran for a different callback") { - r->begin_transaction(); - auto token2 = results.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) { - REQUIRE(!c.empty()); - }); - - table->create_object().set(col, 5); - r->commit_transaction(); - advance_and_notify(*r); - } - - SECTION("handling of results not ready") { - make_remote_change(); - - SECTION("notify() does nothing") { - r->notify(); - REQUIRE(notification_calls == 1); - coordinator->on_change(); - r->notify(); - REQUIRE(notification_calls == 2); - } - - SECTION("refresh() blocks") { - REQUIRE(notification_calls == 1); - JoiningThread thread([&] { - std::this_thread::sleep_for(std::chrono::microseconds(5000)); - coordinator->on_change(); - }); - r->refresh(); - REQUIRE(notification_calls == 2); - } - - SECTION("refresh() advances to the first version with notifiers ready that is at least a recent as the newest at the time it is called") { - JoiningThread thread([&] { - std::this_thread::sleep_for(std::chrono::microseconds(5000)); - make_remote_change(); - coordinator->on_change(); - make_remote_change(); - }); - // advances to the version after the one it was waiting for, but still - // not the latest - r->refresh(); - REQUIRE(notification_calls == 2); - - thread.join(); - REQUIRE(notification_calls == 2); - - // now advances to the latest - coordinator->on_change(); - r->refresh(); - REQUIRE(notification_calls == 3); - } - - SECTION("begin_transaction() blocks") { - REQUIRE(notification_calls == 1); - JoiningThread thread([&] { - std::this_thread::sleep_for(std::chrono::microseconds(5000)); - coordinator->on_change(); - }); - r->begin_transaction(); - REQUIRE(notification_calls == 2); - r->cancel_transaction(); - } - - SECTION("refresh() does not block for results without callbacks") { - token = {}; - // this would deadlock if it waits for the notifier to be ready - r->refresh(); - } - - SECTION("begin_transaction() does not block for results without callbacks") { - token = {}; - // this would deadlock if it waits for the notifier to be ready - r->begin_transaction(); - r->cancel_transaction(); - } - - SECTION("begin_transaction() does not block for Results for different Realms") { - // this would deadlock if beginning the write on the secondary Realm - // waited for the primary Realm to be ready - make_remote_change(); - - // sanity check that the notifications never did run - r->notify(); - REQUIRE(notification_calls == 1); - } - } - - SECTION("handling of stale results") { - make_remote_change(); - coordinator->on_change(); - make_remote_change(); - - SECTION("notify() uses the older version") { - r->notify(); - REQUIRE(notification_calls == 2); - coordinator->on_change(); - r->notify(); - REQUIRE(notification_calls == 3); - r->notify(); - REQUIRE(notification_calls == 3); - } - - SECTION("refresh() blocks") { - REQUIRE(notification_calls == 1); - JoiningThread thread([&] { - std::this_thread::sleep_for(std::chrono::microseconds(5000)); - coordinator->on_change(); - }); - r->refresh(); - REQUIRE(notification_calls == 2); - } - - SECTION("begin_transaction() blocks") { - REQUIRE(notification_calls == 1); - JoiningThread thread([&] { - std::this_thread::sleep_for(std::chrono::microseconds(5000)); - coordinator->on_change(); - }); - r->begin_transaction(); - REQUIRE(notification_calls == 2); - r->cancel_transaction(); - } - } - - SECTION("updates are delivered after invalidate()") { - r->invalidate(); - make_remote_change(); - - SECTION("notify()") { - coordinator->on_change(); - REQUIRE_FALSE(r->is_in_read_transaction()); - r->notify(); - REQUIRE(notification_calls == 2); - } - - SECTION("notify() without autorefresh") { - r->set_auto_refresh(false); - coordinator->on_change(); - REQUIRE_FALSE(r->is_in_read_transaction()); - r->notify(); - REQUIRE(notification_calls == 1); - r->refresh(); - REQUIRE(notification_calls == 2); - } - - SECTION("refresh()") { - coordinator->on_change(); - REQUIRE_FALSE(r->is_in_read_transaction()); - r->refresh(); - REQUIRE(notification_calls == 2); - } - - SECTION("begin_transaction()") { - coordinator->on_change(); - REQUIRE_FALSE(r->is_in_read_transaction()); - r->begin_transaction(); - REQUIRE(notification_calls == 2); - r->cancel_transaction(); - } - } - - SECTION("refresh() from within changes_available() do not interfere with notification delivery") { - struct Context : BindingContext { - Realm& realm; - Context(Realm& realm) : realm(realm) { } - - void changes_available() override - { - REQUIRE(realm.refresh()); - } - }; - - make_remote_change(); - coordinator->on_change(); - - r->set_auto_refresh(false); - REQUIRE(notification_calls == 1); - - r->notify(); - REQUIRE(notification_calls == 1); - - r->m_binding_context.reset(new Context(*r)); - r->notify(); - REQUIRE(notification_calls == 2); - } - - SECTION("refresh() from within a notification is a no-op") { - token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) { - REQUIRE_FALSE(err); - REQUIRE_FALSE(r->refresh()); // would deadlock if it actually tried to refresh - }); - advance_and_notify(*r); - make_remote_change(); // 1 - coordinator->on_change(); - make_remote_change(); // 2 - r->notify(); // advances to version from 1 - coordinator->on_change(); - REQUIRE(r->refresh()); // advances to version from 2 - REQUIRE_FALSE(r->refresh()); // does not advance since it's now up-to-date - } - - SECTION("begin_transaction() from within a notification does not send notifications immediately") { - bool first = true; - auto token2 = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) { - REQUIRE_FALSE(err); - if (first) - first = false; - else { - // would deadlock if it tried to send notifications as they aren't ready yet - r->begin_transaction(); - r->cancel_transaction(); - } - }); - advance_and_notify(*r); - - make_remote_change(); // 1 - coordinator->on_change(); - make_remote_change(); // 2 - r->notify(); // advances to version from 1 - REQUIRE(notification_calls == 2); - coordinator->on_change(); - REQUIRE_FALSE(r->refresh()); // we made the commit locally, so no advancing here - REQUIRE(notification_calls == 3); - } - - SECTION("begin_transaction() from within a notification does not break delivering additional notifications") { - size_t calls = 0; - token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) { - REQUIRE_FALSE(err); - if (++calls == 1) - return; - - // force the read version to advance by beginning a transaction - r->begin_transaction(); - r->cancel_transaction(); - }); - - auto results2 = results; - size_t calls2 = 0; - auto token2 = results2.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr err) { - REQUIRE_FALSE(err); - if (++calls2 == 1) - return; - REQUIRE_INDICES(c.insertions, 0); - }); - advance_and_notify(*r); - REQUIRE(calls == 1); - REQUIRE(calls2 == 1); - - make_remote_change(); // 1 - coordinator->on_change(); - make_remote_change(); // 2 - r->notify(); // advances to version from 1 - - REQUIRE(calls == 2); - REQUIRE(calls2 == 2); - } - - SECTION("begin_transaction() from within did_change() does not break delivering collection notification") { - struct Context : BindingContext { - Realm& realm; - Context(Realm& realm) : realm(realm) { } - - void did_change(std::vector<ObserverState> const&, std::vector<void*> const&, bool) override - { - if (!realm.is_in_transaction()) { - // advances to version from 2 (and recursively calls this, hence the check above) - realm.begin_transaction(); - realm.cancel_transaction(); - } - } - }; - r->m_binding_context.reset(new Context(*r)); - - make_remote_change(); // 1 - coordinator->on_change(); - make_remote_change(); // 2 - r->notify(); // advances to version from 1 - } - - SECTION("is_in_transaction() is reported correctly within a notification from begin_transaction() and changes can be made") { - bool first = true; - token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) { - REQUIRE_FALSE(err); - if (first) { - REQUIRE_FALSE(r->is_in_transaction()); - first = false; - } - else { - REQUIRE(r->is_in_transaction()); - table->begin()->set(col, 100); - } - }); - advance_and_notify(*r); - make_remote_change(); - coordinator->on_change(); - r->begin_transaction(); - REQUIRE(table->begin()->get<int64_t>(col) == 100); - r->cancel_transaction(); - REQUIRE(table->begin()->get<int64_t>(col) != 100); - } - - SECTION("invalidate() from within notification is a no-op") { - token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) { - REQUIRE_FALSE(err); - r->invalidate(); - REQUIRE(r->is_in_read_transaction()); - }); - advance_and_notify(*r); - REQUIRE(r->is_in_read_transaction()); - make_remote_change(); - coordinator->on_change(); - r->begin_transaction(); - REQUIRE(r->is_in_transaction()); - r->cancel_transaction(); - } - - SECTION("cancel_transaction() from within notification ends the write transaction started by begin_transaction()") { - token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) { - REQUIRE_FALSE(err); - if (r->is_in_transaction()) - r->cancel_transaction(); - }); - advance_and_notify(*r); - make_remote_change(); - coordinator->on_change(); - r->begin_transaction(); - REQUIRE_FALSE(r->is_in_transaction()); - } -} - -TEST_CASE("notifications: skip") { - _impl::RealmCoordinator::assert_no_open_realms(); - - InMemoryTestFile config; - config.automatic_change_notifications = false; - - auto r = Realm::get_shared_realm(config); - r->update_schema({ - {"object", { - {"value", PropertyType::Int} - }}, - }); - - auto coordinator = _impl::RealmCoordinator::get_coordinator(config.path); - auto table = r->read_group().get_table("class_object"); - auto col = table->get_column_key("value"); - - r->begin_transaction(); - for (int i = 0; i < 10; ++i) - table->create_object().set(col, i * 2); - r->commit_transaction(); - - Results results(r, table->where()); - - auto add_callback = [](Results& results, int& calls, CollectionChangeSet& changes) { - return results.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr err) { - REQUIRE_FALSE(err); - ++calls; - changes = std::move(c); - }); - }; - - auto make_local_change = [&](auto& token) { - r->begin_transaction(); - table->create_object(); - token.suppress_next(); - r->commit_transaction(); - }; - - auto make_remote_change = [&] { - auto r2 = coordinator->get_realm(util::Scheduler::get_frozen()); - r2->begin_transaction(); - r2->read_group().get_table("class_object")->create_object(); - r2->commit_transaction(); - }; - - int calls1 = 0; - CollectionChangeSet changes1; - auto token1 = add_callback(results, calls1, changes1); - - SECTION("no notification is sent when only callback is skipped") { - advance_and_notify(*r); - REQUIRE(calls1 == 1); - - make_local_change(token1); - advance_and_notify(*r); - - REQUIRE(calls1 == 1); - REQUIRE(changes1.empty()); - } - - SECTION("unskipped tokens for the same Results are still delivered") { - int calls2 = 0; - CollectionChangeSet changes2; - auto token2 = add_callback(results, calls2, changes2); - - advance_and_notify(*r); - REQUIRE(calls1 == 1); - REQUIRE(calls2 == 1); - - make_local_change(token1); - advance_and_notify(*r); - - REQUIRE(calls1 == 1); - REQUIRE(changes1.empty()); - REQUIRE(calls2 == 2); - REQUIRE_INDICES(changes2.insertions, 10); - } - - SECTION("unskipped tokens for different Results are still delivered") { - Results results2(r, table->where()); - int calls2 = 0; - CollectionChangeSet changes2; - auto token2 = add_callback(results2, calls2, changes2); - - advance_and_notify(*r); - REQUIRE(calls1 == 1); - REQUIRE(calls2 == 1); - - make_local_change(token1); - advance_and_notify(*r); - - REQUIRE(calls1 == 1); - REQUIRE(changes1.empty()); - REQUIRE(calls2 == 2); - REQUIRE_INDICES(changes2.insertions, 10); - } - - SECTION("additional commits which occur before calculation are merged in") { - int calls2 = 0; - CollectionChangeSet changes2; - auto token2 = add_callback(results, calls2, changes2); - - advance_and_notify(*r); - REQUIRE(calls1 == 1); - REQUIRE(calls2 == 1); - - make_local_change(token1); - make_remote_change(); - advance_and_notify(*r); - - REQUIRE(calls1 == 2); - REQUIRE_INDICES(changes1.insertions, 11); - REQUIRE(calls2 == 2); - REQUIRE_INDICES(changes2.insertions, 10, 11); - } - - SECTION("additional commits which occur before delivery are merged in") { - int calls2 = 0; - CollectionChangeSet changes2; - auto token2 = add_callback(results, calls2, changes2); - - advance_and_notify(*r); - REQUIRE(calls1 == 1); - REQUIRE(calls2 == 1); - - make_local_change(token1); - coordinator->on_change(); - make_remote_change(); - advance_and_notify(*r); - - REQUIRE(calls1 == 2); - REQUIRE_INDICES(changes1.insertions, 11); - REQUIRE(calls2 == 2); - REQUIRE_INDICES(changes2.insertions, 10, 11); - } - - SECTION("skipping must be done from within a write transaction") { - REQUIRE_THROWS(token1.suppress_next()); - } - - SECTION("skipping must be done from the Realm's thread") { - advance_and_notify(*r); - r->begin_transaction(); - std::thread([&] { - REQUIRE_THROWS(token1.suppress_next()); - }).join(); - r->cancel_transaction(); - } - - SECTION("new notifiers do not interfere with skipping") { - advance_and_notify(*r); - REQUIRE(calls1 == 1); - - CollectionChangeSet changes; - - // new notifier at a version before the skipped one - auto r2 = coordinator->get_realm(); - Results results2(r2, r2->read_group().get_table("class_object")->where()); - int calls2 = 0; - auto token2 = add_callback(results2, calls2, changes); - - make_local_change(token1); - - // new notifier at the skipped version - auto r3 = coordinator->get_realm(); - Results results3(r3, r3->read_group().get_table("class_object")->where()); - int calls3 = 0; - auto token3 = add_callback(results3, calls3, changes); - - make_remote_change(); - - // new notifier at version after the skipped one - auto r4 = coordinator->get_realm(); - Results results4(r4, r4->read_group().get_table("class_object")->where()); - int calls4 = 0; - auto token4 = add_callback(results4, calls4, changes); - - coordinator->on_change(); - r->notify(); - r2->notify(); - r3->notify(); - r4->notify(); - - REQUIRE(calls1 == 2); - REQUIRE(calls2 == 1); - REQUIRE(calls3 == 1); - REQUIRE(calls4 == 1); - } - - SECTION("skipping only effects the current transaction even if no notification would occur anyway") { - advance_and_notify(*r); - REQUIRE(calls1 == 1); - - // would not produce a notification even if it wasn't skipped because no changes were made - r->begin_transaction(); - token1.suppress_next(); - r->commit_transaction(); - advance_and_notify(*r); - REQUIRE(calls1 == 1); - - // should now produce a notification - r->begin_transaction(); - table->create_object(); - r->commit_transaction(); - advance_and_notify(*r); - REQUIRE(calls1 == 2); - } - - SECTION("removing skipped notifier before it gets the chance to run") { - advance_and_notify(*r); - REQUIRE(calls1 == 1); - - // Set the skip version - make_local_change(token1); - // Advance the file to a version after the skip version - make_remote_change(); - REQUIRE(calls1 == 1); - - // Remove the skipped notifier and add an entirely new notifier, so that - // notifications need to run but the skip logic shouldn't be used - token1 = {}; - results = {}; - Results results2(r, table->where()); - auto token2 = add_callback(results2, calls1, changes1); - - advance_and_notify(*r); - REQUIRE(calls1 == 2); - } -} - -TEST_CASE("notifications: TableView delivery") { - _impl::RealmCoordinator::assert_no_open_realms(); - - InMemoryTestFile config; - config.automatic_change_notifications = false; - config.max_number_of_active_versions = 5; - - auto r = Realm::get_shared_realm(config); - r->update_schema({ - {"object", { - {"value", PropertyType::Int} - }}, - }); - - auto coordinator = _impl::RealmCoordinator::get_coordinator(config.path); - auto table = r->read_group().get_table("class_object"); - auto col = table->get_column_key("value"); - - r->begin_transaction(); - for (int i = 0; i < 10; ++i) - table->create_object().set(col, i * 2); - r->commit_transaction(); - - Results results(r, table->where()); - results.set_update_policy(Results::UpdatePolicy::AsyncOnly); - - SECTION("Initial run never happens with no callbacks") { - advance_and_notify(*r); - REQUIRE(results.get_mode() == Results::Mode::Query); - } - - results.evaluate_query_if_needed(); - // Create and immediately remove a callback so that the notifier gets created - // even though we have automatic change notifications disabled - static_cast<void>(results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {})); - REQUIRE(results.get_mode() == Results::Mode::TableView); - REQUIRE(results.size() == 0); - - auto make_local_change = [&] { - r->begin_transaction(); - table->create_object(); - r->commit_transaction(); - }; - - auto make_remote_change = [&] { - auto r2 = coordinator->get_realm(util::Scheduler::get_frozen()); - r2->begin_transaction(); - r2->read_group().get_table("class_object")->create_object(); - r2->commit_transaction(); - }; - - SECTION("does not update after local change with no on_change") { - make_local_change(); - REQUIRE(results.size() == 0); - } - - SECTION("TV is delivered when no commit is made") { - advance_and_notify(*r); - REQUIRE(results.get_mode() == Results::Mode::TableView); - REQUIRE(results.size() == 10); - } - - SECTION("TV is not delivered when notifier version > local version") { - make_remote_change(); - r->refresh(); - REQUIRE(results.size() == 0); - } - - SECTION("TV is delivered when notifier version = local version") { - make_remote_change(); - advance_and_notify(*r); - REQUIRE(results.size() == 11); - } - - SECTION("TV is delivered when previous TV wasn't used due to never refreshing") { - // These two generate TVs that never get used - make_remote_change(); - on_change_but_no_notify(*r); - make_remote_change(); - on_change_but_no_notify(*r); - - // But we generate a third one anyway because the main thread never even - // got a chance to use them, rather than it not wanting them - make_remote_change(); - advance_and_notify(*r); - - REQUIRE(results.size() == 13); - } - - SECTION("TV is not delivered when main thread refreshed but previous TV was not used") { - // First run generates a TV that's unused - make_remote_change(); - advance_and_notify(*r); - - // When the second run is delivered we discover first run wasn't used - make_remote_change(); - advance_and_notify(*r); - - // And then third one doesn't run at all - make_remote_change(); - advance_and_notify(*r); - - // And we can't use the old TV because it's out of date - REQUIRE(results.size() == 0); - - // We don't start implicitly updating again even after it is used - make_remote_change(); - advance_and_notify(*r); - REQUIRE(results.size() == 0); - } - - SECTION("TV can be delivered in a write transaction") { - make_remote_change(); - advance_and_notify(*r); - r->begin_transaction(); - REQUIRE(results.size() == 11); - r->cancel_transaction(); - } - - SECTION("unused background TVs do not pin old versions forever") { - // This will exceed the maximum active version count (5) if any - // transactions are being pinned, resulting in make_remote_change() throwing - for (int i = 0; i < 10; ++i) { - REQUIRE_NOTHROW(make_remote_change()); - advance_and_notify(*r); - } - } -} - - -#if REALM_PLATFORM_APPLE && NOTIFIER_BACKGROUND_ERRORS -TEST_CASE("notifications: async error handling") { - _impl::RealmCoordinator::assert_no_open_realms(); - - InMemoryTestFile config; - config.automatic_change_notifications = false; - - auto r = Realm::get_shared_realm(config); - r->update_schema({ - {"object", { - {"value", PropertyType::Int}, - }}, - }); - - auto coordinator = _impl::RealmCoordinator::get_coordinator(config.path); - Results results(r, *r->read_group().get_table("class_object")); - - auto r2 = Realm::get_shared_realm(config); - - 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; - - bool called = false; - auto token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) { - REQUIRE(err); - REQUIRE_FALSE(called); - called = true; - }); - REQUIRE(!called); - - SECTION("error is delivered on notify() without changes") { - coordinator->on_change(); - REQUIRE(!called); - r->notify(); - REQUIRE(called); - } - - SECTION("error is delivered on notify() with changes") { - r2->begin_transaction(); r2->commit_transaction(); - REQUIRE(!called); - coordinator->on_change(); - REQUIRE(!called); - r->notify(); - REQUIRE(called); - } - - SECTION("error is delivered on refresh() without changes") { - coordinator->on_change(); - REQUIRE(!called); - r->refresh(); - REQUIRE(called); - } - - SECTION("error is delivered on refresh() with changes") { - r2->begin_transaction(); r2->commit_transaction(); - REQUIRE(!called); - coordinator->on_change(); - REQUIRE(!called); - r->refresh(); - REQUIRE(called); - } - - SECTION("error is delivered on begin_transaction() without changes") { - coordinator->on_change(); - REQUIRE(!called); - r->begin_transaction(); - REQUIRE(called); - r->cancel_transaction(); - } - - SECTION("error is delivered on begin_transaction() with changes") { - r2->begin_transaction(); r2->commit_transaction(); - REQUIRE(!called); - coordinator->on_change(); - REQUIRE(!called); - r->begin_transaction(); - REQUIRE(called); - r->cancel_transaction(); - } - - SECTION("adding another callback sends the error to only the newly added one") { - advance_and_notify(*r); - REQUIRE(called); - - 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("destroying a token from before the error does not remove newly added callbacks") { - advance_and_notify(*r); - - bool called = false; - auto token2 = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) { - REQUIRE(err); - REQUIRE_FALSE(called); - called = true; - }); - token = {}; - - advance_and_notify(*r); - REQUIRE(called); - } - - SECTION("adding another callback from within an error callback defers delivery") { - NotificationToken token2; - token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { - token2 = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) { - REQUIRE(err); - REQUIRE_FALSE(called); - called = true; - }); - }); - advance_and_notify(*r); - REQUIRE(!called); - advance_and_notify(*r); - REQUIRE(called); - } - - SECTION("adding a callback to a different collection from within the error callback defers delivery") { - auto results2 = results; - NotificationToken token2; - token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { - token2 = results2.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) { - REQUIRE(err); - REQUIRE_FALSE(called); - called = true; - }); - }); - advance_and_notify(*r); - REQUIRE(!called); - advance_and_notify(*r); - REQUIRE(called); - } - } - - 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 only sends the error to the new one") { - 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); - } - } -} -#endif - -#if REALM_ENABLE_SYNC -TEST_CASE("notifications: sync") { - _impl::RealmCoordinator::assert_no_open_realms(); - - SyncServer server(false); - SyncTestFile config(server); - config.schema = Schema{ - {"object", { - {"value", PropertyType::Int}, - }}, - }; - - SECTION("sync progress commits do not distrupt notifications") { - auto r = Realm::get_shared_realm(config); - auto wait_realm = Realm::get_shared_realm(config); - - Results results(r, r->read_group().get_table("class_object")); - Results wait_results(wait_realm, wait_realm->read_group().get_table("class_object")); - auto token1 = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { }); - auto token2 = wait_results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) { }); - - // Add an object to the Realm so that notifications are needed - { - auto write_realm = Realm::get_shared_realm(config); - write_realm->begin_transaction(); - write_realm->read_group().get_table("class_object")->create_object(); - write_realm->commit_transaction(); - } - - // Wait for the notifications to become ready for the new version - wait_realm->refresh(); - - // Start the server and wait for the Realm to be uploaded so that sync - // makes some writes to the Realm and bumps the version - server.start(); - wait_for_upload(*r); - - // Make sure that the notifications still get delivered rather than - // waiting forever due to that we don't get a commit notification from - // the commits sync makes to store the upload progress - r->refresh(); - } -} -#endif - -TEST_CASE("notifications: results") { - _impl::RealmCoordinator::assert_no_open_realms(); - - InMemoryTestFile config; - config.automatic_change_notifications = false; - - auto r = Realm::get_shared_realm(config); - r->update_schema({ - {"object", { - {"value", PropertyType::Int}, - {"link", PropertyType::Object|PropertyType::Nullable, "linked to object"} - }}, - {"other object", { - {"value", PropertyType::Int} - }}, - {"linking object", { - {"link", PropertyType::Object|PropertyType::Nullable, "object"} - }}, - {"linked to object", { - {"value", PropertyType::Int} - }} - }); - - auto coordinator = _impl::RealmCoordinator::get_coordinator(config.path); - auto table = r->read_group().get_table("class_object"); - auto col_value = table->get_column_key("value"); - auto col_link = table->get_column_key("link"); - - r->begin_transaction(); - std::vector<ObjKey> target_keys; - r->read_group().get_table("class_linked to object")->create_objects(10, target_keys); - - ObjKeys object_keys({3, 4, 7, 9, 10, 21, 24, 34, 42, 50}); - for (int i = 0; i < 10; ++i) { - table->create_object(object_keys[i]).set_all(i * 2, target_keys[i]); - } - r->commit_transaction(); - - auto r2 = coordinator->get_realm(); - auto r2_table = r2->read_group().get_table("class_object"); - - Results results(r, table->where().greater(col_value, 0).less(col_value, 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("modifications to unrelated tables do not send notifications") { - write([&] { - r->read_group().get_table("class_other object")->create_object(); - }); - REQUIRE(notification_calls == 1); - } - - SECTION("irrelevant modifications to linked tables do not send notifications") { - write([&] { - r->read_group().get_table("class_linked to object")->create_object(); - }); - REQUIRE(notification_calls == 1); - } - - SECTION("irrelevant modifications to linking tables do not send notifications") { - write([&] { - r->read_group().get_table("class_linking object")->create_object(); - }); - REQUIRE(notification_calls == 1); - } - - SECTION("modifications that leave a non-matching row non-matching do not send notifications") { - write([&] { - table->get_object(object_keys[6]).set(col_value, 13); - }); - REQUIRE(notification_calls == 1); - } - - SECTION("deleting non-matching rows does not send a notification") { - write([&] { - table->remove_object(object_keys[0]); - table->remove_object(object_keys[6]); - }); - REQUIRE(notification_calls == 1); - } - - SECTION("modifying a matching row and leaving it matching marks that row as modified") { - write([&] { - table->get_object(object_keys[1]).set(col_value, 3); - }); - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.modifications, 0); - REQUIRE_INDICES(change.modifications_new, 0); - } - - SECTION("modifying a matching row to no longer match marks that row as deleted") { - write([&] { - table->get_object(object_keys[2]).set(col_value, 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->get_object(object_keys[7]).set(col_value, 3); - }); - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.insertions, 4); - REQUIRE(change.modifications.empty()); - REQUIRE(change.modifications_new.empty()); - } - - SECTION("deleting a matching row marks that row as deleted") { - write([&] { - table->remove_object(object_keys[3]); - }); - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.deletions, 2); - } - - SECTION("modifications from multiple transactions are collapsed") { - r2->begin_transaction(); - r2_table->get_object(object_keys[0]).set(col_value, 6); - r2->commit_transaction(); - - coordinator->on_change(); - - r2->begin_transaction(); - r2_table->get_object(object_keys[1]).set(col_value,03); - r2->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") { - r2->begin_transaction(); - ObjKey k = r2_table->create_object(ObjKey(53)).set(col_value, 6).get_key(); - r2->commit_transaction(); - - coordinator->on_change(); - - r2->begin_transaction(); - r2_table->get_object(k).set(col_value, 7); - r2->commit_transaction(); - - advance_and_notify(*r); - - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.insertions, 4); - REQUIRE(change.modifications.empty()); - REQUIRE(change.modifications_new.empty()); - } - - SECTION("modification indices are pre-insert/delete") { - r->begin_transaction(); - table->get_object(object_keys[2]).set(col_value, 0); - table->get_object(object_keys[3]).set(col_value, 6); - r->commit_transaction(); - advance_and_notify(*r); - - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.deletions, 1); - REQUIRE_INDICES(change.modifications, 2); - REQUIRE_INDICES(change.modifications_new, 1); - } - - SECTION("notifications are not delivered when collapsing transactions results in no net change") { - r2->begin_transaction(); - ObjKey k = r2_table->create_object().set(col_value, 5).get_key(); - r2->commit_transaction(); - - coordinator->on_change(); - - r2->begin_transaction(); - r2_table->remove_object(k); - r2->commit_transaction(); - - REQUIRE(notification_calls == 1); - coordinator->on_change(); - r->notify(); - REQUIRE(notification_calls == 1); - } - - SECTION("inserting a non-matching row at the beginning does not produce a notification") { - write([&] { - table->create_object(ObjKey(1)); - }); - REQUIRE(notification_calls == 1); - } - - SECTION("inserting a matching row at the beginning marks just it as inserted") { - write([&] { - table->create_object(ObjKey(0)).set(col_value, 5); - }); - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.insertions, 0); - } - - SECTION("modification to related table not included in query") { - write([&] { - auto table = r->read_group().get_table("class_linked to object"); - auto col = table->get_column_key("value"); - auto obj = table->get_object(target_keys[1]); - obj.set(col, 42); // Will affect first entry in results - }); - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.modifications, 0); - } - } - - SECTION("before/after change callback") { - struct Callback { - size_t before_calls = 0; - size_t after_calls = 0; - CollectionChangeSet before_change; - CollectionChangeSet after_change; - std::function<void(void)> on_before = []{}; - std::function<void(void)> on_after = []{}; - - void before(CollectionChangeSet c) { - before_change = c; - ++before_calls; - on_before(); - } - void after(CollectionChangeSet c) { - after_change = c; - ++after_calls; - on_after(); - } - void error(std::exception_ptr) { - FAIL("error() should not be called"); - } - } callback; - auto token = results.add_notification_callback(&callback); - advance_and_notify(*r); - - SECTION("only after() is called for initial results") { - REQUIRE(callback.before_calls == 0); - REQUIRE(callback.after_calls == 1); - REQUIRE(callback.after_change.empty()); - } - - auto write = [&](auto&& func) { - r2->begin_transaction(); - func(*r2_table); - r2->commit_transaction(); - advance_and_notify(*r); - }; - - SECTION("both are called after a write") { - write([&](auto&& t) { - t.create_object(ObjKey(53)).set(col_value, 5); - }); - REQUIRE(callback.before_calls == 1); - REQUIRE(callback.after_calls == 2); - REQUIRE_INDICES(callback.before_change.insertions, 4); - REQUIRE_INDICES(callback.after_change.insertions, 4); - } - - SECTION("deleted objects are usable in before()") { - callback.on_before = [&] { - REQUIRE(results.size() == 4); - REQUIRE_INDICES(callback.before_change.deletions, 0); - REQUIRE(results.get(0).is_valid()); - REQUIRE(results.get(0).get<int64_t>(col_value) == 2); - }; - write([&](auto&& t) { - t.remove_object(results.get(0).get_key()); - }); - REQUIRE(callback.before_calls == 1); - REQUIRE(callback.after_calls == 2); - } - - SECTION("inserted objects are usable in after()") { - callback.on_after = [&] { - REQUIRE(results.size() == 5); - REQUIRE_INDICES(callback.after_change.insertions, 4); - REQUIRE(results.last()->get<int64_t>(col_value) == 5); - }; - write([&](auto&& t) { - t.create_object(ObjKey(53)).set(col_value, 5); - }); - REQUIRE(callback.before_calls == 1); - REQUIRE(callback.after_calls == 2); - } - } - - SECTION("sorted notifications") { - // Sort in descending order - results = results.sort({{{col_value}}, {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("modifications that leave a non-matching row non-matching do not send notifications") { - write([&] { - table->get_object(object_keys[6]).set(col_value, 13); - }); - REQUIRE(notification_calls == 1); - } - - SECTION("deleting non-matching rows does not send a notification") { - write([&] { - table->remove_object(object_keys[0]); - table->remove_object(object_keys[6]); - }); - REQUIRE(notification_calls == 1); - } - - SECTION("modifying a matching row and leaving it matching marks that row as modified") { - write([&] { - table->get_object(object_keys[1]).set(col_value, 3); - }); - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.modifications, 3); - REQUIRE_INDICES(change.modifications_new, 3); - } - - SECTION("modifying a matching row to no longer match marks that row as deleted") { - write([&] { - table->get_object(object_keys[2]).set(col_value, 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->get_object(object_keys[7]).set(col_value, 3); - }); - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.insertions, 3); - } - - SECTION("deleting a matching row marks that row as deleted") { - write([&] { - table->remove_object(object_keys[3]); - }); - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.deletions, 1); - } - - SECTION("clearing the table marks all rows as deleted") { - size_t num_expected_deletes = results.size(); - write([&] { - table->clear(); - }); - REQUIRE(notification_calls == 2); - REQUIRE(change.deletions.count() == num_expected_deletes); - } - - SECTION("clear insert clear marks the correct rows as deleted") { - size_t num_expected_deletes = results.size(); - write([&] { - table->clear(); - }); - REQUIRE(notification_calls == 2); - REQUIRE(change.deletions.count() == num_expected_deletes); - write([&] { - table->create_object().set(col_value, 3); - table->create_object().set(col_value, 4); - table->create_object().set(col_value, 5); - }); - REQUIRE(notification_calls == 3); - REQUIRE_INDICES(change.insertions, 0, 1, 2); - REQUIRE(change.deletions.empty()); - write([&] { - table->clear(); - }); - REQUIRE(notification_calls == 4); - REQUIRE_INDICES(change.deletions, 0, 1, 2); - REQUIRE(change.insertions.empty()); - REQUIRE(change.modifications.empty()); - } - - SECTION("delete insert clear marks the correct rows as deleted") { - size_t num_expected_deletes = results.size(); - write([&] { - results.clear(); // delete all 4 matches - }); - REQUIRE(notification_calls == 2); - REQUIRE(change.deletions.count() == num_expected_deletes); - write([&] { - table->create_object(ObjKey(57)).set(col_value, 3); - table->create_object(ObjKey(58)).set(col_value, 4); - table->create_object(ObjKey(59)).set(col_value, 5); - }); - REQUIRE(notification_calls == 3); - REQUIRE_INDICES(change.insertions, 0, 1, 2); - REQUIRE(change.deletions.empty()); - write([&] { - table->clear(); - }); - REQUIRE(notification_calls == 4); - REQUIRE_INDICES(change.deletions, 0, 1, 2); - REQUIRE(change.insertions.empty()); - REQUIRE(change.modifications.empty()); - } - - SECTION("modifying a matching row to change its position sends insert+delete") { - write([&] { - table->get_object(object_keys[2]).set(col_value, 9); - }); - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.deletions, 2); - REQUIRE_INDICES(change.insertions, 0); - } - - SECTION("modifications from multiple transactions are collapsed") { - r2->begin_transaction(); - r2_table->get_object(object_keys[0]).set(col_value, 5); - r2->commit_transaction(); - - r2->begin_transaction(); - r2_table->get_object(object_keys[1]).set(col_value, 0); - r2->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(); - ObjKey k0 = table->create_object().set(col_value, 15).get_key(); - table->create_object().set(col_value, 5); - r->commit_transaction(); - advance_and_notify(*r); - - write([&] { - table->remove_object(k0); - table->create_object().set(col_value, 3); - }); - - REQUIRE(notification_calls == 3); - REQUIRE(change.deletions.empty()); - REQUIRE_INDICES(change.insertions, 1); - } - } - - SECTION("distinct notifications") { - results = results.distinct(DistinctDescriptor({{col_value}})); - - 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->get_object(object_keys[6]).set(col_value, 13); - }); - REQUIRE(notification_calls == 1); - } - - SECTION("deleting non-matching rows does not send a notification") { - write([&] { - table->remove_object(object_keys[0]); - table->remove_object(object_keys[6]); - }); - REQUIRE(notification_calls == 1); - } - - SECTION("modifying a matching row and leaving it matching marks that row as modified") { - write([&] { - table->get_object(object_keys[1]).set(col_value, 3); - }); - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.modifications, 0); - REQUIRE_INDICES(change.modifications_new, 0); - } - - SECTION("modifying a non-matching row which is after the distinct results in the table to be a same value \ - in the distinct results doesn't send notification.") { - write([&] { - table->get_object(object_keys[6]).set(col_value, 2); - }); - REQUIRE(notification_calls == 1); - } - - SECTION("modifying a non-matching row which is before the distinct results in the table to be a same value \ - in the distinct results send insert + delete.") { - write([&] { - table->get_object(object_keys[0]).set(col_value, 2); - }); - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.deletions, 0); - REQUIRE_INDICES(change.insertions, 0); - } - - SECTION("modifying a matching row to duplicated value in distinct results marks that row as deleted") { - write([&] { - table->get_object(object_keys[2]).set(col_value, 2); - }); - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.deletions, 1); - } - - SECTION("modifying a non-matching row to match and different value marks that row as inserted") { - write([&] { - table->get_object(object_keys[0]).set(col_value, 1); - }); - REQUIRE(notification_calls == 2); - REQUIRE_INDICES(change.insertions, 0); - } - } - - SECTION("schema changes") { - CollectionChangeSet change; - auto token = results.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr err) { - REQUIRE_FALSE(err); - change = c; - }); - advance_and_notify(*r); - - auto write = [&](auto&& f) { - r->begin_transaction(); - f(); - r->commit_transaction(); - advance_and_notify(*r); - }; - - SECTION("insert table before observed table") { - write([&] { - table->create_object(ObjKey(53)).set(col_value, 5); - r->read_group().add_table("new table"); - table->create_object(ObjKey(0)).set(col_value, 5); - }); - REQUIRE_INDICES(change.insertions, 0, 5); - } - - auto linked_table = table->get_link_target(col_link); - auto col = linked_table->get_column_key("value"); - SECTION("insert new column before link column") { - write([&] { - linked_table->get_object(target_keys[1]).set(col, 5); - table->add_column(type_Int, "new col"); - linked_table->get_object(target_keys[2]).set(col, 5); - }); - REQUIRE_INDICES(change.modifications, 0, 1); - } -#ifdef UNITTESTS_NOT_PARSING - SECTION("insert table before link target") { - write([&] { - linked_table->get_object(target_keys[1]).set(col, 5); - r->read_group().add_table("new table"); - linked_table->get_object(target_keys[2]).set(col, 5); - }); - REQUIRE_INDICES(change.modifications, 0, 1); - } -#endif - } -} - -TEST_CASE("results: notifications after move") { - InMemoryTestFile config; - config.automatic_change_notifications = false; - - auto r = Realm::get_shared_realm(config); - r->update_schema({ - {"object", { - {"value", PropertyType::Int}, - }}, - }); - - auto table = r->read_group().get_table("class_object"); - auto results = std::make_unique<Results>(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->create_object().set_all(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->create_object().set_all(1); - }); - REQUIRE(notification_calls == 2); - } -} - -TEST_CASE("results: notifier with no callbacks") { - _impl::RealmCoordinator::assert_no_open_realms(); - - InMemoryTestFile config; - config.automatic_change_notifications = false; - - auto coordinator = _impl::RealmCoordinator::get_coordinator(config.path); - auto r = coordinator->get_realm(std::move(config), none); - r->update_schema({ - {"object", { - {"value", PropertyType::Int}, - }}, - }); - - auto table = r->read_group().get_table("class_object"); - Results results(r, table->where()); - results.last(); // force evaluation and creation of TableView - - SECTION("refresh() does not block due to implicit notifier") { - // Create and then immediately remove a callback because - // `automatic_change_notifications = false` makes Results not implicitly - // create a notifier - results.add_notification_callback([](CollectionChangeSet const&, std::exception_ptr) {}); - - auto r2 = coordinator->get_realm(util::Scheduler::get_frozen()); - r2->begin_transaction(); - r2->read_group().get_table("class_object")->create_object(); - r2->commit_transaction(); - - r->refresh(); // would deadlock if there was a callback - } - - SECTION("refresh() does not attempt to deliver stale results") { - results.add_notification_callback([](CollectionChangeSet const&, std::exception_ptr) {}); - - // Create version 1 - r->begin_transaction(); - table->create_object(); - r->commit_transaction(); - - r->begin_transaction(); - // Run async query for version 1 - coordinator->on_change(); - // Create version 2 without ever letting 1 be delivered - table->create_object(); - r->commit_transaction(); - - // Give it a chance to deliver the async query results (and fail, becuse - // they're for version 1 and the realm is at 2) - r->refresh(); - } - - SECTION("should not pin the source version even after the Realm has been closed") { - auto r2 = coordinator->get_realm(); - REQUIRE(r != r2); - r->close(); - - auto& shared_group = TestHelper::get_shared_group(r2); - // There's always at least 2 live versions because the previous version - // isn't clean up until the *next* commit - REQUIRE(shared_group->get_number_of_versions() == 2); - - auto table = r2->read_group().get_table("class_object"); - - r2->begin_transaction(); - table->create_object(); - r2->commit_transaction(); - r2->begin_transaction(); - table->create_object(); - r2->commit_transaction(); - - // Would now be 3 if the closed Realm is still pinning the version it was at - REQUIRE(shared_group->get_number_of_versions() == 2); - } -} - -TEST_CASE("results: error messages") { - InMemoryTestFile config; - config.schema = 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->create_object(); - r->commit_transaction(); - - SECTION("out of bounds access") { - REQUIRE_THROWS_WITH(results.get(5), "Requested index 5 greater than max 0"); - } - - SECTION("unsupported aggregate operation") { - REQUIRE_THROWS_WITH(results.sum("value"), "Cannot sum property 'value': operation not supported for 'string' properties"); - } -} - -TEST_CASE("results: snapshots") { - InMemoryTestFile config; - config.automatic_change_notifications = false; - config.schema = Schema{ - {"object", { - {"value", PropertyType::Int}, - {"array", PropertyType::Array|PropertyType::Object, "linked to object"} - }}, - {"linked to object", { - {"value", PropertyType::Int} - }} - }; - - auto r = Realm::get_shared_realm(config); - - SECTION("snapshot of empty Results") { - Results results; - auto snapshot = results.snapshot(); - REQUIRE(snapshot.size() == 0); - } - - auto write = [&](auto&& f) { - r->begin_transaction(); - f(); - r->commit_transaction(); - advance_and_notify(*r); - }; - - 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->create_object(); - }); - 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->begin()->remove(); - }); - REQUIRE(results.size() == 0); - REQUIRE(snapshot.size() == 1); - REQUIRE(!snapshot.get(0).is_valid()); - - // Adding a row at the same index that was formerly present in the snapshot shouldn't - // affect the state of the snapshot. - write([=]{ - table->create_object(); - }); - REQUIRE(snapshot.size() == 1); - REQUIRE(!snapshot.get(0).is_valid()); - } - } - - SECTION("snapshot of Results based on LinkView") { - auto object = r->read_group().get_table("class_object"); - auto col_link = object->get_column_key("array"); - auto linked_to = r->read_group().get_table("class_linked to object"); - - write([=]{ - object->create_object(); - }); - - std::shared_ptr<LnkLst> lv = object->begin()->get_linklist_ptr(col_link); - 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->create_object().get_key()); - }); - 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_valid()); - - // Removing a row present in the snapshot from its table should result in the snapshot - // returning a detached row accessor. - write([&]{ - linked_to->begin()->remove(); - }); - REQUIRE(snapshot.size() == 1); - REQUIRE(!snapshot.get(0).is_valid()); - - // Adding a new row to the link list shouldn't affect the state of the snapshot. - write([&]{ - lv->add(linked_to->create_object().get_key()); - }); - REQUIRE(snapshot.size() == 1); - REQUIRE(!snapshot.get(0).is_valid()); - } - } - - SECTION("snapshot of Results based on Query") { - auto table = r->read_group().get_table("class_object"); - auto col_value = table->get_column_key("value"); - Query q = table->column<Int>(col_value) > 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->create_object().set(col_value, 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->begin()->set(col_value, 0); - }); - REQUIRE(results.size() == 0); - REQUIRE(snapshot.size() == 1); - REQUIRE(snapshot.get(0).is_valid()); - - // Removing a row present in the snapshot from its table should result in the snapshot - // returning a detached row accessor. - write([=]{ - table->begin()->remove(); - }); - REQUIRE(snapshot.size() == 1); - REQUIRE(!snapshot.get(0).is_valid()); - - // Adding a new row that matches the query criteria shouldn't affect the state of the snapshot. - write([=]{ - table->create_object().set(col_value, 1); - }); - REQUIRE(snapshot.size() == 1); - REQUIRE(!snapshot.get(0).is_valid()); - } - } - - SECTION("snapshot of Results based on TableView from query") { - auto table = r->read_group().get_table("class_object"); - auto col_value = table->get_column_key("value"); - Query q = table->column<Int>(col_value) > 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->create_object().set(col_value, 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->begin()->set(col_value, 0); - }); - REQUIRE(results.size() == 0); - REQUIRE(snapshot.size() == 1); - REQUIRE(snapshot.get(0).is_valid()); - - // Removing a row present in the snapshot from its table should result in the snapshot - // returning a detached row accessor. - write([=]{ - table->begin()->remove(); - }); - REQUIRE(snapshot.size() == 1); - REQUIRE(!snapshot.get(0).is_valid()); - - // Adding a new row that matches the query criteria shouldn't affect the state of the snapshot. - write([=]{ - table->create_object().set(col_value, 1); - }); - REQUIRE(snapshot.size() == 1); - REQUIRE(!snapshot.get(0).is_valid()); - } - } - - SECTION("snapshot of Results based on TableView from backlinks") { - auto object = r->read_group().get_table("class_object"); - auto col_link = object->get_column_key("array"); - auto linked_to = r->read_group().get_table("class_linked to object"); - - write([=]{ - linked_to->create_object(); - object->create_object(); - }); - - auto linked_to_obj = *linked_to->begin(); - auto lv = object->begin()->get_linklist_ptr(col_link); - - TableView backlinks = linked_to_obj.get_backlink_view(object, col_link); - Results results(r, std::move(backlinks)); - - { - // 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_obj.get_key()); - }); - 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([&]{ - if (lv->size() > 0) - lv->remove(0); - }); - REQUIRE(results.size() == 0); - REQUIRE(snapshot.size() == 1); - REQUIRE(snapshot.get(0).is_valid()); - - // Removing a row present in the snapshot from its table should result in the snapshot - // returning a detached row accessor. - write([=]{ - object->begin()->remove(); - }); - REQUIRE(snapshot.size() == 1); - REQUIRE(!snapshot.get(0).is_valid()); - - // Adding a new link shouldn't affect the state of the snapshot. - write([=]{ - object->create_object().get_linklist(col_link).add(linked_to_obj.get_key()); - }); - REQUIRE(snapshot.size() == 1); - REQUIRE(!snapshot.get(0).is_valid()); - } - } - - SECTION("snapshot of Results with notification callback registered") { - auto table = r->read_group().get_table("class_object"); - auto col_value = table->get_column_key("value"); - Query q = table->column<Int>(col_value) > 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->create_object().set(col_value, 1); - }); - REQUIRE(snapshot.size() == 0); - } - - SECTION("snapshot of rvalue") { - auto snapshot = std::move(results).snapshot(); - write([=] { - table->create_object().set(col_value, 1); - }); - REQUIRE(snapshot.size() == 0); - } - } - - SECTION("adding notification callback to snapshot throws") { - auto table = r->read_group().get_table("class_object"); - auto col_value = table->get_column_key("value"); - Query q = table->column<Int>(col_value) > 0; - Results results(r, q.find_all()); - auto snapshot = results.snapshot(); - CHECK_THROWS(snapshot.add_notification_callback([](CollectionChangeSet, std::exception_ptr) {})); - } - - SECTION("accessors should return none for detached row") { - auto table = r->read_group().get_table("class_object"); - write([=] { - table->create_object(); - }); - Results results(r, table); - auto snapshot = results.snapshot(); - write([=] {; - table->clear(); - }); - - REQUIRE_FALSE(snapshot.get(0).is_valid()); - REQUIRE_FALSE(snapshot.first()->is_valid()); - REQUIRE_FALSE(snapshot.last()->is_valid()); - } -} - -TEST_CASE("results: distinct") { - const int N = 10; - InMemoryTestFile config; - config.automatic_change_notifications = false; - - auto r = Realm::get_shared_realm(config); - r->update_schema({ - {"object", { - {"num1", PropertyType::Int}, - {"string", PropertyType::String}, - {"num2", PropertyType::Int}, - {"num3", PropertyType::Int} - }}, - }); - - auto table = r->read_group().get_table("class_object"); - - r->begin_transaction(); - for (int i = 0; i < N; ++i) { - table->create_object().set_all(i % 3, util::format("Foo_%1", i % 3).c_str(), N - i, i % 2); - } - // table: - // 0, Foo_0, 10, 0 - // 1, Foo_1, 9, 1 - // 2, Foo_2, 8, 0 - // 0, Foo_0, 7, 1 - // 1, Foo_1, 6, 0 - // 2, Foo_2, 5, 1 - // 0, Foo_0, 4, 0 - // 1, Foo_1, 3, 1 - // 2, Foo_2, 2, 0 - // 0, Foo_0, 1, 1 - - r->commit_transaction(); - Results results(r, table->where()); - ColKey col_num1 = table->get_column_key("num1"); - ColKey col_string = table->get_column_key("string"); - ColKey col_num2 = table->get_column_key("num2"); - ColKey col_num3 = table->get_column_key("num3"); - - SECTION("Single integer property") { - Results unique = results.distinct(DistinctDescriptor({{col_num1}})); - // unique: - // 0, Foo_0, 10 - // 1, Foo_1, 9 - // 2, Foo_2, 8 - REQUIRE(unique.size() == 3); - REQUIRE(unique.get(0).get<Int>(col_num2) == 10); - REQUIRE(unique.get(1).get<Int>(col_num2) == 9); - REQUIRE(unique.get(2).get<Int>(col_num2) == 8); - } - - SECTION("Single integer via apply_ordering") { - DescriptorOrdering ordering; - ordering.append_sort(SortDescriptor({{col_num1}})); - ordering.append_distinct(DistinctDescriptor({{col_num1}})); - Results unique = results.apply_ordering(std::move(ordering)); - // unique: - // 0, Foo_0, 10 - // 1, Foo_1, 9 - // 2, Foo_2, 8 - REQUIRE(unique.size() == 3); - REQUIRE(unique.get(0).get<Int>(col_num2) == 10); - REQUIRE(unique.get(1).get<Int>(col_num2) == 9); - REQUIRE(unique.get(2).get<Int>(col_num2) == 8); - } - - SECTION("Single string property") { - Results unique = results.distinct(DistinctDescriptor({{col_string}})); - // unique: - // 0, Foo_0, 10 - // 1, Foo_1, 9 - // 2, Foo_2, 8 - REQUIRE(unique.size() == 3); - REQUIRE(unique.get(0).get<Int>(col_num2) == 10); - REQUIRE(unique.get(1).get<Int>(col_num2) == 9); - REQUIRE(unique.get(2).get<Int>(col_num2) == 8); - } - - SECTION("Two integer properties combined") { - Results unique = results.distinct(DistinctDescriptor({{col_num1}, {col_num2}})); - // unique is the same as the table - REQUIRE(unique.size() == N); - for (int i = 0; i < N; ++i) { - REQUIRE(unique.get(i).get<String>(col_string) == StringData(util::format("Foo_%1", i % 3).c_str())); - } - } - - SECTION("String and integer combined") { - Results unique = results.distinct(DistinctDescriptor({{col_num2}, {col_string}})); - // unique is the same as the table - REQUIRE(unique.size() == N); - for (int i = 0; i < N; ++i) { - REQUIRE(unique.get(i).get<String>(col_string) == StringData(util::format("Foo_%1", i % 3).c_str())); - } - } - - // This section and next section demonstrate that sort().distinct() != distinct().sort() - SECTION("Order after sort and distinct") { - Results reverse = results.sort(SortDescriptor({{col_num2}}, {true})); - // reverse: - // 0, Foo_0, 1 - // ... - // 0, Foo_0, 10 - REQUIRE(reverse.first()->get<Int>(col_num2) == 1); - REQUIRE(reverse.last()->get<Int>(col_num2) == 10); - - // distinct() will be applied to the table, after sorting - Results unique = reverse.distinct(DistinctDescriptor({{col_num1}})); - // unique: - // 0, Foo_0, 1 - // 2, Foo_2, 2 - // 1, Foo_1, 3 - REQUIRE(unique.size() == 3); - REQUIRE(unique.get(0).get<Int>(col_num2) == 1); - REQUIRE(unique.get(1).get<Int>(col_num2) == 2); - REQUIRE(unique.get(2).get<Int>(col_num2) == 3); - } - - SECTION("Order after distinct and sort") { - Results unique = results.distinct(DistinctDescriptor({{col_num1}})); - // unique: - // 0, Foo_0, 10 - // 1, Foo_1, 9 - // 2, Foo_2, 8 - REQUIRE(unique.size() == 3); - REQUIRE(unique.first()->get<Int>(col_num2) == 10); - REQUIRE(unique.last()->get<Int>(col_num2) == 8); - - // sort() is only applied to unique - Results reverse = unique.sort(SortDescriptor({{col_num2}}, {true})); - // reversed: - // 2, Foo_2, 8 - // 1, Foo_1, 9 - // 0, Foo_0, 10 - REQUIRE(reverse.size() == 3); - REQUIRE(reverse.get(0).get<Int>(col_num2) == 8); - REQUIRE(reverse.get(1).get<Int>(col_num2) == 9); - REQUIRE(reverse.get(2).get<Int>(col_num2) == 10); - } - - SECTION("Chaining distinct") { - Results first = results.distinct(DistinctDescriptor({{col_num1}})); - REQUIRE(first.size() == 3); - - // distinct() will not discard the previous applied distinct() calls - Results second = first.distinct(DistinctDescriptor({{col_num3}})); - REQUIRE(second.size() == 2); - } - - SECTION("Chaining sort") { - using cols_0_3 = std::pair<int, int>; - Results first = results.sort(SortDescriptor({{col_num1}})); - Results second = first.sort(SortDescriptor({{col_num3}})); - - REQUIRE(second.size() == 10); - // results are ordered first by the last sorted column - // if any duplicates exist in that column, they are resolved by sorting the - // previously sorted column. Eg. sort(a).sort(b) == sort(b, a) - std::vector<cols_0_3> results - = {{0, 0}, {0, 0}, {1, 0}, {2, 0}, {2, 0}, {0, 1}, {0, 1}, {1, 1}, {1, 1}, {2, 1}}; - for (size_t i = 0; i < results.size(); ++i) { - REQUIRE(second.get(i).get<Int>(col_num1) == results[i].first); - REQUIRE(second.get(i).get<Int>(col_num3) == results[i].second); - } - } - - SECTION("Distinct is carried over to new queries") { - Results unique = results.distinct(DistinctDescriptor({{col_num1}})); - // unique: - // 0, Foo_0, 10 - // 1, Foo_1, 9 - // 2, Foo_2, 8 - REQUIRE(unique.size() == 3); - - Results filtered = unique.filter(Query(table->where().less(col_num1, 2))); - // filtered: - // 0, Foo_0, 10 - // 1, Foo_1, 9 - REQUIRE(filtered.size() == 2); - REQUIRE(filtered.get(0).get<Int>(col_num2) == 10); - REQUIRE(filtered.get(1).get<Int>(col_num2) == 9); - } - - SECTION("Distinct will not forget previous query") { - Results filtered = results.filter(Query(table->where().greater(col_num2, 5))); - // filtered: - // 0, Foo_0, 10 - // 1, Foo_1, 9 - // 2, Foo_2, 8 - // 0, Foo_0, 7 - // 1, Foo_1, 6 - REQUIRE(filtered.size() == 5); - - Results unique = filtered.distinct(DistinctDescriptor({{col_num1}})); - // unique: - // 0, Foo_0, 10 - // 1, Foo_1, 9 - // 2, Foo_2, 8 - REQUIRE(unique.size() == 3); - REQUIRE(unique.get(0).get<Int>(col_num2) == 10); - REQUIRE(unique.get(1).get<Int>(col_num2) == 9); - REQUIRE(unique.get(2).get<Int>(col_num2) == 8); - - Results further_filtered = unique.filter(Query(table->where().equal(col_num2, 9))); - // further_filtered: - // 1, Foo_1, 9 - REQUIRE(further_filtered.size() == 1); - REQUIRE(further_filtered.get(0).get<Int>(col_num2) == 9); - } -} - -TEST_CASE("results: sort") { - InMemoryTestFile config; - config.schema = Schema{ - {"object", { - {"value", PropertyType::Int}, - {"bool", PropertyType::Bool}, - {"data prop", PropertyType::Data}, - {"link", PropertyType::Object|PropertyType::Nullable, "object 2"}, - {"array", PropertyType::Object|PropertyType::Array, "object 2"}, - }}, - {"object 2", { - {"value", PropertyType::Int}, - {"link", PropertyType::Object|PropertyType::Nullable, "object"}, - }}, - }; - - auto realm = Realm::get_shared_realm(config); - auto table = realm->read_group().get_table("class_object"); - auto table2 = realm->read_group().get_table("class_object 2"); - Results r(realm, table); - - SECTION("invalid keypaths") { - SECTION("empty property name") { - REQUIRE_THROWS_WITH(r.sort({{"", true}}), "Cannot sort on key path '': missing property name."); - REQUIRE_THROWS_WITH(r.sort({{".", true}}), "Cannot sort on key path '.': missing property name."); - REQUIRE_THROWS_WITH(r.sort({{"link.", true}}), "Cannot sort on key path 'link.': missing property name."); - REQUIRE_THROWS_WITH(r.sort({{".value", true}}), "Cannot sort on key path '.value': missing property name."); - REQUIRE_THROWS_WITH(r.sort({{"link..value", true}}), "Cannot sort on key path 'link..value': missing property name."); - } - SECTION("bad property name") { - REQUIRE_THROWS_WITH(r.sort({{"not a property", true}}), - "Cannot sort on key path 'not a property': property 'object.not a property' does not exist."); - REQUIRE_THROWS_WITH(r.sort({{"link.not a property", true}}), - "Cannot sort on key path 'link.not a property': property 'object 2.not a property' does not exist."); - } - SECTION("subscript primitive") { - REQUIRE_THROWS_WITH(r.sort({{"value.link", true}}), - "Cannot sort on key path 'value.link': property 'object.value' of type 'int' may only be the final property in the key path."); - } - SECTION("end in link") { - REQUIRE_THROWS_WITH(r.sort({{"link", true}}), - "Cannot sort on key path 'link': property 'object.link' of type 'object' cannot be the final property in the key path."); - REQUIRE_THROWS_WITH(r.sort({{"link.link", true}}), - "Cannot sort on key path 'link.link': property 'object 2.link' of type 'object' cannot be the final property in the key path."); - } - SECTION("sort involving bad property types") { - REQUIRE_THROWS_WITH(r.sort({{"array", true}}), - "Cannot sort on key path 'array': property 'object.array' is of unsupported type 'array'."); - REQUIRE_THROWS_WITH(r.sort({{"array.value", true}}), - "Cannot sort on key path 'array.value': property 'object.array' is of unsupported type 'array'."); - REQUIRE_THROWS_WITH(r.sort({{"link.link.array.value", true}}), - "Cannot sort on key path 'link.link.array.value': property 'object.array' is of unsupported type 'array'."); - REQUIRE_THROWS_WITH(r.sort({{"data prop", true}}), - "Cannot sort on key path 'data prop': property 'object.data prop' is of unsupported type 'data'."); - } - } - - realm->begin_transaction(); - ObjKeys table_keys; - ObjKeys table2_keys; - table->create_objects(4, table_keys); - table2->create_objects(4, table2_keys); - ColKey col_link = table->get_column_key("link"); - ColKey col_link2 = table2->get_column_key("link"); - for (int i = 0; i < 4; ++i) { - table->get_object(table_keys[i]).set_all((i + 2) % 4, bool(i % 2)).set(col_link, table2_keys[3 - i]); - table2->get_object(table2_keys[i]).set_all((i + 1) % 4).set(col_link2, table_keys[i]); - } - realm->commit_transaction(); - /* - | index | value | bool | link.value | link.link.value | - |-------|-------|------|------------|-----------------| - | 0 | 2 | 0 | 0 | 1 | - | 1 | 3 | 1 | 3 | 0 | - | 2 | 0 | 0 | 2 | 3 | - | 3 | 1 | 1 | 1 | 2 | - */ - - #define REQUIRE_ORDER(sort, ...) do { \ - ObjKeys expected({__VA_ARGS__}); \ - auto results = sort; \ - REQUIRE(results.size() == expected.size()); \ - for (size_t i = 0; i < expected.size(); ++i) \ - REQUIRE(results.get(i).get_key() == expected[i]); \ - } while (0) - - SECTION("sort on single property") { - REQUIRE_ORDER((r.sort({{"value", true}})), - 2, 3, 0, 1); - REQUIRE_ORDER((r.sort({{"value", false}})), - 1, 0, 3, 2); - } - - SECTION("sort on two properties") { - REQUIRE_ORDER((r.sort({{"bool", true}, {"value", true}})), - 2, 0, 3, 1); - REQUIRE_ORDER((r.sort({{"bool", false}, {"value", true}})), - 3, 1, 2, 0); - REQUIRE_ORDER((r.sort({{"bool", true}, {"value", false}})), - 0, 2, 1, 3); - REQUIRE_ORDER((r.sort({{"bool", false}, {"value", false}})), - 1, 3, 0, 2); - } - SECTION("sort over link") { - REQUIRE_ORDER((r.sort({{"link.value", true}})), - 0, 3, 2, 1); - REQUIRE_ORDER((r.sort({{"link.value", false}})), - 1, 2, 3, 0); - } - SECTION("sort over two links") { - REQUIRE_ORDER((r.sort({{"link.link.value", true}})), - 1, 0, 3, 2); - REQUIRE_ORDER((r.sort({{"link.link.value", false}})), - 2, 3, 0, 1); - } -} - -struct ResultsFromTable { - static Results call(std::shared_ptr<Realm> r, ConstTableRef table) { - return Results(std::move(r), table); - } -}; -struct ResultsFromQuery { - static Results call(std::shared_ptr<Realm> r, ConstTableRef table) { - return Results(std::move(r), table->where()); - } -}; -struct ResultsFromTableView { - static Results call(std::shared_ptr<Realm> r, ConstTableRef table) { - return Results(std::move(r), table->where().find_all()); - } -}; -struct ResultsFromLinkView { - static Results call(std::shared_ptr<Realm> r, ConstTableRef table) { - r->begin_transaction(); - auto link_table = r->read_group().get_table("class_linking_object"); - std::shared_ptr<LnkLst> link_view = link_table->create_object().get_linklist_ptr(link_table->get_column_key("link")); - for (auto& o : *table) - link_view->add(o.get_key()); - r->commit_transaction(); - return Results(r, link_view); - } -}; - -TEMPLATE_TEST_CASE("results: get()", "", ResultsFromTable, ResultsFromQuery, ResultsFromTableView, ResultsFromLinkView) { - InMemoryTestFile config; - config.automatic_change_notifications = false; - - auto r = Realm::get_shared_realm(config); - r->update_schema({ - {"object", { - {"value", PropertyType::Int}, - }}, - {"linking_object", { - {"link", PropertyType::Array|PropertyType::Object, "object"} - }}, - }); - - auto table = r->read_group().get_table("class_object"); - ColKey col_value = table->get_column_key("value"); - - r->begin_transaction(); - for (int i = 0; i < 10; ++i) - table->create_object().set_all(i); - r->commit_transaction(); - - Results results = TestType::call(r, table); - - SECTION("sequential in increasing order") { - for (int i = 0; i < 10; ++i) - CHECK(results.get<Obj>(i).get<int64_t>(col_value) == i); - for (int i = 0; i < 10; ++i) - CHECK(results.get<Obj>(i).get<int64_t>(col_value) == i); - CHECK_THROWS(results.get(11)); - } - SECTION("sequential in decreasing order") { - for (int i = 9; i >= 0; --i) - CHECK(results.get<Obj>(i).get<int64_t>(col_value) == i); - for (int i = 9; i >= 0; --i) - CHECK(results.get<Obj>(i).get<int64_t>(col_value) == i); - } - SECTION("random order") { - int indexes[10]; - std::iota(std::begin(indexes), std::end(indexes), 0); - std::random_device rd; - std::mt19937 g(rd()); - std::shuffle(std::begin(indexes), std::end(indexes), std::mt19937(rd())); - for (auto index : indexes) - CHECK(results.get<Obj>(index).get<int64_t>(col_value) == index); - } -} - -TEMPLATE_TEST_CASE("results: aggregate", "[query][aggregate]", ResultsFromTable, ResultsFromQuery, ResultsFromTableView, ResultsFromLinkView) { - InMemoryTestFile config; - config.automatic_change_notifications = false; - - auto r = Realm::get_shared_realm(config); - r->update_schema({ - {"object", { - {"int", PropertyType::Int|PropertyType::Nullable}, - {"float", PropertyType::Float|PropertyType::Nullable}, - {"double", PropertyType::Double|PropertyType::Nullable}, - {"date", PropertyType::Date|PropertyType::Nullable}, - }}, - {"linking_object", { - {"link", PropertyType::Array|PropertyType::Object, "object"} - }}, - }); - - auto table = r->read_group().get_table("class_object"); - ColKey col_int = table->get_column_key("int"); - ColKey col_float = table->get_column_key("float"); - ColKey col_double = table->get_column_key("double"); - ColKey col_date = table->get_column_key("date"); - - SECTION("one row with null values") { - r->begin_transaction(); - table->create_object(); - table->create_object().set_all(0, 0.f, 0.0, Timestamp(0, 0)); - table->create_object().set_all(2, 2.f, 2.0, Timestamp(2, 0)); - // table: - // null, null, null, null, - // 0, 0, 0, (0, 0) - // 2, 2, 2, (2, 0) - r->commit_transaction(); - - Results results = TestType::call(r, table); - - SECTION("max") { - REQUIRE(results.max(col_int)->get_int() == 2); - REQUIRE(results.max(col_float)->get_float() == 2.f); - REQUIRE(results.max(col_double)->get_double() == 2.0); - REQUIRE(results.max(col_date)->get_timestamp() == Timestamp(2, 0)); - } - - SECTION("min") { - REQUIRE(results.min(col_int)->get_int() == 0); - REQUIRE(results.min(col_float)->get_float() == 0.f); - REQUIRE(results.min(col_double)->get_double() == 0.0); - REQUIRE(results.min(col_date)->get_timestamp() == Timestamp(0, 0)); - } - - SECTION("average") { - REQUIRE(results.average(col_int) == 1.0); - REQUIRE(results.average(col_float) == 1.0); - REQUIRE(results.average(col_double) == 1.0); - REQUIRE_THROWS_AS(results.average(col_date), Results::UnsupportedColumnTypeException); - } - - SECTION("sum") { - REQUIRE(results.sum(col_int)->get_int() == 2); - REQUIRE(results.sum(col_float)->get_double() == 2.0); - REQUIRE(results.sum(col_double)->get_double() == 2.0); - REQUIRE_THROWS_AS(results.sum(col_date), Results::UnsupportedColumnTypeException); - } - } - - SECTION("rows with all null values") { - r->begin_transaction(); - table->create_object(); - table->create_object(); - table->create_object(); - // table: - // null, null, null, null, null - // null, null, null, null, null - // null, null, null, null, null - r->commit_transaction(); - - Results results = TestType::call(r, table); - - SECTION("max") { - REQUIRE(!results.max(col_int)); - REQUIRE(!results.max(col_float)); - REQUIRE(!results.max(col_double)); - REQUIRE(!results.max(col_date)); - } - - SECTION("min") { - REQUIRE(!results.min(col_int)); - REQUIRE(!results.min(col_float)); - REQUIRE(!results.min(col_double)); - REQUIRE(!results.min(col_date)); - } - - SECTION("average") { - REQUIRE(!results.average(col_int)); - REQUIRE(!results.average(col_float)); - REQUIRE(!results.average(col_double)); - REQUIRE_THROWS_AS(results.average(col_date), Results::UnsupportedColumnTypeException); - } - - SECTION("sum") { - REQUIRE(results.sum(col_int)->get_int() == 0); - REQUIRE(results.sum(col_float)->get_double() == 0.0); - REQUIRE(results.sum(col_double)->get_double() == 0.0); - REQUIRE_THROWS_AS(results.sum(col_date), Results::UnsupportedColumnTypeException); - } - } - - SECTION("empty") { - Results results = TestType::call(r, table); - - SECTION("max") { - REQUIRE(!results.max(col_int)); - REQUIRE(!results.max(col_float)); - REQUIRE(!results.max(col_double)); - REQUIRE(!results.max(col_date)); - } - - SECTION("min") { - REQUIRE(!results.min(col_int)); - REQUIRE(!results.min(col_float)); - REQUIRE(!results.min(col_double)); - REQUIRE(!results.min(col_date)); - } - - SECTION("average") { - REQUIRE(!results.average(col_int)); - REQUIRE(!results.average(col_float)); - REQUIRE(!results.average(col_double)); - REQUIRE_THROWS_AS(results.average(col_date), Results::UnsupportedColumnTypeException); - } - - SECTION("sum") { - REQUIRE(results.sum(col_int)->get_int() == 0); - REQUIRE(results.sum(col_float)->get_double() == 0.0); - REQUIRE(results.sum(col_double)->get_double() == 0.0); - REQUIRE_THROWS_AS(results.sum(col_date), Results::UnsupportedColumnTypeException); - } - } -} - -TEST_CASE("results: set property value on all objects", "[batch_updates]") { - - InMemoryTestFile config; - config.automatic_change_notifications = false; - // config.cache = false; - config.schema = Schema{ - {"AllTypes", { - {"pk", PropertyType::Int, Property::IsPrimary{true}}, - {"bool", PropertyType::Bool}, - {"int", PropertyType::Int}, - {"float", PropertyType::Float}, - {"double", PropertyType::Double}, - {"string", PropertyType::String}, - {"data", PropertyType::Data}, - {"date", PropertyType::Date}, - {"object", PropertyType::Object|PropertyType::Nullable, "AllTypes"}, - {"list", PropertyType::Array|PropertyType::Object, "AllTypes"}, - - {"bool array", PropertyType::Array|PropertyType::Bool}, - {"int array", PropertyType::Array|PropertyType::Int}, - {"float array", PropertyType::Array|PropertyType::Float}, - {"double array", PropertyType::Array|PropertyType::Double}, - {"string array", PropertyType::Array|PropertyType::String}, - {"data array", PropertyType::Array|PropertyType::Data}, - {"date array", PropertyType::Array|PropertyType::Date}, - {"object array", PropertyType::Array|PropertyType::Object, "AllTypes"}, - }, { - {"parents", PropertyType::LinkingObjects|PropertyType::Array, "AllTypes", "object"}, - }} - }; - config.schema_version = 0; - auto realm = Realm::get_shared_realm(config); - auto table = realm->read_group().get_table("class_AllTypes"); - realm->begin_transaction(); - table->create_object(); - table->create_object(); - realm->commit_transaction(); - Results r(realm, table); - - TestContext ctx(realm); - - SECTION("non-existing property name") { - realm->begin_transaction(); - REQUIRE_THROWS_AS(r.set_property_value(ctx, "i dont exist", util::Any(false)), Results::InvalidPropertyException); - realm->cancel_transaction(); - } - - SECTION("readonly property") { - realm->begin_transaction(); - REQUIRE_THROWS_AS(r.set_property_value(ctx, "parents", util::Any(false)), ReadOnlyPropertyException); - realm->cancel_transaction(); - } - - SECTION("primarykey property") { - realm->begin_transaction(); - REQUIRE_THROWS_AS(r.set_property_value(ctx, "pk", util::Any(1)), std::logic_error); - realm->cancel_transaction(); - } - - SECTION("set property values removes object from Results") { - realm->begin_transaction(); - Results results(realm, table->where().equal(table->get_column_key("int"),0)); - CHECK(results.size() == 2); - r.set_property_value(ctx, "int", util::Any(INT64_C(42))); - CHECK(results.size() == 0); - realm->cancel_transaction(); - } - - SECTION("set property value") { - realm->begin_transaction(); - - r.set_property_value<util::Any>(ctx, "bool", util::Any(true)); - for (size_t i = 0; i < r.size(); i++) { - CHECK(r.get(i).get<Bool>("bool") == true); - } - - r.set_property_value(ctx, "int", util::Any(INT64_C(42))); - for (size_t i = 0; i < r.size(); i++) { - CHECK(r.get(i).get<Int>("int") == 42); - } - - r.set_property_value(ctx, "float", util::Any(1.23f)); - for (size_t i = 0; i < r.size(); i++) { - CHECK(r.get(i).get<float>("float") == 1.23f); - } - - r.set_property_value(ctx, "double", util::Any(1.234)); - for (size_t i = 0; i < r.size(); i++) { - CHECK(r.get(i).get<double>("double") == 1.234); - } - - r.set_property_value(ctx, "string", util::Any(std::string("abc"))); - for (size_t i = 0; i < r.size(); i++) { - CHECK(r.get(i).get<String>("string") == "abc"); - } - - r.set_property_value(ctx, "data", util::Any(std::string("abc"))); - for (size_t i = 0; i < r.size(); i++) { - CHECK(r.get(i).get<Binary>("data") == BinaryData("abc", 3)); - } - - util::Any timestamp = Timestamp(1, 2); - r.set_property_value(ctx, "date", timestamp); - for (size_t i = 0; i < r.size(); i++) { - CHECK(r.get(i).get<Timestamp>("date") == any_cast<Timestamp>(timestamp)); - } - - ObjKey object_key = table->create_object().get_key(); - Object linked_obj(realm, "AllTypes", object_key); - r.set_property_value(ctx, "object", util::Any(linked_obj)); - for (size_t i = 0; i < r.size(); i++) { - CHECK(r.get(i).get<ObjKey>("object") == object_key); - } - - ObjKey list_object_key = table->create_object().get_key(); - Object list_object(realm, "AllTypes", list_object_key); - r.set_property_value(ctx, "list", util::Any(AnyVector{list_object, list_object})); - for (size_t i = 0; i < r.size(); i++) { - auto list = r.get(i).get_linklist("list"); - CHECK(list.size() == 2); - CHECK(list.get(0) == list_object_key); - CHECK(list.get(1) == list_object_key); - } - - auto check_array = [&](ColKey col, auto val0, auto... values) { - size_t rows = r.size(); - for (size_t i = 0; i < rows; ++i) { - Obj row = r.get(i); - auto array = row.get_list<decltype(val0)>(col); - CAPTURE(0); - REQUIRE(val0 == array.get(0)); - size_t j = 1; - for (auto& value : {values...}) { - CAPTURE(j); - REQUIRE(j < array.size()); - REQUIRE(value == array.get(j)); - ++j; - } - } - }; - - r.set_property_value(ctx, "bool array", util::Any(AnyVec{true, false})); - check_array(table->get_column_key("bool array"), true, false); - - r.set_property_value(ctx, "int array", util::Any(AnyVec{INT64_C(5), INT64_C(6)})); - check_array(table->get_column_key("int array"), INT64_C(5), INT64_C(6)); - - r.set_property_value(ctx, "float array", util::Any(AnyVec{1.1f, 2.2f})); - check_array(table->get_column_key("float array"), 1.1f, 2.2f); - - r.set_property_value(ctx, "double array", util::Any(AnyVec{3.3, 4.4})); - check_array(table->get_column_key("double array"), 3.3, 4.4); - - r.set_property_value(ctx, "string array", util::Any(AnyVec{"a"s, "b"s, "c"s})); - check_array(table->get_column_key("string array"), StringData("a"), StringData("b"), StringData("c")); - - r.set_property_value(ctx, "data array", util::Any(AnyVec{"d"s, "e"s, "f"s})); - check_array(table->get_column_key("data array"), BinaryData("d",1), BinaryData("e",1), BinaryData("f",1)); - - r.set_property_value(ctx, "date array", util::Any(AnyVec{Timestamp(10,20), Timestamp(20,30), Timestamp(30,40)})); - check_array(table->get_column_key("date array"), Timestamp(10,20), Timestamp(20,30), Timestamp(30,40)); - } -} - -TEST_CASE("results: limit", "[limit]") { - InMemoryTestFile config; - // config.cache = false; - config.automatic_change_notifications = false; - config.schema = Schema{ - {"object", { - {"value", PropertyType::Int}, - }}, - }; - - auto realm = Realm::get_shared_realm(config); - auto table = realm->read_group().get_table("class_object"); - auto col = table->get_column_key("value"); - - realm->begin_transaction(); - for (int i = 0; i < 8; ++i) { - table->create_object().set(col, (i + 2) % 4); - } - realm->commit_transaction(); - Results r(realm, table); - - SECTION("unsorted") { - REQUIRE(r.limit(0).size() == 0); - REQUIRE_ORDER(r.limit(1), 0); - REQUIRE_ORDER(r.limit(2), 0, 1); - REQUIRE_ORDER(r.limit(8), 0, 1, 2, 3, 4, 5, 6, 7); - REQUIRE_ORDER(r.limit(100), 0, 1, 2, 3, 4, 5, 6, 7); - } - - SECTION("sorted") { - auto sorted = r.sort({{"value", true}}); - REQUIRE(sorted.limit(0).size() == 0); - REQUIRE_ORDER(sorted.limit(1), 2); - REQUIRE_ORDER(sorted.limit(2), 2, 6); - REQUIRE_ORDER(sorted.limit(8), 2, 6, 3, 7, 0, 4, 1, 5); - REQUIRE_ORDER(sorted.limit(100), 2, 6, 3, 7, 0, 4, 1, 5); - } - - SECTION("sort after limit") { - REQUIRE(r.limit(0).sort({{"value", true}}).size() == 0); - REQUIRE_ORDER(r.limit(1).sort({{"value", true}}), 0); - REQUIRE_ORDER(r.limit(3).sort({{"value", true}}), 2, 0, 1); - REQUIRE_ORDER(r.limit(8).sort({{"value", true}}), 2, 6, 3, 7, 0, 4, 1, 5); - REQUIRE_ORDER(r.limit(100).sort({{"value", true}}), 2, 6, 3, 7, 0, 4, 1, 5); - } - - SECTION("distinct") { - auto sorted = r.distinct({"value"}); - REQUIRE(sorted.limit(0).size() == 0); - REQUIRE_ORDER(sorted.limit(1), 0); - REQUIRE_ORDER(sorted.limit(2), 0, 1); - REQUIRE_ORDER(sorted.limit(8), 0, 1, 2, 3); - - sorted = r.sort({{"value", true}}).distinct({"value"}); - REQUIRE(sorted.limit(0).size() == 0); - REQUIRE_ORDER(sorted.limit(1), 2); - REQUIRE_ORDER(sorted.limit(2), 2, 3); - REQUIRE_ORDER(sorted.limit(8), 2, 3, 0, 1); - } - - SECTION("notifications on results using all descriptor types") { - r = r.distinct({"value"}).sort({{"value", false}}).limit(2); - int notification_calls = 0; - auto token = r.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr err) { - REQUIRE_FALSE(err); - if (notification_calls == 0) { - REQUIRE(c.empty()); - REQUIRE(r.size() == 2); - REQUIRE(r.get(0).get<Int>(col) == 3); - REQUIRE(r.get(1).get<Int>(col) == 2); - } else if (notification_calls == 1) { - REQUIRE(!c.empty()); - REQUIRE_INDICES(c.insertions, 0); - REQUIRE_INDICES(c.deletions, 1); - REQUIRE(c.moves.size() == 0); - REQUIRE(c.modifications.count() == 0); - REQUIRE(r.size() == 2); - REQUIRE(r.get(0).get<Int>(col) == 5); - REQUIRE(r.get(1).get<Int>(col) == 3); - } - ++notification_calls; - }); - advance_and_notify(*realm); - REQUIRE(notification_calls == 1); - realm->begin_transaction(); - table->create_object().set(col, 5); - realm->commit_transaction(); - advance_and_notify(*realm); - REQUIRE(notification_calls == 2); - } - - SECTION("notifications on only limited results") { - r = r.limit(2); - int notification_calls = 0; - auto token = r.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr err) { - REQUIRE_FALSE(err); - if (notification_calls == 0) { - REQUIRE(c.empty()); - REQUIRE(r.size() == 2); - } else if (notification_calls == 1) { - REQUIRE(!c.empty()); - REQUIRE(c.insertions.count() == 0); - REQUIRE(c.deletions.count() == 0); - REQUIRE(c.modifications.count() == 1); - REQUIRE_INDICES(c.modifications, 1); - REQUIRE(r.size() == 2); - } - ++notification_calls; - }); - advance_and_notify(*realm); - REQUIRE(notification_calls == 1); - realm->begin_transaction(); - table->get_object(1).set(col, 5); - realm->commit_transaction(); - advance_and_notify(*realm); - REQUIRE(notification_calls == 2); - } - - SECTION("does not support further filtering") { - auto limited = r.limit(0); - REQUIRE_THROWS_AS(limited.filter(table->where()), Results::UnimplementedOperationException); - } -} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/schema.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/schema.cpp deleted file mode 100644 index b851c4831..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/schema.cpp +++ /dev/null @@ -1,811 +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 "catch2/catch.hpp" -#include "util/test_file.hpp" - -#include "object_schema.hpp" -#include "object_store.hpp" -#include "property.hpp" -#include "schema.hpp" - -#include <realm/group.hpp> -#include <realm/table.hpp> - -using namespace realm; - -struct SchemaChangePrinter { - std::ostream& out; - - template<typename Value> - void print(Value value) const - { - out << value; - } - - template<typename Value, typename... Rest> - void print(Value value, Rest... rest) const - { - out << value << ", "; - print(rest...); - } - -#define REALM_SC_PRINT(type, ...) \ - void operator()(schema_change::type v) const \ - { \ - out << #type << "{"; \ - print(__VA_ARGS__); \ - out << "}"; \ - } - - REALM_SC_PRINT(AddIndex, v.object, v.property) - REALM_SC_PRINT(AddProperty, v.object, v.property) - REALM_SC_PRINT(AddTable, v.object) - REALM_SC_PRINT(RemoveTable, v.object) - REALM_SC_PRINT(AddInitialProperties, v.object) - REALM_SC_PRINT(ChangePrimaryKey, v.object, v.property) - REALM_SC_PRINT(ChangePropertyType, v.object, v.old_property, v.new_property) - REALM_SC_PRINT(MakePropertyNullable, v.object, v.property) - REALM_SC_PRINT(MakePropertyRequired, v.object, v.property) - REALM_SC_PRINT(RemoveIndex, v.object, v.property) - REALM_SC_PRINT(RemoveProperty, v.object, v.property) - -#undef REALM_SC_PRINT -}; - -namespace Catch { -template<> -struct StringMaker<SchemaChange> { - static std::string convert(SchemaChange const& sc) - { - std::stringstream ss; - sc.visit(SchemaChangePrinter{ss}); - return ss.str(); - } -}; -} // namespace Catch - -#define REQUIRE_THROWS_CONTAINING(expr, msg) \ - REQUIRE_THROWS_WITH(expr, Catch::Matchers::Contains(msg)) - -TEST_CASE("ObjectSchema") { - - SECTION("Aliases are still present in schema returned from the Realm") { - TestFile config; - config.schema_version = 1; - config.schema = Schema{ - {"object", { - {"value", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "alias"} - }}, - }; - - auto realm = Realm::get_shared_realm(config); - REQUIRE(realm->schema().find("object")->property_for_name("value")->public_name == "alias"); - } - - SECTION("looking up properties by alias matches name if alias is not set") { - auto schema = Schema{ - {"object", { - {"value", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "alias"}, - {"other_value", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}} - }}, - }; - - REQUIRE(schema.find("object")->property_for_public_name("value") == nullptr); - REQUIRE(schema.find("object")->property_for_public_name("alias")->name == "value"); - REQUIRE(schema.find("object")->property_for_public_name("other_value")->name == "other_value"); - } - - SECTION("from a Group") { - Group g; - - TableRef table = g.add_table_with_primary_key("class_table", type_Int, "pk"); - TableRef target = g.add_table("class_target"); - - table->add_column(type_Int, "int"); - table->add_column(type_Bool, "bool"); - table->add_column(type_Float, "float"); - table->add_column(type_Double, "double"); - table->add_column(type_String, "string"); - table->add_column(type_Binary, "data"); - table->add_column(type_Timestamp, "date"); - - table->add_column_link(type_Link, "object", *target); - table->add_column_link(type_LinkList, "array", *target); - - table->add_column(type_Int, "int?", true); - table->add_column(type_Bool, "bool?", true); - table->add_column(type_Float, "float?", true); - table->add_column(type_Double, "double?", true); - table->add_column(type_String, "string?", true); - table->add_column(type_Binary, "data?", true); - table->add_column(type_Timestamp, "date?", true); - - auto add_list = [](TableRef table, DataType type, StringData name, bool nullable) { - table->add_column_list(type, name, nullable); - }; - - add_list(table, type_Int, "int array", false); - add_list(table, type_Bool, "bool array", false); - add_list(table, type_Float, "float array", false); - add_list(table, type_Double, "double array", false); - add_list(table, type_String, "string array", false); - add_list(table, type_Binary, "data array", false); - add_list(table, type_Timestamp, "date array", false); - add_list(table, type_Int, "int? array", true); - add_list(table, type_Bool, "bool? array", true); - add_list(table, type_Float, "float? array", true); - add_list(table, type_Double, "double? array", true); - add_list(table, type_String, "string? array", true); - add_list(table, type_Binary, "data? array", true); - add_list(table, type_Timestamp, "date? array", true); - - std::vector<ColKey> indexed_cols; - indexed_cols.push_back(table->add_column(type_Int, "indexed int")); - indexed_cols.push_back(table->add_column(type_Bool, "indexed bool")); - indexed_cols.push_back(table->add_column(type_String, "indexed string")); - indexed_cols.push_back(table->add_column(type_Timestamp, "indexed date")); - - indexed_cols.push_back(table->add_column(type_Int, "indexed int?", true)); - indexed_cols.push_back(table->add_column(type_Bool, "indexed bool?", true)); - indexed_cols.push_back(table->add_column(type_String, "indexed string?", true)); - indexed_cols.push_back(table->add_column(type_Timestamp, "indexed date?", true)); - - for (auto col : indexed_cols) - table->add_search_index(col); - - ObjectSchema os(g, "table", table->get_key()); - REQUIRE(os.table_key == table->get_key()); - -#define REQUIRE_PROPERTY(name, type, ...) do { \ - Property* prop; \ - REQUIRE((prop = os.property_for_name(name))); \ - REQUIRE((*prop == Property{name, PropertyType::type, __VA_ARGS__})); \ - REQUIRE(prop->column_key == *expected_col++); \ -} while (0) - - auto all_column_keys = table->get_column_keys(); - auto expected_col = all_column_keys.begin(); - - REQUIRE(os.property_for_name("nonexistent property") == nullptr); - - REQUIRE_PROPERTY("pk", Int, Property::IsPrimary{true}); - - REQUIRE_PROPERTY("int", Int); - REQUIRE_PROPERTY("bool", Bool); - REQUIRE_PROPERTY("float", Float); - REQUIRE_PROPERTY("double", Double); - REQUIRE_PROPERTY("string", String); - REQUIRE_PROPERTY("data", Data); - REQUIRE_PROPERTY("date", Date); - - REQUIRE_PROPERTY("object", Object|PropertyType::Nullable, "target"); - REQUIRE_PROPERTY("array", Array|PropertyType::Object, "target"); - - REQUIRE_PROPERTY("int?", Int|PropertyType::Nullable); - REQUIRE_PROPERTY("bool?", Bool|PropertyType::Nullable); - REQUIRE_PROPERTY("float?", Float|PropertyType::Nullable); - REQUIRE_PROPERTY("double?", Double|PropertyType::Nullable); - REQUIRE_PROPERTY("string?", String|PropertyType::Nullable); - REQUIRE_PROPERTY("data?", Data|PropertyType::Nullable); - REQUIRE_PROPERTY("date?", Date|PropertyType::Nullable); - - REQUIRE_PROPERTY("int array", Int|PropertyType::Array); - REQUIRE_PROPERTY("bool array", Bool|PropertyType::Array); - REQUIRE_PROPERTY("float array", Float|PropertyType::Array); - REQUIRE_PROPERTY("double array", Double|PropertyType::Array); - REQUIRE_PROPERTY("string array", String|PropertyType::Array); - REQUIRE_PROPERTY("data array", Data|PropertyType::Array); - REQUIRE_PROPERTY("date array", Date|PropertyType::Array); - REQUIRE_PROPERTY("int? array", Int|PropertyType::Array|PropertyType::Nullable); - REQUIRE_PROPERTY("bool? array", Bool|PropertyType::Array|PropertyType::Nullable); - REQUIRE_PROPERTY("float? array", Float|PropertyType::Array|PropertyType::Nullable); - REQUIRE_PROPERTY("double? array", Double|PropertyType::Array|PropertyType::Nullable); - REQUIRE_PROPERTY("string? array", String|PropertyType::Array|PropertyType::Nullable); - REQUIRE_PROPERTY("data? array", Data|PropertyType::Array|PropertyType::Nullable); - REQUIRE_PROPERTY("date? array", Date|PropertyType::Array|PropertyType::Nullable); - - REQUIRE_PROPERTY("indexed int", Int, Property::IsPrimary{false}, Property::IsIndexed{true}); - REQUIRE_PROPERTY("indexed bool", Bool, Property::IsPrimary{false}, Property::IsIndexed{true}); - REQUIRE_PROPERTY("indexed string", String, Property::IsPrimary{false}, Property::IsIndexed{true}); - REQUIRE_PROPERTY("indexed date", Date, Property::IsPrimary{false}, Property::IsIndexed{true}); - - REQUIRE_PROPERTY("indexed int?", Int|PropertyType::Nullable, Property::IsPrimary{false}, Property::IsIndexed{true}); - REQUIRE_PROPERTY("indexed bool?", Bool|PropertyType::Nullable, Property::IsPrimary{false}, Property::IsIndexed{true}); - REQUIRE_PROPERTY("indexed string?", String|PropertyType::Nullable, Property::IsPrimary{false}, Property::IsIndexed{true}); - REQUIRE_PROPERTY("indexed date?", Date|PropertyType::Nullable, Property::IsPrimary{false}, Property::IsIndexed{true}); - } -} - -TEST_CASE("Schema") { - SECTION("validate()") { - SECTION("rejects link properties with no target object") { - Schema schema = { - {"object", { - {"link", PropertyType::Object|PropertyType::Nullable} - }}, - }; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.link' of type 'object' has unknown object type ''"); - } - - SECTION("rejects array properties with no target object") { - Schema schema = { - {"object", { - {"array", PropertyType::Array|PropertyType::Object} - }}, - }; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.array' of type 'array' has unknown object type ''"); - } - - SECTION("rejects link properties with a target not in the schema") { - Schema schema = { - {"object", { - {"link", PropertyType::Object|PropertyType::Nullable, "invalid target"} - }} - }; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.link' of type 'object' has unknown object type 'invalid target'"); - } - - SECTION("rejects array properties with a target not in the schema") { - Schema schema = { - {"object", { - {"array", PropertyType::Array|PropertyType::Object, "invalid target"} - }} - }; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.array' of type 'array' has unknown object type 'invalid target'"); - } - - SECTION("rejects linking objects without a source object") { - Schema schema = { - {"object", { - {"value", PropertyType::Int}, - }, { - {"incoming", PropertyType::Array|PropertyType::LinkingObjects, "", ""} - }} - }; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.incoming' of type 'linking objects' has unknown object type ''"); - } - - SECTION("rejects linking objects without a source property") { - Schema schema = { - {"object", { - {"value", PropertyType::Int}, - }, { - {"incoming", PropertyType::Array|PropertyType::LinkingObjects, "object", ""} - }} - }; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.incoming' of type 'linking objects' must have an origin property name."); - } - - SECTION("rejects linking objects with invalid source object") { - Schema schema = { - {"object", { - {"value", PropertyType::Int}, - }, { - {"incoming", PropertyType::Array|PropertyType::LinkingObjects, "not an object type", ""} - }} - }; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.incoming' of type 'linking objects' has unknown object type 'not an object type'"); - } - - SECTION("rejects linking objects with invalid source property") { - Schema schema = { - {"object", { - {"value", PropertyType::Int}, - }, { - {"incoming", PropertyType::Array|PropertyType::LinkingObjects, "object", "value"} - }} - }; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.value' declared as origin of linking objects property 'object.incoming' is not a link"); - - schema = { - {"object", { - {"value", PropertyType::Int}, - {"link", PropertyType::Object|PropertyType::Nullable, "object 2"}, - }, { - {"incoming", PropertyType::Array|PropertyType::LinkingObjects, "object", "link"} - }}, - {"object 2", { - {"value", PropertyType::Int}, - }} - }; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.link' declared as origin of linking objects property 'object.incoming' links to type 'object 2'"); - - } - - SECTION("rejects non-array linking objects") { - Schema schema = { - {"object", { - {"link", PropertyType::Object|PropertyType::Nullable, "object"}, - }, { - {"incoming", PropertyType::LinkingObjects, "object", "link"} - }} - }; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Linking Objects property 'object.incoming' must be an array."); - } - - SECTION("rejects target object types for non-link properties") { - Schema schema = { - {"object", { - {"int", PropertyType::Int}, - {"bool", PropertyType::Bool}, - {"float", PropertyType::Float}, - {"double", PropertyType::Double}, - {"string", PropertyType::String}, - {"date", PropertyType::Date}, - }} - }; - for (auto& prop : schema.begin()->persisted_properties) { - REQUIRE_NOTHROW(schema.validate()); - prop.object_type = "object"; - REQUIRE_THROWS_CONTAINING(schema.validate(), "cannot have an object type."); - prop.object_type = ""; - } - } - - SECTION("rejects source property name for non-linking objects properties") { - Schema schema = { - {"object", { - {"int", PropertyType::Int}, - {"bool", PropertyType::Bool}, - {"float", PropertyType::Float}, - {"double", PropertyType::Double}, - {"string", PropertyType::String}, - {"data", PropertyType::Data}, - {"date", PropertyType::Date}, - {"object", PropertyType::Object|PropertyType::Nullable, "object"}, - {"array", PropertyType::Object|PropertyType::Array, "object"}, - }} - }; - for (auto& prop : schema.begin()->persisted_properties) { - REQUIRE_NOTHROW(schema.validate()); - prop.link_origin_property_name = "source"; - auto expected = util::format("Property 'object.%1' of type '%1' cannot have an origin property name.", prop.name, prop.name); - REQUIRE_THROWS_CONTAINING(schema.validate(), expected); - prop.link_origin_property_name = ""; - } - } - - SECTION("rejects non-nullable link properties") { - Schema schema = { - {"object", { - {"link", PropertyType::Object, "target"} - }}, - {"target", { - {"value", PropertyType::Int} - }} - }; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.link' of type 'object' must be nullable."); - } - - SECTION("rejects nullable array properties") { - Schema schema = { - {"object", { - {"array", PropertyType::Array|PropertyType::Object|PropertyType::Nullable, "target"} - }}, - {"target", { - {"value", PropertyType::Int} - }} - }; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.array' of type 'array' cannot be nullable."); - } - - SECTION("rejects nullable linking objects") { - Schema schema = { - {"object", { - {"link", PropertyType::Object|PropertyType::Nullable, "object"}, - }, { - {"incoming", PropertyType::LinkingObjects|PropertyType::Array|PropertyType::Nullable, "object", "link"} - }} - }; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.incoming' of type 'linking objects' cannot be nullable."); - } - - SECTION("rejects duplicate primary keys") { - Schema schema = { - {"object", { - {"pk1", PropertyType::Int, Property::IsPrimary{true}}, - {"pk2", PropertyType::Int, Property::IsPrimary{true}}, - }} - }; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Properties 'pk2' and 'pk1' are both marked as the primary key of 'object'."); - } - - SECTION("rejects invalid primary key types") { - Schema schema = { - {"object", { - {"pk", PropertyType::Float, Property::IsPrimary{true}}, - }} - }; - - schema.begin()->primary_key_property()->type = PropertyType::Any; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'any' cannot be made the primary key."); - - schema.begin()->primary_key_property()->type = PropertyType::Bool; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'bool' cannot be made the primary key."); - - schema.begin()->primary_key_property()->type = PropertyType::Float; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'float' cannot be made the primary key."); - - schema.begin()->primary_key_property()->type = PropertyType::Double; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'double' cannot be made the primary key."); - - schema.begin()->primary_key_property()->type = PropertyType::Object; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'object' cannot be made the primary key."); - - schema.begin()->primary_key_property()->type = PropertyType::LinkingObjects; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'linking objects' cannot be made the primary key."); - - schema.begin()->primary_key_property()->type = PropertyType::Data; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'data' cannot be made the primary key."); - - schema.begin()->primary_key_property()->type = PropertyType::Date; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'date' cannot be made the primary key."); - } - - SECTION("allows valid primary key types") { - Schema schema = { - {"object", { - {"pk", PropertyType::Int, Property::IsPrimary{true}}, - }} - }; - - REQUIRE_NOTHROW(schema.validate()); - - schema.begin()->primary_key_property()->type = PropertyType::Int|PropertyType::Nullable; - REQUIRE_NOTHROW(schema.validate()); - schema.begin()->primary_key_property()->type = PropertyType::String; - REQUIRE_NOTHROW(schema.validate()); - schema.begin()->primary_key_property()->type = PropertyType::String|PropertyType::Nullable; - REQUIRE_NOTHROW(schema.validate()); - } - - SECTION("rejects nonexistent primary key") { - Schema schema = { - {"object", { - {"value", PropertyType::Int}, - }} - }; - schema.begin()->primary_key = "nonexistent"; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Specified primary key 'object.nonexistent' does not exist."); - } - - SECTION("rejects indexes for types that cannot be indexed") { - Schema schema = { - {"object", { - {"float", PropertyType::Float}, - {"double", PropertyType::Double}, - {"data", PropertyType::Data}, - {"object", PropertyType::Object|PropertyType::Nullable, "object"}, - {"array", PropertyType::Array|PropertyType::Object, "object"}, - }} - }; - for (auto& prop : schema.begin()->persisted_properties) { - REQUIRE_NOTHROW(schema.validate()); - prop.is_indexed = true; - auto expected = util::format("Property 'object.%1' of type '%1' cannot be indexed.", prop.name); - REQUIRE_THROWS_CONTAINING(schema.validate(), expected); - prop.is_indexed = false; - } - } - - SECTION("allows indexing types that can be indexed") { - Schema schema = { - {"object", { - {"int", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}}, - {"bool", PropertyType::Bool, Property::IsPrimary{false}, Property::IsIndexed{true}}, - {"string", PropertyType::String, Property::IsPrimary{false}, Property::IsIndexed{true}}, - {"date", PropertyType::Date, Property::IsPrimary{false}, Property::IsIndexed{true}}, - }} - }; - REQUIRE_NOTHROW(schema.validate()); - } - - SECTION("rejects duplicate types with the same name") { - Schema schema = { - {"object1", { - {"int", PropertyType::Int}, - }}, - {"object2", { - {"int", PropertyType::Int}, - }}, - {"object3", { - {"int", PropertyType::Int}, - }}, - {"object2", { - {"int", PropertyType::Int}, - }}, - {"object1", { - {"int", PropertyType::Int}, - }} - }; - - REQUIRE_THROWS_CONTAINING(schema.validate(), - "- Type 'object1' appears more than once in the schema.\n" - "- Type 'object2' appears more than once in the schema."); - } - - SECTION("rejects properties with the same name") { - Schema schema = { - {"object", { - {"child", PropertyType::Object|PropertyType::Nullable, "object"}, - {"parent", PropertyType::Int}, - {"field1", PropertyType::Int}, - {"field2", PropertyType::String}, - {"field1", PropertyType::String}, - {"field2", PropertyType::String}, - {"field1", PropertyType::Int}, - }, { - {"parent", PropertyType::Array|PropertyType::LinkingObjects, "object", "child"} - }} - }; - - REQUIRE_THROWS_CONTAINING(schema.validate(), - "- Property 'field1' appears more than once in the schema for type 'object'.\n" - "- Property 'field2' appears more than once in the schema for type 'object'.\n" - "- Property 'parent' appears more than once in the schema for type 'object'."); - } - - SECTION("rejects schema if all properties have the same name") { - Schema schema = { - {"object", { - {"field", PropertyType::Int}, - {"otherField", PropertyType::Int}, - {"field", PropertyType::Int}, - {"otherField", PropertyType::Int}, - {"field", PropertyType::Int}, - {"otherField", PropertyType::Int}, - {"field", PropertyType::Int}, - {"otherField", PropertyType::Int}, - {"field", PropertyType::Int}, - {"otherField", PropertyType::Int}, - }} - }; - - REQUIRE_THROWS_CONTAINING(schema.validate(), - "- Property 'field' appears more than once in the schema for type 'object'.\n" - "- Property 'otherField' appears more than once in the schema for type 'object'."); - } - - SECTION("rejects properties with the same alias") { - Schema schema = { - {"object", { - {"child", PropertyType::Object|PropertyType::Nullable, "object"}, - - // Alias == Name on computed property - {"parentA", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "_parent"}, - - // Name == Alias on other property - {"fieldA", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "_field1"}, - {"fieldB", PropertyType::String, Property::IsPrimary{false}, Property::IsIndexed{false}, "_field2"}, - {"fieldC", PropertyType::String, Property::IsPrimary{false}, Property::IsIndexed{false}, "_field1"}, - {"fieldD", PropertyType::String, Property::IsPrimary{false}, Property::IsIndexed{false}, "_field2"}, - {"fieldE", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "_field1"}, - - // Name == Alias - {"fieldF", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "fieldF"}, - }, { - // Computed property alias == name on persisted property - {"parentB", PropertyType::Array|PropertyType::LinkingObjects, "object", "child", "_parent"} - }} - }; - - REQUIRE_THROWS_CONTAINING(schema.validate(), - "- Alias '_field1' appears more than once in the schema for type 'object'.\n" - "- Alias '_field2' appears more than once in the schema for type 'object'.\n" - "- Alias '_parent' appears more than once in the schema for type 'object'."); - } - - SECTION("rejects properties whose name conflicts with an alias for another property") { - Schema schema = { - {"object", { - {"child", PropertyType::Object|PropertyType::Nullable, "object"}, - {"field1", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "field2"}, - {"field2", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "parent"}, - }, { - {"parent", PropertyType::Array|PropertyType::LinkingObjects, "object", "child", "field1"} - }} - }; - - REQUIRE_THROWS_CONTAINING(schema.validate(), - "- Property 'object.parent' has an alias 'field1' that conflicts with a property of the same name.\n" - "- Property 'object.field1' has an alias 'field2' that conflicts with a property of the same name.\n" - "- Property 'object.field2' has an alias 'parent' that conflicts with a property of the same name."); - } - } - - SECTION("compare()") { - using namespace schema_change; - using vec = std::vector<SchemaChange>; - SECTION("add table") { - Schema schema1 = { - {"object 1", { - {"int", PropertyType::Int}, - }} - }; - Schema schema2 = { - {"object 1", { - {"int", PropertyType::Int}, - }}, - {"object 2", { - {"int", PropertyType::Int}, - }} - }; - auto obj = &*schema2.find("object 2"); - auto expected = vec{AddTable{obj}, AddInitialProperties{obj}}; - REQUIRE(schema1.compare(schema2) == expected); - } - - SECTION("add property") { - Schema schema1 = { - {"object", { - {"int 1", PropertyType::Int}, - }} - }; - Schema schema2 = { - {"object", { - {"int 1", PropertyType::Int}, - {"int 2", PropertyType::Int}, - }} - }; - REQUIRE(schema1.compare(schema2) == vec{(AddProperty{&*schema1.find("object"), &schema2.find("object")->persisted_properties[1]})}); - } - - SECTION("remove property") { - Schema schema1 = { - {"object", { - {"int 1", PropertyType::Int}, - {"int 2", PropertyType::Int}, - }} - }; - Schema schema2 = { - {"object", { - {"int 1", PropertyType::Int}, - }} - }; - REQUIRE(schema1.compare(schema2) == vec{(RemoveProperty{&*schema1.find("object"), &schema1.find("object")->persisted_properties[1]})}); - } - - SECTION("change property type") { - Schema schema1 = { - {"object", { - {"value", PropertyType::Int}, - }} - }; - Schema schema2 = { - {"object", { - {"value", PropertyType::Double}, - }} - }; - REQUIRE(schema1.compare(schema2) == vec{(ChangePropertyType{ - &*schema1.find("object"), - &schema1.find("object")->persisted_properties[0], - &schema2.find("object")->persisted_properties[0]})}); - }; - - SECTION("change link target") { - Schema schema1 = { - {"object", { - {"value", PropertyType::Object, "target 1"}, - }}, - {"target 1", { - {"value", PropertyType::Int}, - }}, - {"target 2", { - {"value", PropertyType::Int}, - }}, - }; - Schema schema2 = { - {"object", { - {"value", PropertyType::Object, "target 2"}, - }}, - {"target 1", { - {"value", PropertyType::Int}, - }}, - {"target 2", { - {"value", PropertyType::Int}, - }}, - }; - REQUIRE(schema1.compare(schema2) == vec{(ChangePropertyType{ - &*schema1.find("object"), - &schema1.find("object")->persisted_properties[0], - &schema2.find("object")->persisted_properties[0]})}); - } - - SECTION("add index") { - Schema schema1 = { - {"object", { - {"int", PropertyType::Int}, - }} - }; - Schema schema2 = { - {"object", { - {"int", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}}, - }} - }; - auto object_schema = &*schema1.find("object"); - REQUIRE(schema1.compare(schema2) == vec{(AddIndex{object_schema, &object_schema->persisted_properties[0]})}); - } - - SECTION("remove index") { - Schema schema1 = { - {"object", { - {"int", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}}, - }} - }; - Schema schema2 = { - {"object", { - {"int", PropertyType::Int}, - }} - }; - auto object_schema = &*schema1.find("object"); - REQUIRE(schema1.compare(schema2) == vec{(RemoveIndex{object_schema, &object_schema->persisted_properties[0]})}); - } - - SECTION("add index and make nullable") { - Schema schema1 = { - {"object", { - {"int", PropertyType::Int}, - }} - }; - Schema schema2 = { - {"object", { - {"int", PropertyType::Int|PropertyType::Nullable, Property::IsPrimary{false}, Property::IsIndexed{true}}, - }} - }; - auto object_schema = &*schema1.find("object"); - REQUIRE(schema1.compare(schema2) == (vec{ - MakePropertyNullable{object_schema, &object_schema->persisted_properties[0]}, - AddIndex{object_schema, &object_schema->persisted_properties[0]}})); - } - - SECTION("add index and change type") { - Schema schema1 = { - {"object", { - {"value", PropertyType::Int}, - }} - }; - Schema schema2 = { - {"object", { - {"value", PropertyType::Double, Property::IsPrimary{false}, Property::IsIndexed{true}}, - }} - }; - REQUIRE(schema1.compare(schema2) == vec{(ChangePropertyType{ - &*schema1.find("object"), - &schema1.find("object")->persisted_properties[0], - &schema2.find("object")->persisted_properties[0]})}); - } - - SECTION("make nullable and change type") { - Schema schema1 = { - {"object", { - {"value", PropertyType::Int}, - }} - }; - Schema schema2 = { - {"object", { - {"value", PropertyType::Double|PropertyType::Nullable}, - }} - }; - REQUIRE(schema1.compare(schema2) == vec{(ChangePropertyType{ - &*schema1.find("object"), - &schema1.find("object")->persisted_properties[0], - &schema2.find("object")->persisted_properties[0]})}); - } - } -} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/thread_safe_reference.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/thread_safe_reference.cpp deleted file mode 100644 index 2594e3a12..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/thread_safe_reference.cpp +++ /dev/null @@ -1,896 +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 "catch2/catch.hpp" - -#include "util/test_file.hpp" -#include "util/test_utils.hpp" - -#include "list.hpp" -#include "object.hpp" -#include "object_schema.hpp" -#include "object_store.hpp" -#include "results.hpp" -#include "schema.hpp" -#include "thread_safe_reference.hpp" -#include "util/scheduler.hpp" - -#include "impl/object_accessor_impl.hpp" -#include "impl/realm_coordinator.hpp" - -#include <realm/db.hpp> -#include <realm/history.hpp> -#include <realm/string_data.hpp> -#include <realm/util/optional.hpp> - -using namespace realm; - -static TableRef get_table(Realm& realm, StringData object_name) { - return ObjectStore::table_for_object_type(realm.read_group(), object_name); -} - -static Object create_object(SharedRealm const& realm, StringData object_type, AnyDict value) { - CppContext ctx(realm); - return Object::create(ctx, realm, object_type, util::Any(value)); -} - -TEST_CASE("thread safe reference") { - using namespace std::string_literals; - - Schema schema{ - {"foo object", { - {"ignore me", PropertyType::Int}, // Used in tests cases that don't care about the value. - }}, - {"string object", { - {"value", PropertyType::String|PropertyType::Nullable}, - }}, - {"int object", { - {"value", PropertyType::Int}, - }}, - {"int array object", { - {"value", PropertyType::Array|PropertyType::Object, "int object"} - }}, - {"int array", { - {"value", PropertyType::Array|PropertyType::Int} - }}, - }; - - InMemoryTestFile config; - config.automatic_change_notifications = false; - SharedRealm r = Realm::get_shared_realm(config); - r->update_schema(schema); - - // Convenience object - r->begin_transaction(); - auto foo = create_object(r, "foo object", {{"ignore me", INT64_C(0)}}); - r->commit_transaction(); - - const auto int_obj_col = r->schema().find("int object")->persisted_properties[0].column_key; - - SECTION("allowed during write transactions") { - SECTION("obtain") { - r->begin_transaction(); - REQUIRE_NOTHROW(ThreadSafeReference(foo)); - } - SECTION("resolve") { - auto ref = ThreadSafeReference(foo); - r->begin_transaction(); - REQUIRE_NOTHROW(ref.resolve<Object>(r)); - } - } - - SECTION("cleanup properly unpins version") { - auto history = make_in_realm_history(config.path); - auto shared_group = DB::create(*history, config.options()); - - auto get_current_version = [&]() -> VersionID { - auto rt = shared_group->start_read(); - auto version = rt->get_version_of_current_transaction(); - return version; - }; - - auto reference_version = get_current_version(); - auto ref = util::make_optional(ThreadSafeReference(foo)); - r->begin_transaction(); r->commit_transaction(); // Advance version - - REQUIRE(get_current_version() != reference_version); // Ensure advanced - REQUIRE_NOTHROW(shared_group->start_read(reference_version)); // Ensure pinned - - ref = {}; // Destroy thread safe reference, unpinning version - r->begin_transaction(); r->commit_transaction(); // Clean up old versions - REQUIRE_THROWS(shared_group->start_read(reference_version)); // Verify unpinned - } - - SECTION("version mismatch") { - SECTION("resolves at older version") { - r->begin_transaction(); - Object num = create_object(r, "int object", {{"value", INT64_C(7)}}); - r->commit_transaction(); - - ColKey col = num.get_object_schema().property_for_name("value")->column_key; - ObjKey k = num.obj().get_key(); - - REQUIRE(num.obj().get<Int>(col) == 7); - ThreadSafeReference ref; - { - SharedRealm r2 = Realm::get_shared_realm(config); - Object num = Object(r2, "int object", k); - REQUIRE(num.obj().get<Int>(col) == 7); - - r2->begin_transaction(); - num.obj().set(col, 9); - r2->commit_transaction(); - - ref = num; - }; - - REQUIRE(num.obj().get<Int>(col) == 7); - Object num_prime = ref.resolve<Object>(r); - REQUIRE(num_prime.obj().get<Int>(col) == 9); - REQUIRE(num.obj().get<Int>(col) == 9); - - r->begin_transaction(); - num.obj().set(col, 11); - r->commit_transaction(); - - REQUIRE(num_prime.obj().get<Int>(col) == 11); - REQUIRE(num.obj().get<Int>(col) == 11); - } - - SECTION("resolve at newer version") { - r->begin_transaction(); - Object num = create_object(r, "int object", {{"value", INT64_C(7)}}); - r->commit_transaction(); - - ColKey col = num.get_object_schema().property_for_name("value")->column_key; - ObjKey k = num.obj().get_key(); - - REQUIRE(num.obj().get<Int>(col) == 7); - auto ref = ThreadSafeReference(num); - { - SharedRealm r2 = Realm::get_shared_realm(config); - Object num = Object(r2, "int object", k); - - r2->begin_transaction(); - num.obj().set(col, 9); - r2->commit_transaction(); - REQUIRE(num.obj().get<Int>(col) == 9); - - Object num_prime = ref.resolve<Object>(r2); - REQUIRE(num_prime.obj().get<Int>(col) == 9); - - r2->begin_transaction(); - num_prime.obj().set(col, 11); - r2->commit_transaction(); - - REQUIRE(num.obj().get<Int>(col) == 11); - REQUIRE(num_prime.obj().get<Int>(col) == 11); - } - - REQUIRE(num.obj().get<Int>(col) == 7); - r->refresh(); - REQUIRE(num.obj().get<Int>(col) == 11); - } - - SECTION("resolve at newer version when schema is specified") { - r->close(); - config.schema = schema; - SharedRealm r = Realm::get_shared_realm(config); - r->begin_transaction(); - Object num = create_object(r, "int object", {{"value", INT64_C(7)}}); - r->commit_transaction(); - - ColKey col = num.get_object_schema().property_for_name("value")->column_key; - auto ref = ThreadSafeReference(num); - - r->begin_transaction(); - num.obj().set(col, 9); - r->commit_transaction(); - - REQUIRE_NOTHROW(ref.resolve<Object>(r)); - } - - SECTION("resolve references at multiple versions") { - auto commit_new_num = [&](int64_t value) -> Object { - r->begin_transaction(); - Object num = create_object(r, "int object", {{"value", value}}); - r->commit_transaction(); - return num; - }; - - auto ref1 = ThreadSafeReference(commit_new_num(1)); - auto ref2 = ThreadSafeReference(commit_new_num(2)); - { - SharedRealm r2 = Realm::get_shared_realm(config); - Object num1 = ref1.resolve<Object>(r2); - Object num2 = ref2.resolve<Object>(r2); - - ColKey col = num1.get_object_schema().property_for_name("value")->column_key; - REQUIRE(num1.obj().get<Int>(col) == 1); - REQUIRE(num2.obj().get<Int>(col) == 2); - } - } - } - - SECTION("same thread") { - r->begin_transaction(); - Object num = create_object(r, "int object", {{"value", INT64_C(7)}}); - r->commit_transaction(); - - ColKey col = num.get_object_schema().property_for_name("value")->column_key; - REQUIRE(num.obj().get<Int>(col) == 7); - auto ref = ThreadSafeReference(num); - bool did_run_section = false; - - SECTION("same realm") { - did_run_section = true; - { - Object num = ref.resolve<Object>(r); - REQUIRE(num.obj().get<Int>(col) == 7); - r->begin_transaction(); - num.obj().set(col, 9); - r->commit_transaction(); - REQUIRE(num.obj().get<Int>(col) == 9); - } - REQUIRE(num.obj().get<Int>(col) == 9); - } - SECTION("different realm") { - did_run_section = true; - { - SharedRealm r = Realm::get_shared_realm(config); - Object num = ref.resolve<Object>(r); - REQUIRE(num.obj().get<Int>(col) == 7); - r->begin_transaction(); - num.obj().set(col, 9); - r->commit_transaction(); - REQUIRE(num.obj().get<Int>(col) == 9); - } - REQUIRE(num.obj().get<Int>(col) == 7); - } - catch2_ensure_section_run_workaround(did_run_section, "same thread", [&](){ - r->begin_transaction(); // advance to latest version by starting a write - REQUIRE(num.obj().get<Int>(col) == 9); - r->cancel_transaction(); - }); - } - - SECTION("passing over") { - SECTION("objects") { - r->begin_transaction(); - auto str = create_object(r, "string object", {}); - auto num = create_object(r, "int object", {{"value", INT64_C(0)}}); - r->commit_transaction(); - - ColKey col_num = num.get_object_schema().property_for_name("value")->column_key; - ColKey col_str = str.get_object_schema().property_for_name("value")->column_key; - auto ref_str = ThreadSafeReference(str); - auto ref_num = ThreadSafeReference(num); - { - SharedRealm r2 = Realm::get_shared_realm(config); - Object str = ref_str.resolve<Object>(r2); - Object num = ref_num.resolve<Object>(r2); - - REQUIRE(str.obj().get<String>(col_str).is_null()); - REQUIRE(num.obj().get<Int>(col_num) == 0); - - r2->begin_transaction(); - str.obj().set(col_str, "the meaning of life"); - num.obj().set(col_num, 42); - r2->commit_transaction(); - } - - REQUIRE(str.obj().get<String>(col_str).is_null()); - REQUIRE(num.obj().get<Int>(col_num) == 0); - - r->refresh(); - - REQUIRE(str.obj().get<String>(col_str) == "the meaning of life"); - REQUIRE(num.obj().get<Int>(col_num) == 42); - } - - SECTION("object list") { - r->begin_transaction(); - auto zero = create_object(r, "int object", {{"value", INT64_C(0)}}); - auto obj = create_object(r, "int array object", {{"value", AnyVector{zero}}}); - auto col = get_table(*r, "int array object")->get_column_key("value"); - List list(r, obj.obj(), col); - r->commit_transaction(); - - REQUIRE(list.size() == 1); - REQUIRE(list.get(0).get<int64_t>(int_obj_col) == 0); - auto ref = ThreadSafeReference(list); - { - SharedRealm r2 = Realm::get_shared_realm(config); - List list = ref.resolve<List>(r2); - REQUIRE(list.size() == 1); - REQUIRE(list.get(0).get<int64_t>(int_obj_col) == 0); - - r2->begin_transaction(); - list.remove_all(); - auto one = create_object(r2, "int object", {{"value", INT64_C(1)}}); - auto two = create_object(r2, "int object", {{"value", INT64_C(2)}}); - list.add(one.obj()); - list.add(two.obj()); - r2->commit_transaction(); - - REQUIRE(list.size() == 2); - REQUIRE(list.get(0).get<int64_t>(int_obj_col) == 1); - REQUIRE(list.get(1).get<int64_t>(int_obj_col) == 2); - } - - REQUIRE(list.size() == 1); - REQUIRE(list.get(0).get<int64_t>(int_obj_col) == 0); - - r->refresh(); - - REQUIRE(list.size() == 2); - REQUIRE(list.get(0).get<int64_t>(int_obj_col) == 1); - REQUIRE(list.get(1).get<int64_t>(int_obj_col) == 2); - } - - SECTION("sorted object results") { - auto& table = *get_table(*r, "string object"); - auto col = table.get_column_key("value"); - auto results = Results(r, table.where().not_equal(col, "C")).sort({{{col}}, {false}}); - - r->begin_transaction(); - create_object(r, "string object", {{"value", "A"s}}); - create_object(r, "string object", {{"value", "B"s}}); - create_object(r, "string object", {{"value", "C"s}}); - create_object(r, "string object", {{"value", "D"s}}); - r->commit_transaction(); - - REQUIRE(results.size() == 3); - REQUIRE(results.get(0).get<StringData>(col) == "D"); - REQUIRE(results.get(1).get<StringData>(col) == "B"); - REQUIRE(results.get(2).get<StringData>(col) == "A"); - auto ref = ThreadSafeReference(results); - { - SharedRealm r2 = Realm::get_shared_realm(config); - Results results = ref.resolve<Results>(r2); - - REQUIRE(results.size() == 3); - REQUIRE(results.get(0).get<StringData>(col) == "D"); - REQUIRE(results.get(1).get<StringData>(col) == "B"); - REQUIRE(results.get(2).get<StringData>(col) == "A"); - - r2->begin_transaction(); - results.get(2).remove(); - results.get(0).remove(); - create_object(r2, "string object", {{"value", "E"s}}); - r2->commit_transaction(); - - REQUIRE(results.size() == 2); - REQUIRE(results.get(0).get<StringData>(col) == "E"); - REQUIRE(results.get(1).get<StringData>(col) == "B"); - } - - REQUIRE(results.size() == 3); - REQUIRE(results.get(0).get<StringData>(col) == "D"); - REQUIRE(results.get(1).get<StringData>(col) == "B"); - REQUIRE(results.get(2).get<StringData>(col) == "A"); - - r->refresh(); - - REQUIRE(results.size() == 2); - REQUIRE(results.get(0).get<StringData>(col) == "E"); - REQUIRE(results.get(1).get<StringData>(col) == "B"); - } - - SECTION("distinct object results") { - auto& table = *get_table(*r, "string object"); - auto col = table.get_column_key("value"); - auto results = Results(r, table.where()).distinct({{{col}}}).sort({{"value", true}}); - - r->begin_transaction(); - create_object(r, "string object", {{"value", "A"s}}); - create_object(r, "string object", {{"value", "A"s}}); - create_object(r, "string object", {{"value", "B"s}}); - r->commit_transaction(); - - REQUIRE(results.size() == 2); - REQUIRE(results.get(0).get<StringData>(col) == "A"); - REQUIRE(results.get(1).get<StringData>(col) == "B"); - auto ref = ThreadSafeReference(results); - { - SharedRealm r2 = Realm::get_shared_realm(config); - Results results = ref.resolve<Results>(r2); - - REQUIRE(results.size() == 2); - REQUIRE(results.get(0).get<StringData>(col) == "A"); - REQUIRE(results.get(1).get<StringData>(col) == "B"); - - r2->begin_transaction(); - results.get(0).remove(); - create_object(r2, "string object", {{"value", "C"s}}); - r2->commit_transaction(); - - REQUIRE(results.size() == 3); - REQUIRE(results.get(0).get<StringData>(col) == "A"); - REQUIRE(results.get(1).get<StringData>(col) == "B"); - REQUIRE(results.get(2).get<StringData>(col) == "C"); - } - - REQUIRE(results.size() == 2); - REQUIRE(results.get(0).get<StringData>(col) == "A"); - REQUIRE(results.get(1).get<StringData>(col) == "B"); - - r->refresh(); - - REQUIRE(results.size() == 3); - REQUIRE(results.get(0).get<StringData>(col) == "A"); - REQUIRE(results.get(1).get<StringData>(col) == "B"); - REQUIRE(results.get(2).get<StringData>(col) == "C"); - } - - SECTION("int list") { - r->begin_transaction(); - auto obj = create_object(r, "int array", {{"value", AnyVector{INT64_C(0)}}}); - auto col = get_table(*r, "int array")->get_column_key("value"); - List list(r, obj.obj(), col); - r->commit_transaction(); - - auto ref = ThreadSafeReference(list); - { - SharedRealm r2 = Realm::get_shared_realm(config); - List list = ref.resolve<List>(r2); - REQUIRE(list.size() == 1); - REQUIRE(list.get<int64_t>(0) == 0); - - r2->begin_transaction(); - list.remove_all(); - list.add(int64_t(1)); - list.add(int64_t(2)); - r2->commit_transaction(); - - REQUIRE(list.size() == 2); - REQUIRE(list.get<int64_t>(0) == 1); - REQUIRE(list.get<int64_t>(1) == 2); - }; - - REQUIRE(list.size() == 1); - REQUIRE(list.get<int64_t>(0) == 0); - - r->refresh(); - - REQUIRE(list.size() == 2); - REQUIRE(list.get<int64_t>(0) == 1); - REQUIRE(list.get<int64_t>(1) == 2); - } - - SECTION("sorted int results") { - r->begin_transaction(); - auto obj = create_object(r, "int array", {{"value", AnyVector{INT64_C(0), INT64_C(2), INT64_C(1)}}}); - auto col = get_table(*r, "int array")->get_column_key("value"); - List list(r, obj.obj(), col); - r->commit_transaction(); - - auto results = list.sort({{"self", true}}); - - REQUIRE(results.size() == 3); - REQUIRE(results.get<int64_t>(0) == 0); - REQUIRE(results.get<int64_t>(1) == 1); - REQUIRE(results.get<int64_t>(2) == 2); - auto ref = ThreadSafeReference(results); - std::thread([ref = std::move(ref), config]() mutable { - config.scheduler = util::Scheduler::get_frozen(); - SharedRealm r = Realm::get_shared_realm(config); - Results results = ref.resolve<Results>(r); - - REQUIRE(results.size() == 3); - REQUIRE(results.get<int64_t>(0) == 0); - REQUIRE(results.get<int64_t>(1) == 1); - REQUIRE(results.get<int64_t>(2) == 2); - - r->begin_transaction(); - auto table = get_table(*r, "int array"); - List list(r, *table->begin(), table->get_column_key("value")); - list.remove(1); - list.add(int64_t(-1)); - r->commit_transaction(); - - REQUIRE(results.size() == 3); - REQUIRE(results.get<int64_t>(0) == -1); - REQUIRE(results.get<int64_t>(1) == 0); - REQUIRE(results.get<int64_t>(2) == 1); - }).join(); - - REQUIRE(results.size() == 3); - REQUIRE(results.get<int64_t>(0) == 0); - REQUIRE(results.get<int64_t>(1) == 1); - REQUIRE(results.get<int64_t>(2) == 2); - - r->refresh(); - - REQUIRE(results.size() == 3); - REQUIRE(results.get<int64_t>(0) == -1); - REQUIRE(results.get<int64_t>(1) == 0); - REQUIRE(results.get<int64_t>(2) == 1); - } - - SECTION("distinct int results") { - r->begin_transaction(); - auto obj = create_object( - r, "int array", {{"value", AnyVector{INT64_C(3), INT64_C(2), INT64_C(1), INT64_C(1), INT64_C(2)}}}); - auto col = get_table(*r, "int array")->get_column_key("value"); - List list(r, obj.obj(), col); - r->commit_transaction(); - - auto results = list.as_results().distinct({"self"}).sort({{"self", true}}); - - REQUIRE(results.size() == 3); - REQUIRE(results.get<int64_t>(0) == 1); - REQUIRE(results.get<int64_t>(1) == 2); - REQUIRE(results.get<int64_t>(2) == 3); - - auto ref = ThreadSafeReference(results); - std::thread([ref = std::move(ref), config]() mutable { - config.scheduler = util::Scheduler::get_frozen(); - SharedRealm r = Realm::get_shared_realm(config); - Results results = ref.resolve<Results>(r); - - REQUIRE(results.size() == 3); - REQUIRE(results.get<int64_t>(0) == 1); - REQUIRE(results.get<int64_t>(1) == 2); - REQUIRE(results.get<int64_t>(2) == 3); - - r->begin_transaction(); - auto table = get_table(*r, "int array"); - List list(r, *table->begin(), table->get_column_key("value")); - list.remove(1); - list.remove(0); - r->commit_transaction(); - - REQUIRE(results.size() == 2); - REQUIRE(results.get<int64_t>(0) == 1); - REQUIRE(results.get<int64_t>(1) == 2); - }).join(); - - REQUIRE(results.size() == 3); - REQUIRE(results.get<int64_t>(0) == 1); - REQUIRE(results.get<int64_t>(1) == 2); - REQUIRE(results.get<int64_t>(2) == 3); - - r->refresh(); - - REQUIRE(results.size() == 2); - REQUIRE(results.get<int64_t>(0) == 1); - REQUIRE(results.get<int64_t>(1) == 2); - } - - SECTION("multiple types") { - auto results = Results(r, get_table(*r, "int object")->where().equal(int_obj_col, 5)); - - r->begin_transaction(); - auto num = create_object(r, "int object", {{"value", INT64_C(5)}}); - auto obj = create_object(r, "int array object", {{"value", AnyVector{}}}); - auto col = get_table(*r, "int array object")->get_column_key("value"); - List list(r, obj.obj(), col); - r->commit_transaction(); - - REQUIRE(list.size() == 0); - REQUIRE(results.size() == 1); - REQUIRE(results.get(0).get<int64_t>(int_obj_col) == 5); - auto ref_num = ThreadSafeReference(num); - auto ref_list = ThreadSafeReference(list); - auto ref_results = ThreadSafeReference(results); - { - SharedRealm r2 = Realm::get_shared_realm(config); - auto num = ref_num.resolve<Object>(r2); - auto list = ref_list.resolve<List>(r2); - auto results = ref_results.resolve<Results>(r2); - - REQUIRE(list.size() == 0); - REQUIRE(results.size() == 1); - REQUIRE(results.get(0).get<int64_t>(int_obj_col) == 5); - - r2->begin_transaction(); - num.obj().set_all(6); - list.add(num.obj().get_key()); - r2->commit_transaction(); - - REQUIRE(list.size() == 1); - REQUIRE(list.get(0).get<int64_t>(int_obj_col) == 6); - REQUIRE(results.size() == 0); - } - - REQUIRE(list.size() == 0); - REQUIRE(results.size() == 1); - REQUIRE(results.get(0).get<int64_t>(int_obj_col) == 5); - - r->refresh(); - - REQUIRE(list.size() == 1); - REQUIRE(list.get(0).get<int64_t>(int_obj_col) == 6); - REQUIRE(results.size() == 0); - } - } - - SECTION("resolve at version where handed over thing has been deleted") { - Object obj; - auto delete_and_resolve = [&](auto&& list) { - auto ref = ThreadSafeReference(list); - - r->begin_transaction(); - obj.obj().remove(); - r->commit_transaction(); - - return ref.resolve<typename std::remove_reference<decltype(list)>::type>(r); - }; - - SECTION("object") { - r->begin_transaction(); - obj = create_object(r, "int object", {{"value", INT64_C(7)}}); - r->commit_transaction(); - - REQUIRE(!delete_and_resolve(obj).is_valid()); - } - - SECTION("object list") { - r->begin_transaction(); - obj = create_object(r, "int array object", {{"value", AnyVector{AnyDict{{"value", INT64_C(0)}}}}}); - auto col = get_table(*r, "int array object")->get_column_key("value"); - List list(r, obj.obj(), col); - r->commit_transaction(); - - REQUIRE(!delete_and_resolve(list).is_valid()); - } - - SECTION("int list") { - r->begin_transaction(); - obj = create_object(r, "int array", {{"value", AnyVector{{INT64_C(1)}}}}); - auto col = get_table(*r, "int array")->get_column_key("value"); - List list(r, obj.obj(), col); - r->commit_transaction(); - - REQUIRE(!delete_and_resolve(list).is_valid()); - } - - SECTION("object results") { - r->begin_transaction(); - obj = create_object(r, "int array object", {{"value", AnyVector{AnyDict{{"value", INT64_C(0)}}}}}); - auto col = get_table(*r, "int array object")->get_column_key("value"); - List list(r, obj.obj(), col); - r->commit_transaction(); - - auto results = delete_and_resolve(list.sort({{"value", true}})); - REQUIRE(results.is_valid()); - REQUIRE(results.size() == 0); - } - - SECTION("int results") { - r->begin_transaction(); - obj = create_object(r, "int array", {{"value", AnyVector{{INT64_C(1)}}}}); - List list(r, obj.obj(), get_table(*r, "int array")->get_column_key("value")); - r->commit_transaction(); - - REQUIRE(!delete_and_resolve(list).is_valid()); - } - } - - SECTION("resolve at version before where handed over thing was created") { - auto create_ref = [&](auto&& fn) -> ThreadSafeReference { - ThreadSafeReference ref; - { - SharedRealm r2 = Realm::get_shared_realm(config); - r2->begin_transaction(); - auto obj = fn(r2); - r2->commit_transaction(); - ref = obj; - }; - return ref; - }; - - SECTION("object") { - auto obj = create_ref([](auto& r) { - return create_object(r, "int object", {{"value", INT64_C(7)}}); - }).resolve<Object>(r); - REQUIRE(obj.is_valid()); - REQUIRE(obj.get_column_value<int64_t>("value") == 7); - } - - SECTION("object list") { - auto list = create_ref([](auto& r) { - auto obj = create_object(r, "int array object", {{"value", AnyVector{AnyDict{{"value", INT64_C(0)}}}}}); - return List(r, obj.obj(), get_table(*r, "int array object")->get_column_key("value")); - }).resolve<List>(r); - REQUIRE(list.is_valid()); - REQUIRE(list.size() == 1); - } - - SECTION("int list") { - auto list = create_ref([](auto& r) { - auto obj = create_object(r, "int array", {{"value", AnyVector{{INT64_C(1)}}}}); - return List(r, obj.obj(), get_table(*r, "int array")->get_column_key("value")); - }).resolve<List>(r); - REQUIRE(list.is_valid()); - REQUIRE(list.size() == 1); - } - - SECTION("object results") { - auto results = create_ref([](auto& r) { - auto obj = create_object(r, "int array object", {{"value", AnyVector{AnyDict{{"value", INT64_C(0)}}}}}); - Results results = List(r, obj.obj(), get_table(*r, "int array object")->get_column_key("value")) - .sort({{"value", true}}); - REQUIRE(results.size() == 1); - return results; - }).resolve<Results>(r); - REQUIRE(results.is_valid()); - REQUIRE(results.size() == 1); - } - - SECTION("int results") { - auto results = create_ref([](auto& r) { - auto obj = create_object(r, "int array", {{"value", AnyVector{{INT64_C(1)}}}}); - return List(r, obj.obj(), get_table(*r, "int array")->get_column_key("value")).sort({{"self", true}}); - }).resolve<Results>(r); - REQUIRE(results.is_valid()); - REQUIRE(results.size() == 1); - } - } - - SECTION("create TSR inside the write transaction which created the object being handed over") { - auto create_ref = [&](auto&& fn) -> ThreadSafeReference { - ThreadSafeReference ref; - { - SharedRealm r2 = Realm::get_shared_realm(config); - r2->begin_transaction(); - ref = fn(r2); - r2->commit_transaction(); - }; - return ref; - }; - - SECTION("object") { - auto obj = create_ref([](auto& r) { - return create_object(r, "int object", {{"value", INT64_C(7)}}); - }).resolve<Object>(r); - REQUIRE(obj.is_valid()); - REQUIRE(obj.get_column_value<int64_t>("value") == 7); - } - - SECTION("object list") { - auto list = create_ref([](auto& r) { - auto obj = create_object(r, "int array object", {{"value", AnyVector{AnyDict{{"value", INT64_C(0)}}}}}); - return List(r, obj.obj(), get_table(*r, "int array object")->get_column_key("value")); - }).resolve<List>(r); - REQUIRE(list.is_valid()); - REQUIRE(list.size() == 1); - } - - SECTION("int list") { - auto list = create_ref([](auto& r) { - auto obj = create_object(r, "int array", {{"value", AnyVector{{INT64_C(1)}}}}); - return List(r, obj.obj(), get_table(*r, "int array")->get_column_key("value")); - }).resolve<List>(r); - REQUIRE(list.is_valid()); - REQUIRE(list.size() == 1); - } - - SECTION("object results") { - REQUIRE_THROWS(create_ref([](auto& r) { - auto obj = create_object(r, "int array object", {{"value", AnyVector{AnyDict{{"value", INT64_C(0)}}}}}); - Results results = List(r, obj.obj(), get_table(*r, "int array object")->get_column_key("value")) - .sort({{"value", true}}); - REQUIRE(results.size() == 1); - return results; - })); - } - - SECTION("int results") { - auto results = create_ref([](auto& r) { - auto obj = create_object(r, "int array", {{"value", AnyVector{{INT64_C(1)}}}}); - return List(r, obj.obj(), get_table(*r, "int array")->get_column_key("value")).sort({{"self", true}}); - }).resolve<Results>(r); - REQUIRE(results.is_valid()); - REQUIRE(results.size() == 1); - } - } - - SECTION("create TSR inside cancelled write transaction") { - auto create_ref = [&](auto&& fn) -> ThreadSafeReference { - ThreadSafeReference ref; - { - SharedRealm r2 = Realm::get_shared_realm(config); - r2->begin_transaction(); - ref = fn(r2); - r2->cancel_transaction(); - }; - return ref; - }; - - SECTION("object") { - auto obj = create_ref([](auto& r) { - return create_object(r, "int object", {{"value", INT64_C(7)}}); - }).resolve<Object>(r); - REQUIRE_FALSE(obj.is_valid()); - } - - SECTION("object list") { - auto list = create_ref([](auto& r) { - auto obj = create_object(r, "int array object", {{"value", AnyVector{AnyDict{{"value", INT64_C(0)}}}}}); - return List(r, obj.obj(), get_table(*r, "int array object")->get_column_key("value")); - }).resolve<List>(r); - REQUIRE_FALSE(list.is_valid()); - } - - SECTION("int list") { - auto list = create_ref([](auto& r) { - auto obj = create_object(r, "int array", {{"value", AnyVector{{INT64_C(1)}}}}); - return List(r, obj.obj(), get_table(*r, "int array")->get_column_key("value")); - }).resolve<List>(r); - REQUIRE_FALSE(list.is_valid()); - } - - SECTION("object results") { - REQUIRE_THROWS(create_ref([](auto& r) { - auto obj = create_object(r, "int array object", {{"value", AnyVector{AnyDict{{"value", INT64_C(0)}}}}}); - Results results = List(r, obj.obj(), get_table(*r, "int array object")->get_column_key("value")) - .sort({{"value", true}}); - REQUIRE(results.size() == 1); - return results; - })); - } - - SECTION("int results") { - auto results = create_ref([](auto& r) { - auto obj = create_object(r, "int array", {{"value", AnyVector{{INT64_C(1)}}}}); - return List(r, obj.obj(), get_table(*r, "int array")->get_column_key("value")).sort({{"self", true}}); - }).resolve<Results>(r); - REQUIRE_FALSE(results.is_valid()); - } - } - - SECTION("lifetime") { - SECTION("retains source realm") { // else version will become unpinned - auto ref = ThreadSafeReference(foo); - foo = {}; - r = nullptr; - r = Realm::get_shared_realm(config); - REQUIRE_NOTHROW(ref.resolve<Object>(r)); - } - - SECTION("retains source RealmCoordinator") { - auto ref = ThreadSafeReference(foo); - auto coordinator = _impl::RealmCoordinator::get_existing_coordinator(config.path).get(); - foo = {}; - r = nullptr; - REQUIRE(coordinator == _impl::RealmCoordinator::get_existing_coordinator(config.path).get()); - } - } - - SECTION("metadata") { - r->begin_transaction(); - auto num = create_object(r, "int object", {{"value", INT64_C(5)}}); - r->commit_transaction(); - REQUIRE(num.get_object_schema().name == "int object"); - - auto ref = ThreadSafeReference(num); - { - SharedRealm r2 = Realm::get_shared_realm(config); - Object num = ref.resolve<Object>(r2); - REQUIRE(num.get_object_schema().name == "int object"); - } - } - - SECTION("allow multiple resolves") { - auto ref = ThreadSafeReference(foo); - ref.resolve<Object>(r); - REQUIRE_NOTHROW(ref.resolve<Object>(r)); - } -} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/transaction_log_parsing.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/transaction_log_parsing.cpp deleted file mode 100644 index ed52366de..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/transaction_log_parsing.cpp +++ /dev/null @@ -1,1714 +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 "catch2/catch.hpp" - -#include "util/index_helpers.hpp" -#include "util/test_file.hpp" -#include "util/test_utils.hpp" - -#include "impl/collection_notifier.hpp" -#include "impl/realm_coordinator.hpp" -#include "impl/transact_log_handler.hpp" -#include "binding_context.hpp" -#include "property.hpp" -#include "object_schema.hpp" -#include "schema.hpp" - -#include <realm/db.hpp> -#include <realm/history.hpp> - -using namespace realm; - -class CaptureHelper { -public: - CaptureHelper(TransactionRef group, SharedRealm const& r, LnkLst& lv, TableKey table_key) - : m_realm(r) - , m_group(group) - , m_list(lv) - , m_table_key(table_key) - { - m_realm->begin_transaction(); - - m_initial.reserve(lv.size()); - for (size_t i = 0; i < lv.size(); ++i) - m_initial.push_back(lv.ObjList::get_key(i)); - } - - CollectionChangeSet finish() { - m_realm->commit_transaction(); - - _impl::CollectionChangeBuilder c; - _impl::TransactionChangeInfo info{}; - info.tables[m_table_key.value]; - info.lists.push_back({m_table_key, m_list.ConstLstBase::get_key().value, m_list.get_col_key().value, &c}); - _impl::transaction::advance(*m_group, info); - - if (info.lists.empty()) { - REQUIRE(!m_list.is_attached()); - return {}; - } - - validate(c); - return std::move(c); - } - - explicit operator bool() const { return m_realm->is_in_transaction(); } - -private: - SharedRealm m_realm; - TransactionRef m_group; - - LnkLst& m_list; - std::vector<ObjKey> m_initial; - TableKey m_table_key; - - void validate(CollectionChangeSet const& info) - { - info.insertions.verify(); - info.deletions.verify(); - info.modifications.verify(); - - std::vector<ObjKey> 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); - } - - m_list.size(); - for (auto const& range : info.insertions) { - for (auto i = range.first; i < range.second; ++i) - m_initial.insert(m_initial.begin() + i, m_list.ObjList::get_key(i)); - } - - for (auto const& range : info.modifications) { - for (auto i = range.first; i < range.second; ++i) - m_initial[i] = m_list.ObjList::get_key(i); - } - - REQUIRE(m_list.is_attached()); - - // and make sure we end up with the same end result - if (m_initial.size() != m_list.size()) { - std::cout << "Error " << m_list.size() << std::endl; - } - REQUIRE(m_initial.size() == m_list.size()); - for (size_t i = 0; i < m_initial.size(); ++i) - CHECK(m_initial[i] == m_list.ObjList::get_key(i)); - - // 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_list.ObjList::get_key(info.moves[i].to) == move_sources[i]); - } - } - } -}; - -struct ArrayChange { - BindingContext::ColumnInfo::Kind kind; - IndexSet indices; -}; - -static bool operator==(ArrayChange const& a, ArrayChange const& b) -{ - return a.kind == b.kind - && std::equal(a.indices.as_indexes().begin(), a.indices.as_indexes().end(), - b.indices.as_indexes().begin(), b.indices.as_indexes().end()); -} - -namespace Catch { -template<> -struct StringMaker<ArrayChange> { - static std::string convert(ArrayChange const& c) - { - std::stringstream ss; - switch (c.kind) { - case BindingContext::ColumnInfo::Kind::Insert: ss << "Insert{"; break; - case BindingContext::ColumnInfo::Kind::Remove: ss << "Remove{"; break; - case BindingContext::ColumnInfo::Kind::Set: ss << "Set{"; break; - case BindingContext::ColumnInfo::Kind::SetAll: return "SetAll"; - case BindingContext::ColumnInfo::Kind::None: return "None"; - } - for (auto& range : c.indices) - ss << range.first << "-" << range.second << ", "; - auto str = ss.str(); - str.pop_back(); - str.back() = '}'; - return str; - } -}; -} // namespace Catch - -class KVOContext : public BindingContext { -public: - KVOContext(std::initializer_list<Obj> objects) - { - m_result.reserve(objects.size()); - for (auto& obj : objects) { - m_result.push_back(ObserverState{obj.get_table()->get_key(), - obj.get_key().value, (void *)(uintptr_t)m_result.size()}); - } - } - - bool modified(size_t index, ColKey col_key) const noexcept - { - auto it = std::find_if(begin(m_result), end(m_result), - [=](auto&& change) { return (void *)(uintptr_t)index == change.info; }); - if (it == m_result.end()) - return false; - auto col = it->changes.find(col_key.value); - return col != it->changes.end() && col->second.kind != BindingContext::ColumnInfo::Kind::None; - } - - bool invalidated(size_t index) const noexcept - { - return std::find(begin(m_invalidated), end(m_invalidated), (void *)(uintptr_t)index) != end(m_invalidated); - } - - ArrayChange array_change(size_t index, ColKey col_key) const noexcept - { - auto& changes = m_result[index].changes; - auto col = changes.find(col_key.value); - return col == changes.end() - ? ArrayChange{ColumnInfo::Kind::None, {}} - : ArrayChange{col->second.kind, col->second.indices}; - } - -private: - std::vector<ObserverState> m_result; - std::vector<void*> m_invalidated; - - std::vector<ObserverState> get_observed_rows() override - { - return m_result; - } - - void did_change(std::vector<ObserverState> const& observers, - std::vector<void*> const& invalidated, bool) override - { - m_invalidated = invalidated; - m_result = observers; - } -}; - -TEST_CASE("Transaction log parsing: schema change validation") { - InMemoryTestFile config; - config.automatic_change_notifications = false; - config.schema_mode = SchemaMode::Additive; - auto r = Realm::get_shared_realm(config); - r->update_schema({ - {"table", { - {"unindexed", PropertyType::Int}, - {"indexed", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}} - }}, - }); - r->read_group(); - - auto history = make_in_realm_history(config.path); - auto db = DB::create(*history, config.options()); - - SECTION("adding a table is allowed") { - auto wt = db->start_write(); - TableRef table = wt->add_table("new table"); - table->add_column(type_String, "new col"); - wt->commit(); - - REQUIRE_NOTHROW(r->refresh()); - } - - SECTION("adding a column at the end of an existing table is allowed") { - auto wt = db->start_write(); - TableRef table = wt->get_table("class_table"); - table->add_column(type_String, "new col"); - wt->commit(); - - REQUIRE_NOTHROW(r->refresh()); - } - - SECTION("removing a column is not allowed") { - auto wt = db->start_write(); - TableRef table = wt->get_table("class_table"); - table->remove_column(table->get_column_key("indexed")); - wt->commit(); - - REQUIRE_THROWS(r->refresh()); - } - - SECTION("removing a table is not allowed") { - auto wt = db->start_write(); - wt->remove_table("class_table"); - wt->commit(); - - REQUIRE_THROWS(r->refresh()); - } -} - -TEST_CASE("Transaction log parsing: changeset calcuation") { - InMemoryTestFile config; - config.automatic_change_notifications = false; - - SECTION("table change information") { - auto r = Realm::get_shared_realm(config); - r->update_schema({ - {"table", { - {"pk", PropertyType::Int, Property::IsPrimary{true}}, - {"value", PropertyType::Int} - }}, - }); - - auto& table = *r->read_group().get_table("class_table"); - auto table_key = table.get_key().value; - auto cols = table.get_column_keys(); - - r->begin_transaction(); - std::vector<ObjKey> objects; - table.create_objects(10, objects); - for (int i = 0; i < 10; ++i) - table.get_object(objects[i]).set_all(i, i); - r->commit_transaction(); - - auto coordinator = _impl::RealmCoordinator::get_coordinator(config.path); - using TableKeyType = decltype(TableKey::value); - auto track_changes = [&](std::vector<TableKeyType> tables_needed, auto&& f) { - auto sg = coordinator->begin_read(); - - r->begin_transaction(); - f(); - r->commit_transaction(); - - _impl::TransactionChangeInfo info{}; - for (auto table : tables_needed) - info.tables[table]; - _impl::transaction::advance(static_cast<Transaction&>(*sg), info); - return info; - }; - - SECTION("modifying a row marks it as modified") { - auto info = track_changes({table_key}, [&] { - table.get_object(objects[1]).set(cols[1], 2); - }); - REQUIRE(info.tables.size() == 1); - REQUIRE(info.tables[table_key].modifications_size() == 1); - REQUIRE(info.tables[table_key].modifications_contains(1)); - } - - SECTION("modifications to untracked tables are ignored") { - auto info = track_changes({}, [&] { - table.get_object(objects[1]).set(cols[1], 2); - }); - REQUIRE(info.tables.empty()); - } - - SECTION("new row additions are reported") { - auto info = track_changes({table_key}, [&] { - table.create_object(); - table.create_object(); - }); - REQUIRE(info.tables.size() == 1); - REQUIRE(info.tables[table_key].insertions_size() == 2); - REQUIRE(info.tables[table_key].insertions_contains(10)); - REQUIRE(info.tables[table_key].insertions_contains(11)); - } - - SECTION("deleting newly added rows makes them not be reported") { - auto info = track_changes({table_key}, [&] { - table.create_object(); - table.remove_object(table.create_object().get_key()); - }); - REQUIRE(info.tables.size() == 1); - REQUIRE(info.tables[table_key].insertions_size() == 1); - REQUIRE(info.tables[table_key].insertions_contains(10)); - REQUIRE(info.tables[table_key].deletions_empty()); - } - - SECTION("modifying newly added rows does not report it as a modification") { - auto info = track_changes({table_key}, [&] { - table.create_object().set_all(10, 0); - }); - REQUIRE(info.tables.size() == 1); - REQUIRE(info.tables[table_key].insertions_size() == 1); - REQUIRE(info.tables[table_key].insertions_contains(10)); - REQUIRE(info.tables[table_key].modifications_size() == 0); - REQUIRE(!info.tables[table_key].modifications_contains(10)); - REQUIRE(info.tables[table_key].deletions_empty()); - } - - SECTION("remove_object() does not shift rows") { - auto info = track_changes({table_key}, [&] { - table.remove_object(objects[2]); - table.remove_object(objects[3]); - }); - REQUIRE(info.tables.size() == 1); - REQUIRE(info.tables[table_key].deletions_size() == 2); - REQUIRE(info.tables[table_key].deletions_contains(2)); - REQUIRE(info.tables[table_key].deletions_contains(3)); - REQUIRE(info.tables[table_key].insertions_empty()); - REQUIRE(info.tables[table_key].modifications_empty()); - } - - SECTION("SetDefault does not mark a row as modified") { - auto info = track_changes({table_key}, [&] { - bool is_default = true; - table.get_object(objects[0]).set(cols[0], 1, is_default); - }); - REQUIRE(info.tables.empty()); - } - } - - SECTION("LinkView change information") { - auto r = Realm::get_shared_realm(config); - r->update_schema({ - {"origin", { - {"array", PropertyType::Array|PropertyType::Object, "target"} - }}, - {"target", { - {"value", PropertyType::Int} - }}, - }); - - auto origin = r->read_group().get_table("class_origin"); - auto target = r->read_group().get_table("class_target"); - - r->begin_transaction(); - - LnkLst lv = origin->create_object().get_linklist("array"); - std::vector<ObjKey> target_keys; - for (int i = 0; i < 10; ++i) { - target_keys.push_back(target->create_object().set_all(i).get_key()); - lv.add(target_keys.back()); - } - - r->commit_transaction(); - - auto coordinator = _impl::RealmCoordinator::get_coordinator(config.path); -#define VALIDATE_CHANGES(out) \ - for (CaptureHelper helper(std::static_pointer_cast<Transaction>(coordinator->begin_read()), r, lv, origin->get_key()); \ - helper; out = helper.finish()) - - CollectionChangeSet changes; - SECTION("single change type") { - SECTION("add single") { - VALIDATE_CHANGES(changes) { - lv.add(target_keys[0]); - } - REQUIRE_INDICES(changes.insertions, 10); - } - SECTION("add multiple") { - VALIDATE_CHANGES(changes) { - lv.add(target_keys[0]); - lv.add(target_keys[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, target_keys[0]); - } - REQUIRE_INDICES(changes.modifications, 5); - } - SECTION("set contiguous") { - VALIDATE_CHANGES(changes) { - lv.set(5, target_keys[0]); - lv.set(6, target_keys[0]); - lv.set(7, target_keys[0]); - } - REQUIRE_INDICES(changes.modifications, 5, 6, 7); - } - SECTION("set scattered") { - VALIDATE_CHANGES(changes) { - lv.set(5, target_keys[0]); - lv.set(7, target_keys[0]); - lv.set(9, target_keys[0]); - } - REQUIRE_INDICES(changes.modifications, 5, 7, 9); - } - SECTION("set redundant") { - VALIDATE_CHANGES(changes) { - lv.set(5, target_keys[0]); - lv.set(5, target_keys[0]); - lv.set(5, target_keys[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->get_object(5).remove(); - } - 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_MOVES(changes, {3, 5}, {5, 3}); - } - } - - SECTION("mixed change types") { - SECTION("set -> insert") { - VALIDATE_CHANGES(changes) { - lv.set(5, target_keys[0]); - lv.insert(5, target_keys[0]); - } - REQUIRE_INDICES(changes.insertions, 5); - REQUIRE_INDICES(changes.modifications, 6); - - VALIDATE_CHANGES(changes) { - lv.set(4, target_keys[0]); - lv.insert(5, target_keys[0]); - } - REQUIRE_INDICES(changes.insertions, 5); - REQUIRE_INDICES(changes.modifications, 4); - } - SECTION("insert -> set") { - VALIDATE_CHANGES(changes) { - lv.insert(5, target_keys[0]); - lv.set(5, target_keys[1]); - } - REQUIRE_INDICES(changes.insertions, 5); - REQUIRE_INDICES(changes.modifications, 5); - - VALIDATE_CHANGES(changes) { - lv.insert(5, target_keys[0]); - lv.set(6, target_keys[1]); - } - REQUIRE_INDICES(changes.insertions, 5); - REQUIRE_INDICES(changes.modifications, 6); - - VALIDATE_CHANGES(changes) { - lv.insert(6, target_keys[0]); - lv.set(5, target_keys[1]); - } - REQUIRE_INDICES(changes.insertions, 6); - REQUIRE_INDICES(changes.modifications, 5); - } - - SECTION("set -> erase") { - VALIDATE_CHANGES(changes) { - lv.set(5, target_keys[0]); - lv.remove(5); - } - REQUIRE_INDICES(changes.deletions, 5); - REQUIRE(changes.modifications.empty()); - - VALIDATE_CHANGES(changes) { - lv.set(5, target_keys[0]); - lv.remove(4); - } - REQUIRE_INDICES(changes.deletions, 4); - REQUIRE_INDICES(changes.modifications, 4); - - VALIDATE_CHANGES(changes) { - lv.set(5, target_keys[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, target_keys[0]); - } - REQUIRE_INDICES(changes.deletions, 5); - REQUIRE_INDICES(changes.modifications, 5); - } - - SECTION("insert -> clear") { - VALIDATE_CHANGES(changes) { - lv.add(target_keys[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, target_keys[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(target_keys[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(target_keys[0]); - lv.remove(10); - } - REQUIRE(changes.insertions.empty()); - REQUIRE(changes.deletions.empty()); - - VALIDATE_CHANGES(changes) { - lv.add(target_keys[0]); - lv.remove(9); - } - REQUIRE_INDICES(changes.deletions, 9); - REQUIRE_INDICES(changes.insertions, 9); - - VALIDATE_CHANGES(changes) { - lv.insert(1, target_keys[1]); - lv.insert(3, target_keys[3]); - lv.insert(5, target_keys[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, target_keys[1]); - lv.insert(3, target_keys[3]); - lv.insert(5, target_keys[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(target_keys[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, target_keys[9]); - lv.insert(3, target_keys[7]); - lv.insert(2, target_keys[5]); - lv.insert(1, target_keys[3]); - lv.insert(0, target_keys[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, target_keys[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, target_keys[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, target_keys[5]); - lv.set(6, target_keys[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, target_keys[5]); - } - REQUIRE_MOVES(changes, {2, 9}); - } - } - - SECTION("deleting the linkview") { - SECTION("directly") { - VALIDATE_CHANGES(changes) { - origin->get_object(0).remove(); - } - 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(); - auto new_obj = origin->create_object(); - r->commit_transaction(); - - VALIDATE_CHANGES(changes) { - new_obj.remove(); - } - REQUIRE(changes.insertions.empty()); - REQUIRE(changes.deletions.empty()); - REQUIRE(changes.modifications.empty()); - } - } - - SECTION("modifying a different linkview should not produce notifications") { - r->begin_transaction(); - auto lv2 = origin->create_object().get_linklist("array"); - lv2.add(target_keys[5]); - r->commit_transaction(); - - VALIDATE_CHANGES(changes) { - lv2.add(target_keys[1]); - lv2.add(target_keys[2]); - lv2.remove(0); - lv2.set(0, target_keys[6]); - lv2.move(1, 0); - lv2.swap(0, 1); - lv2.clear(); - lv2.add(target_keys[1]); - } - - REQUIRE(changes.insertions.empty()); - REQUIRE(changes.deletions.empty()); - REQUIRE(changes.modifications.empty()); - } - } - - SECTION("object change information") { - auto realm = Realm::get_shared_realm(config); - realm->update_schema({ - {"origin", { - {"pk", PropertyType::Int, Property::IsPrimary{true}}, - {"link", PropertyType::Object|PropertyType::Nullable, "target"}, - {"array", PropertyType::Array|PropertyType::Object, "target"}, - {"int array", PropertyType::Array|PropertyType::Int}, - }}, - {"origin 2", { - {"pk", PropertyType::Int, Property::IsPrimary{true}}, - {"link", PropertyType::Object|PropertyType::Nullable, "target"}, - {"array", PropertyType::Array|PropertyType::Object, "target"} - }}, - {"target", { - {"pk", PropertyType::Int, Property::IsPrimary{true}}, - {"value 1", PropertyType::Int}, - {"value 2", PropertyType::Int}, - }}, - }); - - auto origin = realm->read_group().get_table("class_origin"); - auto target = realm->read_group().get_table("class_target"); - auto origin_cols = origin->get_column_keys(); - auto target_cols = target->get_column_keys(); - - realm->begin_transaction(); - - std::vector<ObjKey> target_keys; - target->create_objects(10, target_keys); - for (int i = 0; i < 10; ++i) - target->get_object(target_keys[i]).set_all(i, i, i); - - std::vector<ObjKey> origin_keys; - origin->create_objects(3, origin_keys); - origin->get_object(origin_keys[0]).set_all(5, target_keys[5]); - origin->get_object(origin_keys[1]).set_all(5, target_keys[6]); - - auto lv = origin->get_object(origin_keys[0]).get_linklist(origin_cols[2]); - for (auto key : target_keys) - lv.add(key); - auto lv2 = origin->get_object(origin_keys[1]).get_linklist(origin_cols[2]); - lv2.add(target_keys[0]); - - auto tr = origin->get_object(origin_keys[0]).get_list<int64_t>(origin_cols[3]); - for (int i = 0; i < 10; ++i) - tr.add(i); - auto tr2 = origin->get_object(origin_keys[1]).get_list<int64_t>(origin_cols[3]); - for (int i = 0; i < 10; ++i) - tr2.add(0); - - realm->read_group().get_table("class_origin 2")->create_object(); - - realm->commit_transaction(); - - auto observe = [&](std::initializer_list<Obj> rows, auto&& fn) { - auto realm2 = Realm::get_shared_realm(config); - auto& group = realm2->read_group(); - static_cast<void>(group); // silence unused warning - KVOContext observer(rows); - observer.realm = realm2; - realm2->m_binding_context.reset(&observer); - - realm->begin_transaction(); - lv.size(); lv2.size(); tr.size(); tr2.size(); - fn(); - realm->commit_transaction(); - lv.size(); lv2.size(); tr.size(); tr2.size(); - - realm2->refresh(); - realm2->m_binding_context.release(); - - return observer; - }; - - auto observe_rollback = [&](std::initializer_list<Obj> rows, auto&& fn) { - KVOContext observer(rows); - observer.realm = realm; - realm->m_binding_context.reset(&observer); - - realm->begin_transaction(); - lv.size(); lv2.size(); tr.size(); tr2.size(); - fn(); - realm->cancel_transaction(); - lv.size(); lv2.size(); tr.size(); tr2.size(); - - realm->m_binding_context.release(); - return observer; - }; - - SECTION("setting a property marks that property as changed") { - auto o = target->get_object(target_keys[0]); - auto changes = observe({o}, [&] { - o.set(target_cols[0], 1); - }); - REQUIRE(changes.modified(0, target_cols[0])); - REQUIRE_FALSE(changes.modified(0, target_cols[1])); - REQUIRE_FALSE(changes.modified(0, target_cols[2])); - } - - SECTION("self-assignment marks as changed") { - auto o = target->get_object(target_keys[0]); - auto changes = observe({o}, [&] { - o.set(target_cols[0], o.get<int64_t>(target_cols[0])); - }); - REQUIRE(changes.modified(0, target_cols[0])); - REQUIRE_FALSE(changes.modified(0, target_cols[1])); - REQUIRE_FALSE(changes.modified(0, target_cols[2])); - } - - SECTION("SetDefault does not mark as changed") { - auto o = target->get_object(target_keys[0]); - auto changes = observe({o}, [&] { - o.set(target_cols[0], 5, true); - }); - REQUIRE_FALSE(changes.modified(0, target_cols[0])); - REQUIRE_FALSE(changes.modified(0, target_cols[1])); - REQUIRE_FALSE(changes.modified(0, target_cols[2])); - } - - SECTION("multiple properties on a single object are handled properly") { - auto o = target->get_object(target_keys[0]); - auto changes = observe({o}, [&] { - o.set(target_cols[1], 1); - }); - REQUIRE_FALSE(changes.modified(0, target_cols[0])); - REQUIRE(changes.modified(0, target_cols[1])); - REQUIRE_FALSE(changes.modified(0, target_cols[2])); - - changes = observe({o}, [&] { - o.set(target_cols[2], 1); - }); - REQUIRE_FALSE(changes.modified(0, target_cols[0])); - REQUIRE_FALSE(changes.modified(0, target_cols[1])); - REQUIRE(changes.modified(0, target_cols[2])); - - changes = observe({o}, [&] { - o.set(target_cols[0], 1); - o.set(target_cols[2], 1); - }); - REQUIRE(changes.modified(0, target_cols[0])); - REQUIRE_FALSE(changes.modified(0, target_cols[1])); - REQUIRE(changes.modified(0, target_cols[2])); - - changes = observe({o}, [&] { - o.set(target_cols[0], 1); - o.set(target_cols[1], 1); - o.set(target_cols[2], 1); - }); - REQUIRE(changes.modified(0, target_cols[0])); - REQUIRE(changes.modified(0, target_cols[1])); - REQUIRE(changes.modified(0, target_cols[2])); - } - - SECTION("setting other objects does not mark as changed") { - auto o = target->get_object(target_keys[0]); - auto changes = observe({o}, [&] { - target->get_object(target_keys[1]).set(target_cols[0], 5); - }); - REQUIRE_FALSE(changes.modified(0, target_cols[0])); - REQUIRE_FALSE(changes.modified(0, target_cols[1])); - REQUIRE_FALSE(changes.modified(0, target_cols[2])); - } - - SECTION("deleting an observed object adds it to invalidated") { - auto o = target->get_object(target_keys[0]); - auto changes = observe({o}, [&] { - o.remove(); - }); - REQUIRE(changes.invalidated(0)); - } - - SECTION("deleting an unobserved object does nothing") { - auto o = target->get_object(target_keys[0]); - auto changes = observe({o}, [&] { - target->get_object(target_keys[1]).remove(); - }); - REQUIRE_FALSE(changes.invalidated(0)); - } - - SECTION("deleting the target of a link marks the link as modified") { - auto o = origin->get_object(origin_keys[0]); - auto changes = observe({o}, [&] { - o.get_linked_object(origin_cols[1]).remove(); - }); - REQUIRE(changes.modified(0, origin_cols[1])); - } - - SECTION("clearing the target table of a link marks the link as modified") { - auto o = origin->get_object(origin_keys[0]); - auto changes = observe({o}, [&] { - target->clear(); - }); - REQUIRE(changes.modified(0, origin_cols[1])); - } - - SECTION("clearing a table invalidates all observers for that table") { - auto r1 = target->get_object(target_keys[0]); - auto r2 = target->get_object(target_keys[5]); - auto r3 = origin->get_object(origin_keys[0]); - auto changes = observe({r1, r2, r3}, [&] { - target->clear(); - }); - REQUIRE(changes.invalidated(0)); - REQUIRE(changes.invalidated(1)); - REQUIRE_FALSE(changes.invalidated(2)); - } - - using Kind = BindingContext::ColumnInfo::Kind; - auto o = origin->get_object(origin_keys[0]); - const auto lv_col = origin_cols[2]; - SECTION("array: add()") { - auto changes = observe({o}, [&] { - lv.add(target_keys[0]); - }); - REQUIRE(changes.array_change(0, lv_col) == (ArrayChange{Kind::Insert, {10}})); - } - - SECTION("array: insert()") { - auto changes = observe({o}, [&] { - lv.insert(4, target_keys[0]); - lv.insert(2, target_keys[0]); - lv.insert(8, target_keys[0]); - }); - REQUIRE(changes.array_change(0, lv_col) == (ArrayChange{Kind::Insert, {2, 5, 8}})); - } - - SECTION("array: remove()") { - auto changes = observe({o}, [&] { - lv.remove(0); - lv.remove(2); - }); - REQUIRE(changes.array_change(0, lv_col) == (ArrayChange{Kind::Remove, {0, 3}})); - } - - SECTION("array: set()") { - auto changes = observe({o}, [&] { - lv.set(0, target_keys[3]); - lv.set(2, target_keys[3]); - }); - REQUIRE(changes.array_change(0, lv_col) == (ArrayChange{Kind::Set, {0, 2}})); - } - - SECTION("array: move()") { - SECTION("swap forward") { - auto changes = observe({o}, [&] { - lv.move(3, 4); - }); - REQUIRE(changes.array_change(0, lv_col) == (ArrayChange{Kind::Set, {3, 4}})); - } - - SECTION("swap backwards") { - auto changes = observe({o}, [&] { - lv.move(4, 3); - }); - REQUIRE(changes.array_change(0, lv_col) == (ArrayChange{Kind::Set, {3, 4}})); - } - - SECTION("move fowards") { - auto changes = observe({o}, [&] { - lv.move(3, 5); - }); - REQUIRE(changes.array_change(0, lv_col) == (ArrayChange{Kind::Set, {3, 4, 5}})); - } - - SECTION("move backwards") { - auto changes = observe({o}, [&] { - lv.move(5, 3); - }); - REQUIRE(changes.array_change(0, lv_col) == (ArrayChange{Kind::Set, {3, 4, 5}})); - } - - SECTION("multiple moves collapsing to nothing") { - auto changes = observe({o}, [&] { - lv.move(3, 4); - lv.move(4, 5); - lv.move(5, 3); - }); - REQUIRE(changes.array_change(0, lv_col) == (ArrayChange{Kind::None, {}})); - } - - SECTION("multiple moves") { - auto changes = observe({o}, [&] { - lv.move(3, 6); - lv.move(6, 4); - }); - REQUIRE(changes.array_change(0, lv_col) == (ArrayChange{Kind::Set, {3, 4}})); - - changes = observe({o}, [&] { - lv.move(3, 6); - lv.move(6, 0); - }); - REQUIRE(changes.array_change(0, lv_col) == (ArrayChange{Kind::Set, {0, 1, 2, 3}})); - - changes = observe({o}, [&] { - lv.move(9, 0); - lv.move(1, 7); - }); - REQUIRE(changes.array_change(0, lv_col) == (ArrayChange{Kind::Set, {0, 7, 8, 9}})); - } - } - - SECTION("array: swap()") { - auto changes = observe({o}, [&] { - lv.swap(5, 3); - }); - REQUIRE(changes.array_change(0, lv_col) == (ArrayChange{Kind::Set, {3, 5}})); - } - - SECTION("array: clear()") { - auto changes = observe({o}, [&] { - lv.clear(); - }); - REQUIRE(changes.array_change(0, lv_col) == (ArrayChange{Kind::Remove, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}})); - } - - SECTION("array: clear() after add()") { - auto changes = observe({o}, [&] { - lv.add(target_keys[0]); - lv.clear(); - }); - REQUIRE(changes.array_change(0, lv_col) == (ArrayChange{Kind::Remove, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}})); - } - - SECTION("array: clear() after set()") { - auto changes = observe({o}, [&] { - lv.set(5, target_keys[3]); - lv.clear(); - }); - REQUIRE(changes.array_change(0, lv_col) == (ArrayChange{Kind::Remove, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}})); - } - - SECTION("array: clear() after remove()") { - auto changes = observe({o}, [&] { - lv.remove(2); - lv.clear(); - }); - REQUIRE(changes.array_change(0, lv_col) == (ArrayChange{Kind::Remove, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}})); - } - - SECTION("array: rollback clear()") { - auto changes = observe_rollback({o}, [&] { - lv.clear(); - }); - REQUIRE(changes.array_change(0, lv_col) == (ArrayChange{Kind::Insert, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}})); - } - - SECTION("array: rollback clear() after add()") { - auto changes = observe_rollback({o}, [&] { - lv.add(target_keys[0]); - lv.clear(); - }); - REQUIRE(changes.array_change(0, lv_col) == (ArrayChange{Kind::Insert, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}})); - } - - SECTION("array: rollback clear() after set()") { - auto changes = observe_rollback({o}, [&] { - lv.set(5, target_keys[3]); - lv.clear(); - }); - REQUIRE(changes.array_change(0, lv_col) == (ArrayChange{Kind::Insert, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}})); - } - - SECTION("array: rollback clear() after remove()") { - auto changes = observe_rollback({o}, [&] { - lv.remove(2); - lv.clear(); - }); - REQUIRE(changes.array_change(0, lv_col) == (ArrayChange{Kind::Insert, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}})); - } - - SECTION("array: rollback add after clear()") { - auto changes = observe_rollback({o}, [&] { - lv.clear(); - lv.add(target_keys[0]); - }); - REQUIRE(changes.array_change(0, lv_col) == (ArrayChange{Kind::SetAll, {}})); - } - - SECTION("array: multiple change kinds") { - auto changes = observe({o}, [&] { - lv.add(target_keys[0]); - lv.remove(0); - }); - REQUIRE(changes.array_change(0, lv_col) == (ArrayChange{Kind::SetAll, {}})); - } - - SECTION("array: modify newly inserted row") { - auto changes = observe({o}, [&] { - lv.add(target_keys[0]); - lv.set(lv.size() - 1, target_keys[1]); - }); - REQUIRE(changes.array_change(0, lv_col) == (ArrayChange{Kind::Insert, {10}})); - } - - SECTION("array: modifying different array does not produce changes") { - auto changes = observe({o}, [&] { - lv2.add(target_keys[0]); - }); - REQUIRE_FALSE(changes.modified(0, target_cols[2])); - } - - SECTION("array: modifying different table does not produce changes") { - auto changes = observe({o}, [&] { - realm->read_group().get_table("class_origin 2")->begin()->get_linklist("array").add(target_keys[0]); - }); - REQUIRE_FALSE(changes.modified(0, target_cols[2])); - } - - SECTION("array: deleting the containing row after making changes discards the changes") { - auto changes = observe({o}, [&] { - lv.insert(4, target_keys[0]); - lv.insert(2, target_keys[0]); - lv.insert(8, target_keys[0]); - o.remove(); - }); - REQUIRE(changes.array_change(0, lv_col) == (ArrayChange{Kind::None, {}})); - } - - // ---------------------------------------------------------------------- - - const auto tr_col = origin_cols[3]; - SECTION("int array: add()") { - auto changes = observe({o}, [&] { - tr.add(0); - }); - REQUIRE(changes.array_change(0, tr_col) == (ArrayChange{Kind::Insert, {10}})); - } - - SECTION("int array: insert()") { - auto changes = observe({o}, [&] { - tr.insert(4, 0); - tr.insert(2, 0); - tr.insert(8, 0); - }); - REQUIRE(changes.array_change(0, tr_col) == (ArrayChange{Kind::Insert, {2, 5, 8}})); - } - - SECTION("int array: remove()") { - auto changes = observe({o}, [&] { - tr.remove(0); - tr.remove(2); - }); - REQUIRE(changes.array_change(0, tr_col) == (ArrayChange{Kind::Remove, {0, 3}})); - } - - SECTION("int array: set()") { - auto changes = observe({o}, [&] { - tr.set(0, 3); - tr.set(2, 3); - }); - REQUIRE(changes.array_change(0, tr_col) == (ArrayChange{Kind::Set, {0, 2}})); - } - - SECTION("int array: move()") { - auto changes = observe({o}, [&] { - tr.move(8, 2); - tr.move(4, 6); - - // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 - // Now: 0, 1, 8, 2, 4, 5, 3, 6, 7, 9 - }); - REQUIRE(changes.array_change(0, tr_col) == (ArrayChange{Kind::Set, {2, 3, 6, 7, 8}})); - } - - SECTION("int array: emulated move()") { - auto changes = observe({o}, [&] { - // list.move(8, 2); - tr.insert(2, 0); - tr.swap(9, 2); - tr.remove(9); - - // list.move(4, 6); - tr.insert(7, 0); - tr.swap(4, 7); - tr.remove(4); - - // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 - // Now: 0, 1, 8, 2, 4, 5, 3, 6, 7, 9 - }); - REQUIRE(changes.array_change(0, tr_col) == (ArrayChange{Kind::Set, {2, 3, 6, 7, 8}})); - } - - SECTION("int array: swap()") { - SECTION("adjacent") { - auto changes = observe({o}, [&] { - tr.swap(5, 4); - }); - REQUIRE(changes.array_change(0, tr_col) == (ArrayChange{Kind::Set, {4, 5}})); - } - SECTION("non-adjacent") { - auto changes = observe({o}, [&] { - tr.swap(5, 3); - }); - REQUIRE(changes.array_change(0, tr_col) == (ArrayChange{Kind::Set, {3, 5}})); - } - } - - SECTION("int array: clear()") { - auto changes = observe({o}, [&] { - tr.clear(); - }); - REQUIRE(changes.array_change(0, tr_col) == (ArrayChange{Kind::Remove, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}})); - } - - SECTION("int array: clear() after add()") { - auto changes = observe({o}, [&] { - tr.add(0); - tr.clear(); - }); - REQUIRE(changes.array_change(0, tr_col) == (ArrayChange{Kind::Remove, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}})); - } - - SECTION("int array: clear() after set()") { - auto changes = observe({o}, [&] { - tr.set(5, 3); - tr.clear(); - }); - REQUIRE(changes.array_change(0, tr_col) == (ArrayChange{Kind::Remove, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}})); - } - - SECTION("int array: clear() after remove()") { - auto changes = observe({o}, [&] { - tr.remove(2); - tr.clear(); - }); - REQUIRE(changes.array_change(0, tr_col) == (ArrayChange{Kind::Remove, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}})); - } - - SECTION("int array: multiple change kinds") { - auto changes = observe({o}, [&] { - tr.add(0); - tr.remove(0); - }); - REQUIRE(changes.array_change(0, tr_col) == (ArrayChange{Kind::SetAll, {}})); - } - - SECTION("int array: modifying different array does not produce changes") { - auto changes = observe({o}, [&] { - tr2.add(0); - }); - REQUIRE_FALSE(changes.modified(0, target_cols[3])); - } - - SECTION("int array: deleting the containing row after making changes discards the changes") { - auto changes = observe({o}, [&] { - tr.insert(4, 0); - tr.insert(2, 0); - tr.insert(8, 0); - o.remove(); - }); - REQUIRE(changes.array_change(0, tr_col) == (ArrayChange{Kind::None, {}})); - } - - SECTION("int array: rollback clear()") { - auto changes = observe_rollback({o}, [&] { - tr.clear(); - }); - REQUIRE(changes.array_change(0, tr_col) == (ArrayChange{Kind::Insert, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}})); - } - - SECTION("int array: rollback clear() after add()") { - auto changes = observe_rollback({o}, [&] { - tr.add(0); - tr.clear(); - }); - REQUIRE(changes.array_change(0, tr_col) == (ArrayChange{Kind::Insert, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}})); - } - - SECTION("int array: rollback clear() after set()") { - auto changes = observe_rollback({o}, [&] { - tr.set(5, 3); - tr.clear(); - }); - REQUIRE(changes.array_change(0, tr_col) == (ArrayChange{Kind::Insert, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}})); - } - - SECTION("int array: rollback clear() after remove()") { - auto changes = observe_rollback({o}, [&] { - tr.remove(2); - tr.clear(); - }); - REQUIRE(changes.array_change(0, tr_col) == (ArrayChange{Kind::Insert, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}})); - } - - SECTION("int array: rollback add() after clear()") { - auto changes = observe_rollback({o}, [&] { - tr.clear(); - tr.add(0); - }); - REQUIRE(changes.array_change(0, tr_col) == (ArrayChange{Kind::SetAll, {}})); - } - } -} - -TEST_CASE("DeepChangeChecker") { - InMemoryTestFile config; - config.automatic_change_notifications = false; - auto r = Realm::get_shared_realm(config); - r->update_schema({ - {"table", { - {"int", PropertyType::Int}, - {"link1", PropertyType::Object|PropertyType::Nullable, "table"}, - {"link2", PropertyType::Object|PropertyType::Nullable, "table"}, - {"array", PropertyType::Array|PropertyType::Object, "table"} - }}, - }); - auto table = r->read_group().get_table("class_table"); - - std::vector<Obj> objects; - r->begin_transaction(); - for (int i = 0; i < 10; ++i) - objects.push_back(table->create_object().set_all(i)); - r->commit_transaction(); - - auto track_changes = [&](auto&& f) { - auto history = make_in_realm_history(config.path); - auto db = DB::create(*history, config.options()); - auto rt = db->start_read(); - - r->begin_transaction(); - f(); - r->commit_transaction(); - - _impl::TransactionChangeInfo info{}; - for (auto key : rt->get_table_keys()) - info.tables[key.value]; - _impl::transaction::advance(*rt, info); - return info; - }; - - std::vector<_impl::DeepChangeChecker::RelatedTable> tables; - _impl::DeepChangeChecker::find_related_tables(tables, *table); - - auto cols = table->get_column_keys(); - SECTION("direct changes are tracked") { - auto info = track_changes([&] { - table->get_object(9).set(cols[0], 10); - }); - - _impl::DeepChangeChecker checker(info, *table, tables); - REQUIRE_FALSE(checker(8)); - REQUIRE(checker(9)); - } - - SECTION("changes over links are tracked") { - bool did_run_section = false; - SECTION("first link set") { - did_run_section = true; - r->begin_transaction(); - objects[0].set(cols[1], objects[1].get_key()); - objects[1].set(cols[1], objects[2].get_key()); - objects[2].set(cols[1], objects[4].get_key()); - r->commit_transaction(); - } - SECTION("second link set") { - did_run_section = true; - r->begin_transaction(); - objects[0].set(cols[2], objects[1].get_key()); - objects[1].set(cols[2], objects[2].get_key()); - objects[2].set(cols[2], objects[4].get_key()); - r->commit_transaction(); - } - SECTION("both set") { - did_run_section = true; - r->begin_transaction(); - objects[0].set(cols[1], objects[1].get_key()); - objects[1].set(cols[1], objects[2].get_key()); - objects[2].set(cols[1], objects[4].get_key()); - - objects[0].set(cols[2], objects[1].get_key()); - objects[1].set(cols[2], objects[2].get_key()); - objects[2].set(cols[2], objects[4].get_key()); - r->commit_transaction(); - } - SECTION("circular link") { - did_run_section = true; - r->begin_transaction(); - objects[0].set(cols[1], objects[0].get_key()); - objects[1].set(cols[1], objects[1].get_key()); - objects[2].set(cols[1], objects[2].get_key()); - objects[3].set(cols[1], objects[3].get_key()); - objects[4].set(cols[1], objects[4].get_key()); - - objects[0].set(cols[2], objects[1].get_key()); - objects[1].set(cols[2], objects[2].get_key()); - objects[2].set(cols[2], objects[4].get_key()); - r->commit_transaction(); - } - - catch2_ensure_section_run_workaround(did_run_section, "changes over links are tracked", [&]() { - auto info = track_changes([&] { - objects[4].set(cols[0], 10); - }); - - // link chain should cascade to all but #3 being marked as modified - REQUIRE(_impl::DeepChangeChecker(info, *table, tables)(0)); - REQUIRE(_impl::DeepChangeChecker(info, *table, tables)(1)); - REQUIRE(_impl::DeepChangeChecker(info, *table, tables)(2)); - REQUIRE_FALSE(_impl::DeepChangeChecker(info, *table, tables)(3)); - - }); - } - - SECTION("changes over linklists are tracked") { - r->begin_transaction(); - for (int i = 0; i < 3; ++i) { - objects[i].get_linklist(cols[3]).add(objects[i].get_key()); - objects[i].get_linklist(cols[3]).add(objects[i].get_key()); - objects[i].get_linklist(cols[3]).add(objects[i + 1 + (i == 2)].get_key()); - } - r->commit_transaction(); - - auto info = track_changes([&] { - objects[4].set(cols[0], 10); - }); - - REQUIRE(_impl::DeepChangeChecker(info, *table, tables)(0)); - REQUIRE_FALSE(_impl::DeepChangeChecker(info, *table, tables)(3)); - } - - SECTION("cycles over links do not loop forever") { - r->begin_transaction(); - objects[0].set(cols[1], objects[0].get_key()); - r->commit_transaction(); - - auto info = track_changes([&] { - objects[9].set(cols[0], 10); - }); - REQUIRE_FALSE(_impl::DeepChangeChecker(info, *table, tables)(0)); - } - - SECTION("cycles over linklists do not loop forever") { - r->begin_transaction(); - objects[0].get_linklist(cols[3]).add(objects[0].get_key()); - r->commit_transaction(); - - auto info = track_changes([&] { - objects[9].set(cols[0], 10); - }); - REQUIRE_FALSE(_impl::DeepChangeChecker(info, *table, tables)(0)); - } - - SECTION("link chains are tracked up to 4 levels deep") { - r->begin_transaction(); - for (int i = 0; i < 10; ++i) - objects.push_back(table->create_object()); - for (int i = 0; i < 19; ++i) - objects[i].set(cols[1], objects[i + 1].get_key()); - r->commit_transaction(); - - auto info = track_changes([&] { - objects[19].set(cols[0], -1); - }); - - _impl::DeepChangeChecker checker(info, *table, tables); - CHECK(checker(19)); - CHECK(checker(18)); - CHECK(checker(16)); - CHECK_FALSE(checker(15)); - - // Check in other orders to make sure that the caching doesn't effect - // the results - _impl::DeepChangeChecker checker2(info, *table, tables); - CHECK_FALSE(checker2(15)); - CHECK(checker2(16)); - CHECK(checker2(18)); - CHECK(checker2(19)); - - _impl::DeepChangeChecker checker3(info, *table, tables); - CHECK(checker3(16)); - CHECK_FALSE(checker3(15)); - CHECK(checker3(18)); - CHECK(checker3(19)); - } - - SECTION("changes made in the 3rd elements in the link list") { - r->begin_transaction(); - objects[0].get_linklist(cols[3]).add(objects[1].get_key()); - objects[0].get_linklist(cols[3]).add(objects[2].get_key()); - objects[0].get_linklist(cols[3]).add(objects[3].get_key()); - objects[1].set(cols[1], objects[0].get_key()); - objects[2].set(cols[1], objects[0].get_key()); - objects[3].set(cols[1], objects[0].get_key()); - r->commit_transaction(); - - auto info = track_changes([&] { - objects[3].set(cols[0], 42); - }); - _impl::DeepChangeChecker checker(info, *table, tables); - REQUIRE(checker(1)); - REQUIRE(checker(2)); - REQUIRE(checker(3)); - } - - SECTION("changes made to lists mark the containing row as modified") { - auto info = track_changes([&] { - objects[0].get_linklist(cols[3]).add(objects[1].get_key()); - }); - _impl::DeepChangeChecker checker(info, *table, tables); - REQUIRE(checker(0)); - } -} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/util/event_loop.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/util/event_loop.cpp deleted file mode 100644 index 9bb8bc251..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/util/event_loop.cpp +++ /dev/null @@ -1,215 +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/event_loop.hpp> - -#include <realm/util/features.h> - -#include <mutex> -#include <stdexcept> -#include <vector> - -#if (REALM_HAVE_UV && !REALM_PLATFORM_APPLE) || REALM_PLATFORM_NODE -#define REALM_USE_UV 1 -#else -#define REALM_USE_UV 0 -#endif - -#if REALM_USE_UV -#include <uv.h> -#elif REALM_PLATFORM_APPLE -#include <realm/util/cf_ptr.hpp> -#include <CoreFoundation/CoreFoundation.h> -#endif - -using namespace realm::util; - -struct EventLoop::Impl { - // Returns the main event loop. - static std::unique_ptr<Impl> main(); - - // Run the event loop until the given return predicate returns true - void run_until(std::function<bool()> predicate); - - // Schedule execution of the given function on the event loop. - void perform(std::function<void()>); - - ~Impl(); - -private: -#if REALM_USE_UV - Impl(uv_loop_t* loop); - - std::vector<std::function<void()>> m_pending_work; - std::mutex m_mutex; - uv_loop_t* m_loop; - uv_async_t m_perform_work; -#elif REALM_PLATFORM_APPLE - Impl(util::CFPtr<CFRunLoopRef> loop) : m_loop(std::move(loop)) { } - - util::CFPtr<CFRunLoopRef> m_loop; -#endif -}; - -EventLoop& EventLoop::main() -{ - static EventLoop main(Impl::main()); - return main; -} - -EventLoop::EventLoop(std::unique_ptr<Impl> impl) : m_impl(std::move(impl)) -{ -} - -EventLoop::~EventLoop() = default; - -void EventLoop::run_until(std::function<bool()> predicate) -{ - return m_impl->run_until(std::move(predicate)); -} - -void EventLoop::perform(std::function<void()> function) -{ - return m_impl->perform(std::move(function)); -} - -#if REALM_USE_UV - -bool EventLoop::has_implementation() { return true; } - -std::unique_ptr<EventLoop::Impl> EventLoop::Impl::main() -{ - return std::unique_ptr<Impl>(new Impl(uv_default_loop())); -} - -EventLoop::Impl::Impl(uv_loop_t* loop) - : m_loop(loop) -{ - m_perform_work.data = this; - uv_async_init(uv_default_loop(), &m_perform_work, [](uv_async_t* handle) { - std::vector<std::function<void()>> pending_work; - { - Impl& self = *static_cast<Impl*>(handle->data); - std::lock_guard<std::mutex> lock(self.m_mutex); - std::swap(pending_work, self.m_pending_work); - } - - for (auto& f : pending_work) - f(); - }); -} - -EventLoop::Impl::~Impl() -{ - uv_close((uv_handle_t*)&m_perform_work, [](uv_handle_t*){}); - uv_loop_close(m_loop); -} - -struct IdleHandler { - uv_idle_t* idle = new uv_idle_t; - - IdleHandler(uv_loop_t* loop) - { - uv_idle_init(loop, idle); - } - ~IdleHandler() - { - uv_close(reinterpret_cast<uv_handle_t*>(idle), [](uv_handle_t* handle) { - delete reinterpret_cast<uv_idle_t*>(handle); - }); - } -}; - -void EventLoop::Impl::run_until(std::function<bool()> predicate) -{ - if (predicate()) - return; - - IdleHandler observer(m_loop); - observer.idle->data = &predicate; - - uv_idle_start(observer.idle, [](uv_idle_t* handle) { - auto& predicate = *static_cast<std::function<bool()>*>(handle->data); - if (predicate()) { - uv_stop(handle->loop); - } - }); - - uv_run(m_loop, UV_RUN_DEFAULT); - uv_idle_stop(observer.idle); -} - -void EventLoop::Impl::perform(std::function<void()> f) -{ - { - std::lock_guard<std::mutex> lock(m_mutex); - m_pending_work.push_back(std::move(f)); - } - uv_async_send(&m_perform_work); -} - -#elif REALM_PLATFORM_APPLE - -bool EventLoop::has_implementation() { return true; } - -std::unique_ptr<EventLoop::Impl> EventLoop::Impl::main() -{ - return std::unique_ptr<Impl>(new Impl(retainCF(CFRunLoopGetMain()))); -} - -EventLoop::Impl::~Impl() = default; - -void EventLoop::Impl::run_until(std::function<bool()> predicate) -{ - REALM_ASSERT(m_loop.get() == CFRunLoopGetCurrent()); - - auto callback = [](CFRunLoopObserverRef, CFRunLoopActivity, void* info) { - if ((*static_cast<std::function<bool()>*>(info))()) { - CFRunLoopStop(CFRunLoopGetCurrent()); - } - }; - CFRunLoopObserverContext ctx{}; - ctx.info = &predicate; - auto observer = adoptCF(CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, - true, 0, callback, &ctx)); - auto timer = adoptCF(CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent(), 0.0005, 0, 0, - ^(CFRunLoopTimerRef){ - // Do nothing. The timer firing is sufficient to cause our runloop observer to run. - })); - CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer.get(), kCFRunLoopCommonModes); - CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer.get(), kCFRunLoopCommonModes); - CFRunLoopRun(); - CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), timer.get(), kCFRunLoopCommonModes); - CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer.get(), kCFRunLoopCommonModes); -} - -void EventLoop::Impl::perform(std::function<void()> f) -{ - CFRunLoopPerformBlock(m_loop.get(), kCFRunLoopDefaultMode, ^{ f(); }); - CFRunLoopWakeUp(m_loop.get()); -} - -#else - -bool EventLoop::has_implementation() { return false; } -std::unique_ptr<EventLoop::Impl> EventLoop::Impl::main() { return nullptr; } -EventLoop::Impl::~Impl() = default; -void EventLoop::Impl::run_until(std::function<bool()>) { printf("WARNING: there is no event loop implementation and nothing is happening.\n"); } -void EventLoop::Impl::perform(std::function<void()>) { printf("WARNING: there is no event loop implementation and nothing is happening.\n"); } - -#endif diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/util/event_loop.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/util/event_loop.hpp deleted file mode 100644 index 0b0f6a58c..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/util/event_loop.hpp +++ /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. - * - **************************************************************************/ - -#ifndef REALM_OS_TESTS_UTIL_EVENT_LOOP_HPP -#define REALM_OS_TESTS_UTIL_EVENT_LOOP_HPP - -#include <functional> -#include <memory> - -namespace realm { -namespace util { - -struct EventLoop { - // Returns if the current platform has an event loop implementation - static bool has_implementation(); - - // Returns the main event loop. - static EventLoop& main(); - - // Run the event loop until the given return predicate returns true - void run_until(std::function<bool()> predicate); - - // Schedule execution of the given function on the event loop. - void perform(std::function<void()>); - - EventLoop(EventLoop&&) = default; - EventLoop& operator=(EventLoop&&) = default; - ~EventLoop(); - -private: - struct Impl; - - EventLoop(std::unique_ptr<Impl>); - - std::unique_ptr<Impl> m_impl; -}; - -} // namespace util -} // namespace realm - -#endif // REALM_OS_TESTS_UTIL_EVENT_LOOP_HPP diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/util/index_helpers.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/util/index_helpers.hpp deleted file mode 100644 index 3e4e208e6..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/util/index_helpers.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. -// -//////////////////////////////////////////////////////////////////////////// - -#define REQUIRE_INDICES(index_set, ...) do { \ - index_set.verify(); \ - std::initializer_list<size_t> expected = {__VA_ARGS__}; \ - auto actual = index_set.as_indexes(); \ - INFO("Checking " #index_set); \ - REQUIRE(expected.size() == static_cast<size_t>(std::distance(actual.begin(), actual.end()))); \ - auto begin = actual.begin(); \ - for (auto index : expected) { \ - REQUIRE(*begin++ == index); \ - } \ -} while (0) - -#define REQUIRE_COLUMN_INDICES(columns, col, ...) do { \ - auto it = (columns).find(col); \ - REQUIRE(it != (columns).end()); \ - REQUIRE_INDICES(it->second, __VA_ARGS__); \ -} while (0) - -#define REQUIRE_MOVES(c, ...) do { \ - auto actual = (c); \ - std::initializer_list<CollectionChangeSet::Move> 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/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/util/test_file.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/util/test_file.cpp deleted file mode 100644 index e09cabf51..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/util/test_file.cpp +++ /dev/null @@ -1,329 +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/test_file.hpp" - -#include "test_utils.hpp" - -#include "impl/realm_coordinator.hpp" - -#if REALM_ENABLE_SYNC -#include "sync/sync_config.hpp" -#include "sync/sync_manager.hpp" -#include "sync/sync_session.hpp" -#include "schema.hpp" -#endif - -#include <realm/db.hpp> -#include <realm/disable_sync_to_disk.hpp> -#include <realm/history.hpp> -#include <realm/string_data.hpp> -#include <realm/util/base64.hpp> - -#include <cstdlib> - -#ifdef _WIN32 -#include <io.h> -#include <fcntl.h> - -inline static int mkstemp(char* _template) { return _open(_mktemp(_template), _O_CREAT | _O_TEMPORARY, _S_IREAD | _S_IWRITE); } -#else -#include <unistd.h> -#endif - -#if REALM_HAVE_CLANG_FEATURE(thread_sanitizer) -#include <condition_variable> -#include <functional> -#include <thread> -#include <map> -#endif - -using namespace realm; - -TestFile::TestFile() -{ - static std::string tmpdir = [] { - disable_sync_to_disk(); - - const char* dir = getenv("TMPDIR"); - if (dir && *dir) - return dir; -#if REALM_ANDROID - return "/data/local/tmp"; -#else - return "/tmp"; -#endif - }(); - path = tmpdir + "/realm.XXXXXX"; - int fd = mkstemp(&path[0]); - if (fd < 0) { - int err = errno; - throw std::system_error(err, std::system_category()); - } - close(fd); - unlink(path.c_str()); - - schema_version = 0; -} - -TestFile::~TestFile() -{ - if (!m_persist) - unlink(path.c_str()); -} - -DBOptions TestFile::options() const -{ - DBOptions options; - options.durability = in_memory - ? DBOptions::Durability::MemOnly - : DBOptions::Durability::Full; - return options; -} - -InMemoryTestFile::InMemoryTestFile() -{ - in_memory = true; -} - -#if REALM_ENABLE_SYNC -SyncTestFile::SyncTestFile(SyncServer& server, std::string name, bool is_partial, std::string user_name) -{ - if (name.empty()) - name = path.substr(path.rfind('/') + 1); - auto url = server.url_for_realm(name); - - sync_config = std::make_shared<SyncConfig>(SyncManager::shared().get_user({user_name, url}, "not_a_real_token"), url); - sync_config->user->set_is_admin(true); - sync_config->stop_policy = SyncSessionStopPolicy::Immediately; - sync_config->bind_session_handler = [=](auto&, auto& config, auto session) { - std::string token, encoded; - // FIXME: Tokens without a path are currently implicitly considered - // admin tokens by the sync service, so until that changes we need to - // add a path for non-admin users - if (config.user->is_admin()) - token = util::format("{\"identity\": \"%1\", \"access\": [\"download\", \"upload\"]}", user_name); - else { - std::string suffix; - if (config.is_partial) - suffix = util::format("/__partial/%1/%2", config.user->identity(), SyncConfig::partial_sync_identifier(*config.user)); - token = util::format("{\"identity\": \"%1\", \"path\": \"/%2%3\", \"access\": [\"download\", \"upload\"]}", - user_name, name, suffix); - } - encoded.resize(base64_encoded_size(token.size())); - base64_encode(token.c_str(), token.size(), &encoded[0], encoded.size()); - session->refresh_access_token(encoded, config.realm_url()); - }; - sync_config->error_handler = [](auto, auto) { abort(); }; - sync_config->is_partial = is_partial; - schema_mode = SchemaMode::Additive; -} - -SyncServer::SyncServer(StartImmediately start_immediately, std::string local_dir) -: m_local_root_dir(local_dir.empty() ? util::make_temp_dir() : local_dir) -, m_server(m_local_root_dir, util::none, ([&] { - using namespace std::literals::chrono_literals; - - sync::Server::Config config; -#if TEST_ENABLE_SYNC_LOGGING - auto logger = new util::StderrLogger; - logger->set_level_threshold(util::Logger::Level::all); - config.logger = logger; -#else - config.logger = new TestLogger; -#endif - m_logger.reset(config.logger); - config.history_compaction_clock = this; - config.disable_history_compaction = false; - config.history_ttl = 1s; - config.history_compaction_interval = 1s; - config.state_realm_dir = util::make_temp_dir(); - config.listen_address = "127.0.0.1"; - - return config; -})()) -{ -#if TEST_ENABLE_SYNC_LOGGING - SyncManager::shared().set_log_level(util::Logger::Level::all); -#else - SyncManager::shared().set_log_level(util::Logger::Level::off); -#endif - - m_server.start(); - m_url = util::format("realm://127.0.0.1:%1", m_server.listen_endpoint().port()); - if (start_immediately) - start(); -} - -SyncServer::~SyncServer() -{ - stop(); - SyncManager::shared().reset_for_testing(); -} - -void SyncServer::start() -{ - REALM_ASSERT(!m_thread.joinable()); - m_thread = std::thread([this]{ m_server.run(); }); -} - -void SyncServer::stop() -{ - m_server.stop(); - if (m_thread.joinable()) - m_thread.join(); -} - -std::string SyncServer::url_for_realm(StringData realm_name) const -{ - return util::format("%1/%2", m_url, realm_name); -} - -static void wait_for_session(Realm& realm, void (SyncSession::*fn)(std::function<void(std::error_code)>)) -{ - std::condition_variable cv; - std::mutex wait_mutex; - bool wait_flag(false); - auto& session = *SyncManager::shared().get_session(realm.config().path, *realm.config().sync_config); - (session.*fn)([&](auto) { - std::unique_lock<std::mutex> lock(wait_mutex); - wait_flag = true; - cv.notify_one(); - }); - std::unique_lock<std::mutex> lock(wait_mutex); - cv.wait(lock, [&]() { return wait_flag == true; }); -} - -void wait_for_upload(Realm& realm) -{ - wait_for_session(realm, &SyncSession::wait_for_upload_completion); -} - -void wait_for_download(Realm& realm) -{ - wait_for_session(realm, &SyncSession::wait_for_download_completion); -} - -TestSyncManager::TestSyncManager(std::string const& base_path, SyncManager::MetadataMode mode) -{ - configure(base_path, mode); -} - -TestSyncManager::~TestSyncManager() -{ - SyncManager::shared().reset_for_testing(); -} - -void TestSyncManager::configure(std::string const& base_path, SyncManager::MetadataMode mode) -{ - SyncClientConfig config; - config.base_file_path = base_path.empty() ? tmp_dir() : base_path; - config.metadata_mode = mode; -#if TEST_ENABLE_SYNC_LOGGING - config.log_level = util::Logger::Level::all; -#else - config.log_level = util::Logger::Level::off; -#endif - SyncManager::shared().configure(config); -} - -#endif // REALM_ENABLE_SYNC - -#if REALM_HAVE_CLANG_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<_impl::RealmCoordinator *>(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<_impl::RealmCoordinator>& c) - { - auto& it = m_published_coordinators[c.get()]; - if (it.lock()) { - m_signal.store(reinterpret_cast<uintptr_t>(c.get()), std::memory_order_relaxed); - } else { - // Synchronize on the first handover of a given coordinator. - it = c; - m_signal = reinterpret_cast<uintptr_t>(c.get()) | 1; - } - - while (m_signal.load(std::memory_order_relaxed) != 1) ; - } - -private: - std::atomic<uintptr_t> m_signal{0}; - std::thread m_thread; - std::map<_impl::RealmCoordinator*, std::weak_ptr<_impl::RealmCoordinator>> m_published_coordinators; -} s_worker; - -void on_change_but_no_notify(Realm& realm) -{ - s_worker.on_change(_impl::RealmCoordinator::get_existing_coordinator(realm.config().path)); -} - -void advance_and_notify(Realm& realm) -{ - on_change_but_no_notify(realm); - realm.notify(); -} - -#else // REALM_HAVE_CLANG_FEATURE(thread_sanitizer) - -void on_change_but_no_notify(Realm& realm) -{ - _impl::RealmCoordinator::get_coordinator(realm.config().path)->on_change(); -} - -void advance_and_notify(Realm& realm) -{ - on_change_but_no_notify(realm); - realm.notify(); -} -#endif diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/util/test_file.hpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/util/test_file.hpp deleted file mode 100644 index 8aa6fa3b7..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/util/test_file.hpp +++ /dev/null @@ -1,155 +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_TEST_UTIL_TEST_FILE_HPP -#define REALM_TEST_UTIL_TEST_FILE_HPP - -#include "shared_realm.hpp" -#include "util/tagged_bool.hpp" - -#include <realm/util/logger.hpp> -#include <realm/util/optional.hpp> - -#include <thread> - -#if REALM_ENABLE_SYNC -#include "sync/sync_config.hpp" - -#include <realm/sync/client.hpp> -#include <realm/sync/server.hpp> - -// {"identity":"test", "access": ["download", "upload"]} -static const std::string s_test_token = "eyJpZGVudGl0eSI6InRlc3QiLCAiYWNjZXNzIjogWyJkb3dubG9hZCIsICJ1cGxvYWQiXX0="; -#endif // REALM_ENABLE_SYNC - -namespace realm { -class Schema; -enum class SyncSessionStopPolicy; -struct DBOptions; -struct SyncConfig; -} - -class JoiningThread { -public: - template<typename... Args> - JoiningThread(Args&&... args) : m_thread(std::forward<Args>(args)...) { } - ~JoiningThread() { if (m_thread.joinable()) m_thread.join(); } - void join() { m_thread.join(); } - -private: - std::thread m_thread; -}; - - -struct TestFile : realm::Realm::Config { - TestFile(); - ~TestFile(); - - // The file should outlive the object, ie. should not be deleted in destructor - void persist() - { - m_persist = true; - } - - realm::DBOptions options() const; - -private: - bool m_persist = false; -}; - -struct InMemoryTestFile : TestFile { - InMemoryTestFile(); -}; - -void advance_and_notify(realm::Realm& realm); -void on_change_but_no_notify(realm::Realm& realm); - -#if REALM_ENABLE_SYNC - -#define TEST_ENABLE_SYNC_LOGGING 0 // change to 1 to enable logging - -struct TestLogger : realm::util::Logger::LevelThreshold, realm::util::Logger { - void do_log(realm::util::Logger::Level, std::string) override {} - Level get() const noexcept override { return Level::off; } - TestLogger() : Logger::LevelThreshold(), Logger(static_cast<Logger::LevelThreshold&>(*this)) { } -}; - -using StartImmediately = realm::util::TaggedBool<class StartImmediatelyTag>; - -class SyncServer : private realm::sync::Clock { -public: - SyncServer(StartImmediately start_immediately=true, std::string local_dir = ""); - ~SyncServer(); - - void start(); - void stop(); - - std::string url_for_realm(realm::StringData realm_name) const; - std::string base_url() const { return m_url; } - std::string local_root_dir() const { return m_local_root_dir; } - - template <class R, class P> - void advance_clock(std::chrono::duration<R, P> duration = std::chrono::seconds(1)) noexcept - { - m_now += std::chrono::duration_cast<time_point::duration>(duration).count(); - } - -private: - std::string m_local_root_dir; - std::unique_ptr<realm::util::Logger> m_logger; - realm::sync::Server m_server; - std::thread m_thread; - std::string m_url; - std::atomic<time_point::rep> m_now{0}; - - time_point now() const noexcept override - { - return time_point{time_point::duration{m_now}}; - } -}; - -struct SyncTestFile : TestFile { - template<typename BindHandler, typename ErrorHandler> - SyncTestFile(const realm::SyncConfig& sync_config, - realm::SyncSessionStopPolicy stop_policy, - BindHandler&& bind_handler, - ErrorHandler&& error_handler) - { - this->sync_config = std::make_shared<realm::SyncConfig>(sync_config); - this->sync_config->stop_policy = stop_policy; - this->sync_config->bind_session_handler = std::forward<BindHandler>(bind_handler); - this->sync_config->error_handler = std::forward<ErrorHandler>(error_handler); - schema_mode = realm::SchemaMode::Additive; - } - - SyncTestFile(SyncServer& server, std::string name="", bool is_partial=false, - std::string user_name="test"); -}; - -struct TestSyncManager { - TestSyncManager(std::string const& base_path="", realm::SyncManager::MetadataMode = realm::SyncManager::MetadataMode::NoMetadata); - ~TestSyncManager(); - static void configure(std::string const& base_path, realm::SyncManager::MetadataMode); -}; - -void wait_for_upload(realm::Realm& realm); -void wait_for_download(realm::Realm& realm); - -#endif // REALM_ENABLE_SYNC - -#endif diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/uuid.cpp b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/uuid.cpp deleted file mode 100644 index 97e21f0dd..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/uuid.cpp +++ /dev/null @@ -1,50 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 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 "catch2/catch.hpp" - -#include "util/uuid.hpp" - -#include <algorithm> -#include <cctype> - -using namespace realm; - -TEST_CASE("uuid") { - auto isxdigit = [](char c) { return std::isxdigit(c); }; - - auto uuid = util::uuid_string(); - INFO("uuid: " << uuid); - - CHECK(uuid.size() == 36); - - // Version 4. - CHECK(uuid[14] == '4'); - // Variant 1 (IETF). - CHECK((uuid[19] == '8' || uuid[19] == '9' || uuid[19] == 'a' || uuid[19] == 'b')); - - CHECK(std::all_of(&uuid[0], &uuid[8], isxdigit)); - CHECK(uuid[8] == '-'); - CHECK(std::all_of(&uuid[9], &uuid[13], isxdigit)); - CHECK(uuid[13] == '-'); - CHECK(std::all_of(&uuid[14], &uuid[18], isxdigit)); - CHECK(uuid[18] == '-'); - CHECK(std::all_of(&uuid[19], &uuid[23], isxdigit)); - CHECK(uuid[23] == '-'); - CHECK(std::all_of(&uuid[24], &uuid[36], isxdigit)); -} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/workflow/build.sh b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/workflow/build.sh deleted file mode 100755 index 23675ceda..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/workflow/build.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/sh - -# This script is used by CI to build for a specific flavor. It can be used -# locally: `./workspace/build.sh [linux|android] [sync]` -# -# For Android builds, you must set the ANDROID_NDK_PATH environment variable -# to point to your Android NDK installation. - -flavor=${1:-linux} -sync=${2} -extra_flags=${3} - -nprocs=4 -if [ "$(uname)" = "Linux" ]; then - nprocs=$(grep -c ^processor /proc/cpuinfo) -fi - -: ${OPENSSL_ROOT_DIR:=/usr/local} - -set -e - -rm -rf ci.build -mkdir -p ci.build -cd ci.build - -cmake_flags="-DCMAKE_CXX_FLAGS=-Werror" -if [ "${flavor}" = "android" ]; then - [ -z $ANDROID_NDK_PATH ] && (echo "ANDROID_NDK_PATH is not set!"; exit 1) - cmake_flags="-DREALM_PLATFORM=Android -DANDROID_NDK=${ANDROID_NDK_PATH}" -fi - -if [ "${sync}" = "sync" ]; then - cmake_flags="${cmake_flags} -DREALM_ENABLE_SYNC=1 -DREALM_ENABLE_SERVER=1 -DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR}" -fi - -cmake ${cmake_flags} ${extra_flags} .. -make VERBOSE=1 -j${nprocs} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/workflow/docker_build_wrapper.sh b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/workflow/docker_build_wrapper.sh deleted file mode 100755 index 8cf9c9bb0..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/workflow/docker_build_wrapper.sh +++ /dev/null @@ -1,100 +0,0 @@ -#!/bin/bash - -set -e - -script_path="$(pushd "$(dirname "$0")" >/dev/null; pwd)" -src_path="$(pushd "${script_path}/.." >/dev/null; pwd)" - -die() { echo "$@" 1>&2 ; exit 1; } -info() { echo "===> $*"; } - -docker_build() { - declare name="$1"; shift - declare path="$1"; shift - declare args="$*" - - if [ "${DOCKER_REGISTRY}" != "" ]; then - remote_name="${DOCKER_REGISTRY}/${name}" - fi - - info "Building ${name} image..." - if [ "${DOCKER_REGISTRY}" != "" ]; then - docker_pull "${remote_name}" && docker tag "${remote_name}" "${name}" || true - fi - - old_id=$(docker images -q "${name}") - info "Old ${name} image id: ${old_id}" - - if [ "${DOCKERFILE}" != "" ]; then - docker build ${args} -t "${name}" -f "${DOCKERFILE}" "${path}" || \ - die "Building ${name} image failed" - else - docker build ${args} -t "${name}" "${path}" || \ - die "Building ${name} image failed" - fi - - new_id=$(docker images -q "${name}") - info "New ${name} image id: ${new_id}" - - if [ "${DEBUG}" ] && [ "${new_id}" != "${old_id}" ]; then - info "History for old id $old_id:" - if [ "${old_id}" != "" ]; then - docker history "$old_id" - fi - - info "History for new id $new_id:" - docker history "$new_id" - fi - - if [ "${DOCKER_PUSH:-0}" != "0" ] && [ "${DOCKER_REGISTRY}" != "" ]; then - docker tag "${name}" "${remote_name}" - docker_push "${remote_name}" - fi -} - -# Due to https://github.com/docker/docker/issues/20316, we use -# https://github.com/tonistiigi/buildcache to generate a cache of the layer -# metadata for later builds. -my_buildcache() { - if [ "$DOCKER_REGISTRY" != "" ]; then - - docker_path="/var/lib/docker" - - # Stupid hack for our AWS nodes, which have docker data on another volume - if [ -d "/mnt/docker" ]; then - docker_path="/mnt/docker" - fi - - docker pull "${DOCKER_REGISTRY}/ci/buildcache" >/dev/null && \ - docker run --rm \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -v ${docker_path}:/var/lib/docker \ - "${DOCKER_REGISTRY}/ci/buildcache" "$@" - fi -} - -docker_pull() { - info "Attempting to pull '$1' image from registry..." - ( - tmpdir=$(mktemp -d) - docker pull "$1" - ( - # In addition, pull the cached metadata and load it (buildcache) - cd "${tmpdir}" && docker pull "$1-cache" && docker save "$1-cache" | \ - tar -xf - && docker load -i ./*/layer.tar - ) - rm -rf "${tmpdir}" - ) || true -} - -docker_push() { - info "Pushing '$1' image to registry..." - docker push "$1" - # Create a cache of the layer metdata we need and push it as an image - #docker rmi $1-cache 2>/dev/null || true - my_buildcache save -g /var/lib/docker "$1" | gunzip -c | \ - docker import - "$1-cache" && \ - docker push "$1-cache" -} - -docker_build $@ diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/workflow/local_docker_build.sh b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/workflow/local_docker_build.sh deleted file mode 100755 index bf4b86610..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/workflow/local_docker_build.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh -# This script can be used to locally check and debug -# the linux build process outside of CI. -# This should be run from the root directory: `./workflow/local_docker_build.sh` - -set -e - -flavor=${1:-linux} - -docker build -t ci/realm-object-server:build . - -docker run -it --rm \ - -u $(id -u) \ - -v "${HOME}:${HOME}" \ - -e HOME="${HOME}" \ - -v /etc/passwd:/etc/passwd:ro \ - -v $(pwd):/source \ - -w /source \ - ci/realm-object-server:build \ - ./workflow/test_coverage.sh ${flavor} diff --git a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/workflow/test_coverage.sh b/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/workflow/test_coverage.sh deleted file mode 100755 index 731053ba9..000000000 --- a/Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/workflow/test_coverage.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/sh - -# This script is used by CI to test coverage for a specific flavor. It can be -# used locally: `./workspace/test_coverage.sh [sync] [dependency-suffix]` - -sync=${1} -deps_suffix="${2}" - -: ${OPENSSL_ROOT_DIR:=/usr/local} - -set -e - -if [ "${TMPDIR}" = "" ]; then - TMPDIR="/tmp" -fi - -echo "TMPDIR: ${TMPDIR}" -find $TMPDIR -name 'realm*' -exec rm -rf "{}" \+ || true -rm -rf coverage.build -mkdir -p coverage.build -cd coverage.build - -cmake_flags="-DCMAKE_CXX_FLAGS=-Werror" -if [ "${sync}" = "sync" ]; then - cmake_flags="${cmake_flags} -DREALM_ENABLE_SYNC=1 -DREALM_ENABLE_SERVER=1 -DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR}" -fi - -cmake ${cmake_flags} -G Ninja -DCMAKE_BUILD_TYPE=Coverage -DDEPENDENCIES_FILE="dependencies${deps_suffix}.list" .. -ninja -v generate-coverage-cobertura - -find $TMPDIR -name 'realm*' -exec rm -rf "{}" \+ || true |