marinofaggiana 4 年之前
父节点
当前提交
49621ce55f
共有 100 个文件被更改,包括 6303 次插入5877 次删除
  1. 227 0
      Carthage/Checkouts/realm-cocoa/.jenkins.yml
  2. 374 0
      Carthage/Checkouts/realm-cocoa/CHANGELOG.md
  3. 2 1
      Carthage/Checkouts/realm-cocoa/Configuration/Base.xcconfig
  4. 1 1
      Carthage/Checkouts/realm-cocoa/Configuration/Realm/Realm.xcconfig
  5. 26 18
      Carthage/Checkouts/realm-cocoa/Jenkinsfile.releasability
  6. 2 1
      Carthage/Checkouts/realm-cocoa/Realm.podspec
  7. 15 48
      Carthage/Checkouts/realm-cocoa/Realm/ObjectServerTests/RLMObjectServerTests.mm
  8. 0 35
      Carthage/Checkouts/realm-cocoa/Realm/ObjectServerTests/SwiftObjectServerTests.swift
  9. 4 1
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/CMake/CompilerFlags.cmake
  10. 71 87
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/CMake/RealmCore.cmake
  11. 1 1
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/CMakeLists.txt
  12. 1 1
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/Dockerfile
  13. 6 8
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/Jenkinsfile
  14. 3 3
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/dependencies.list
  15. 10 14
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/CMakeLists.txt
  16. 8 13
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/binding_context.hpp
  17. 2 1
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/collection_notifications.hpp
  18. 3 3
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/apple/external_commit_helper.cpp
  19. 1 1
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/apple/keychain_helper.cpp
  20. 112 326
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/collection_change_builder.cpp
  21. 10 21
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/collection_change_builder.hpp
  22. 105 102
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/collection_notifier.cpp
  23. 53 54
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/collection_notifier.hpp
  24. 3 3
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/epoll/external_commit_helper.cpp
  25. 2 2
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/generic/external_commit_helper.cpp
  26. 1 1
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/generic/external_commit_helper.hpp
  27. 42 47
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/list_notifier.cpp
  28. 11 12
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/list_notifier.hpp
  29. 20 20
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/object_accessor_impl.hpp
  30. 18 51
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/object_notifier.cpp
  31. 5 13
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/object_notifier.hpp
  32. 340 221
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/realm_coordinator.cpp
  33. 82 58
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/realm_coordinator.hpp
  34. 246 144
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/results_notifier.cpp
  35. 58 23
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/results_notifier.hpp
  36. 148 459
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/transact_log_handler.cpp
  37. 7 10
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/transact_log_handler.hpp
  38. 16 18
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/weak_realm_notifier.cpp
  39. 6 19
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/weak_realm_notifier.hpp
  40. 1 1
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/windows/external_commit_helper.hpp
  41. 206 167
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/list.cpp
  42. 57 29
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/list.hpp
  43. 48 22
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object.cpp
  44. 29 14
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object.hpp
  45. 75 91
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object_accessor.hpp
  46. 52 28
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object_schema.cpp
  47. 20 13
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object_schema.hpp
  48. 116 234
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object_store.cpp
  49. 7 15
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object_store.hpp
  50. 17 16
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/property.hpp
  51. 456 211
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/results.cpp
  52. 120 90
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/results.hpp
  53. 16 33
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/schema.cpp
  54. 11 11
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/schema.hpp
  55. 249 364
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/shared_realm.cpp
  56. 80 84
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/shared_realm.hpp
  57. 208 78
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/thread_safe_reference.cpp
  58. 21 96
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/thread_safe_reference.hpp
  59. 7 7
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/util/tagged_bool.hpp
  60. 7 0
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/CMakeLists.txt
  61. 26 298
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/collection_change_indices.cpp
  62. 151 183
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/list.cpp
  63. 4 4
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/main.cpp
  64. 120 141
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/migrations.cpp
  65. 0 1
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/fuzzer.cpp
  66. 133 123
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/object.cpp
  67. 3 14
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/object_store.cpp
  68. 102 82
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/primitive_list.cpp
  69. 129 187
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/realm.cpp
  70. 287 202
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/results.cpp
  71. 19 39
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/schema.cpp
  72. 429 252
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/thread_safe_reference.cpp
  73. 195 429
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/transaction_log_parsing.cpp
  74. 3 2
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/util/index_helpers.hpp
  75. 32 24
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/util/test_file.cpp
  76. 21 13
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/util/test_file.hpp
  77. 1 1
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/workflow/build.sh
  78. 4 2
      Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/workflow/test_coverage.sh
  79. 4 4
      Carthage/Checkouts/realm-cocoa/Realm/RLMAccessor.hpp
  80. 83 68
      Carthage/Checkouts/realm-cocoa/Realm/RLMAccessor.mm
  81. 81 4
      Carthage/Checkouts/realm-cocoa/Realm/RLMArray.h
  82. 14 2
      Carthage/Checkouts/realm-cocoa/Realm/RLMArray.mm
  83. 0 1
      Carthage/Checkouts/realm-cocoa/Realm/RLMArray_Private.hpp
  84. 10 10
      Carthage/Checkouts/realm-cocoa/Realm/RLMClassInfo.hpp
  85. 17 18
      Carthage/Checkouts/realm-cocoa/Realm/RLMClassInfo.mm
  86. 27 0
      Carthage/Checkouts/realm-cocoa/Realm/RLMCollection.h
  87. 90 49
      Carthage/Checkouts/realm-cocoa/Realm/RLMCollection.mm
  88. 7 9
      Carthage/Checkouts/realm-cocoa/Realm/RLMCollection_Private.hpp
  89. 2 9
      Carthage/Checkouts/realm-cocoa/Realm/RLMConstants.h
  90. 4 4
      Carthage/Checkouts/realm-cocoa/Realm/RLMListBase.h
  91. 27 15
      Carthage/Checkouts/realm-cocoa/Realm/RLMListBase.mm
  92. 46 20
      Carthage/Checkouts/realm-cocoa/Realm/RLMManagedArray.mm
  93. 20 42
      Carthage/Checkouts/realm-cocoa/Realm/RLMMigration.mm
  94. 65 3
      Carthage/Checkouts/realm-cocoa/Realm/RLMObject.h
  95. 14 140
      Carthage/Checkouts/realm-cocoa/Realm/RLMObject.mm
  96. 257 15
      Carthage/Checkouts/realm-cocoa/Realm/RLMObjectBase.mm
  97. 0 6
      Carthage/Checkouts/realm-cocoa/Realm/RLMObjectSchema.mm
  98. 3 4
      Carthage/Checkouts/realm-cocoa/Realm/RLMObjectStore.h
  99. 15 10
      Carthage/Checkouts/realm-cocoa/Realm/RLMObjectStore.mm
  100. 13 2
      Carthage/Checkouts/realm-cocoa/Realm/RLMObject_Private.h

+ 227 - 0
Carthage/Checkouts/realm-cocoa/.jenkins.yml

@@ -9,6 +9,7 @@ xcode_version:
  - 11.2.1
  - 11.3
  - 11.4.1
+ - 11.5
 target: 
  - docs
  - swiftlint
@@ -24,6 +25,7 @@ target:
  - tvos-swift
  - catalyst
  - catalyst-swift
+ - xcframework
  - cocoapods-osx
  - cocoapods-ios
  - cocoapods-ios-dynamic
@@ -31,6 +33,7 @@ target:
  - swiftpm
  - swiftpm-address
  - swiftpm-thread
+ - swiftpm-ios
 configuration: 
  - Debug
  - Release
@@ -73,6 +76,14 @@ exclude:
     target: docs
     configuration: Debug
 
+  - xcode_version: 11.4.1
+    target: docs
+    configuration: Release
+
+  - xcode_version: 11.5
+    target: docs
+    configuration: Debug
+
   - xcode_version: 10.3
     target: swiftlint
     configuration: Debug
@@ -109,6 +120,14 @@ exclude:
     target: swiftlint
     configuration: Debug
 
+  - xcode_version: 11.4.1
+    target: swiftlint
+    configuration: Release
+
+  - xcode_version: 11.5
+    target: swiftlint
+    configuration: Debug
+
   - xcode_version: 10.3
     target: osx-encryption
     configuration: Debug
@@ -141,6 +160,14 @@ exclude:
     target: osx-encryption
     configuration: Debug
 
+  - xcode_version: 11.4.1
+    target: osx-encryption
+    configuration: Release
+
+  - xcode_version: 11.5
+    target: osx-encryption
+    configuration: Debug
+
   - xcode_version: 10.3
     target: osx-object-server
     configuration: Debug
@@ -173,6 +200,14 @@ exclude:
     target: osx-object-server
     configuration: Debug
 
+  - xcode_version: 11.4.1
+    target: osx-object-server
+    configuration: Release
+
+  - xcode_version: 11.5
+    target: osx-object-server
+    configuration: Debug
+
   - xcode_version: 10.3
     target: ios-static
     configuration: Debug
@@ -205,6 +240,14 @@ exclude:
     target: ios-static
     configuration: Debug
 
+  - xcode_version: 11.4.1
+    target: ios-static
+    configuration: Release
+
+  - xcode_version: 11.5
+    target: ios-static
+    configuration: Debug
+
   - xcode_version: 10.3
     target: ios-dynamic
     configuration: Debug
@@ -237,6 +280,14 @@ exclude:
     target: ios-dynamic
     configuration: Debug
 
+  - xcode_version: 11.4.1
+    target: ios-dynamic
+    configuration: Release
+
+  - xcode_version: 11.5
+    target: ios-dynamic
+    configuration: Debug
+
   - xcode_version: 10.3
     target: watchos
     configuration: Debug
@@ -269,6 +320,14 @@ exclude:
     target: watchos
     configuration: Debug
 
+  - xcode_version: 11.4.1
+    target: watchos
+    configuration: Release
+
+  - xcode_version: 11.5
+    target: watchos
+    configuration: Debug
+
   - xcode_version: 10.3
     target: tvos
     configuration: Debug
@@ -301,6 +360,14 @@ exclude:
     target: tvos
     configuration: Debug
 
+  - xcode_version: 11.4.1
+    target: tvos
+    configuration: Release
+
+  - xcode_version: 11.5
+    target: tvos
+    configuration: Debug
+
   - xcode_version: 10.3
     target: ios-swift
     configuration: Debug
@@ -333,6 +400,14 @@ exclude:
     target: ios-swift
     configuration: Debug
 
+  - xcode_version: 11.4.1
+    target: ios-swift
+    configuration: Release
+
+  - xcode_version: 11.5
+    target: ios-swift
+    configuration: Debug
+
   - xcode_version: 10.3
     target: tvos-swift
     configuration: Debug
@@ -365,6 +440,14 @@ exclude:
     target: tvos-swift
     configuration: Debug
 
+  - xcode_version: 11.4.1
+    target: tvos-swift
+    configuration: Release
+
+  - xcode_version: 11.5
+    target: tvos-swift
+    configuration: Debug
+
   - xcode_version: 10.3
     target: catalyst
     configuration: Debug
@@ -385,6 +468,10 @@ exclude:
     target: catalyst
     configuration: Debug
 
+  - xcode_version: 11.4.1
+    target: catalyst
+    configuration: Debug
+
   - xcode_version: 10.3
     target: catalyst-swift
     configuration: Debug
@@ -405,6 +492,54 @@ exclude:
     target: catalyst-swift
     configuration: Debug
 
+  - xcode_version: 11.4.1
+    target: catalyst-swift
+    configuration: Debug
+
+  - xcode_version: 10.3
+    target: xcframework
+    configuration: Debug
+
+  - xcode_version: 10.3
+    target: xcframework
+    configuration: Release
+
+  - xcode_version: 11.1
+    target: xcframework
+    configuration: Debug
+
+  - xcode_version: 11.1
+    target: xcframework
+    configuration: Release
+
+  - xcode_version: 11.2.1
+    target: xcframework
+    configuration: Debug
+
+  - xcode_version: 11.2.1
+    target: xcframework
+    configuration: Release
+
+  - xcode_version: 11.3
+    target: xcframework
+    configuration: Debug
+
+  - xcode_version: 11.3
+    target: xcframework
+    configuration: Release
+
+  - xcode_version: 11.4.1
+    target: xcframework
+    configuration: Debug
+
+  - xcode_version: 11.4.1
+    target: xcframework
+    configuration: Release
+
+  - xcode_version: 11.5
+    target: xcframework
+    configuration: Debug
+
   - xcode_version: 10.3
     target: cocoapods-osx
     configuration: Debug
@@ -425,6 +560,10 @@ exclude:
     target: cocoapods-osx
     configuration: Debug
 
+  - xcode_version: 11.5
+    target: cocoapods-osx
+    configuration: Debug
+
   - xcode_version: 10.3
     target: cocoapods-ios
     configuration: Debug
@@ -457,6 +596,14 @@ exclude:
     target: cocoapods-ios
     configuration: Debug
 
+  - xcode_version: 11.4.1
+    target: cocoapods-ios
+    configuration: Release
+
+  - xcode_version: 11.5
+    target: cocoapods-ios
+    configuration: Debug
+
   - xcode_version: 10.3
     target: cocoapods-ios-dynamic
     configuration: Debug
@@ -489,6 +636,14 @@ exclude:
     target: cocoapods-ios-dynamic
     configuration: Debug
 
+  - xcode_version: 11.4.1
+    target: cocoapods-ios-dynamic
+    configuration: Release
+
+  - xcode_version: 11.5
+    target: cocoapods-ios-dynamic
+    configuration: Debug
+
   - xcode_version: 10.3
     target: cocoapods-watchos
     configuration: Debug
@@ -521,6 +676,14 @@ exclude:
     target: cocoapods-watchos
     configuration: Debug
 
+  - xcode_version: 11.4.1
+    target: cocoapods-watchos
+    configuration: Release
+
+  - xcode_version: 11.5
+    target: cocoapods-watchos
+    configuration: Debug
+
   - xcode_version: 10.3
     target: swiftpm
     configuration: Debug
@@ -541,6 +704,10 @@ exclude:
     target: swiftpm
     configuration: Debug
 
+  - xcode_version: 11.4.1
+    target: swiftpm
+    configuration: Debug
+
   - xcode_version: 10.3
     target: swiftpm-address
     configuration: Debug
@@ -577,6 +744,14 @@ exclude:
     target: swiftpm-address
     configuration: Debug
 
+  - xcode_version: 11.4.1
+    target: swiftpm-address
+    configuration: Release
+
+  - xcode_version: 11.5
+    target: swiftpm-address
+    configuration: Debug
+
   - xcode_version: 10.3
     target: swiftpm-thread
     configuration: Debug
@@ -612,3 +787,55 @@ exclude:
   - xcode_version: 11.4.1
     target: swiftpm-thread
     configuration: Debug
+
+  - xcode_version: 11.4.1
+    target: swiftpm-thread
+    configuration: Release
+
+  - xcode_version: 11.5
+    target: swiftpm-thread
+    configuration: Debug
+
+  - xcode_version: 10.3
+    target: swiftpm-ios
+    configuration: Debug
+
+  - xcode_version: 10.3
+    target: swiftpm-ios
+    configuration: Release
+
+  - xcode_version: 11.1
+    target: swiftpm-ios
+    configuration: Debug
+
+  - xcode_version: 11.1
+    target: swiftpm-ios
+    configuration: Release
+
+  - xcode_version: 11.2.1
+    target: swiftpm-ios
+    configuration: Debug
+
+  - xcode_version: 11.2.1
+    target: swiftpm-ios
+    configuration: Release
+
+  - xcode_version: 11.3
+    target: swiftpm-ios
+    configuration: Debug
+
+  - xcode_version: 11.3
+    target: swiftpm-ios
+    configuration: Release
+
+  - xcode_version: 11.4.1
+    target: swiftpm-ios
+    configuration: Debug
+
+  - xcode_version: 11.4.1
+    target: swiftpm-ios
+    configuration: Release
+
+  - xcode_version: 11.5
+    target: swiftpm-ios
+    configuration: Debug

+ 374 - 0
Carthage/Checkouts/realm-cocoa/CHANGELOG.md

@@ -1,3 +1,226 @@
+5.0.3 Release notes (2020-06-10)
+=============================================================
+
+### Fixed
+
+* `-[RLMObject isFrozen]` always returned false. ([#6568](https://github.com/realm/realm-cocoa/issues/6568), since 5.0.0).
+* Freezing an object within the write transaction that the object was created
+  in now throws an exception rather than crashing when the object is first
+  used.
+* The schema for frozen Realms was not properly initialized, leading to crashes
+  when accessing a RLMLinkingObjects property.
+  ([#6568](https://github.com/realm/realm-cocoa/issues/6568), since 5.0.0).
+* Observing `Object.isInvalidated` via a keypath literal would produce a
+  warning in Swift 5.2 due to the property not being marked as @objc.
+  ([#6554](https://github.com/realm/realm-cocoa/issues/6554))
+
+### Compatibility
+
+* File format: Generates Realms with format v10 (Reads and upgrades all previous formats)
+* Realm Object Server: 3.21.0 or later.
+* Realm Studio: 3.11 or later.
+* APIs are backwards compatible with all previous releases in the 5.x.y series.
+* Carthage release for Swift is built with Xcode 11.5.
+
+5.0.2 Release notes (2020-06-02)
+=============================================================
+
+### Fixed
+
+* Fix errSecDuplicateItem (-25299) errors when opening a synchronized Realm
+  when upgrading from pre-5.0 versions of Realm.
+  ([#6538](https://github.com/realm/realm-cocoa/issues/6538), [#6494](https://github.com/realm/realm-cocoa/issues/6494), since 5.0.0).
+* Opening Realms stored on filesystems which do not support preallocation (such
+  as ExFAT) would give "Operation not supported" exceptions.
+  ([#6508](https://github.com/realm/realm-cocoa/issues/6508), since 3.2.0).
+* 'NoSuchTable' exceptions would sometimes be thrown after upgrading a Relam
+  file to the v10 format. ([Core #3701](https://github.com/realm/realm-core/issues/3701), since 5.0.0)
+* If the upgrade process was interrupted/killed for various reasons, the
+  following run could stop with some assertions failing. No instances of this
+  happening were reported to us. (Since 5.0.0).
+* Queries filtering a `List` where the query was on an indexed property over a
+  link would sometimes give incomplete results.
+  ([#6540](https://github.com/realm/realm-cocoa/issues/6540), since 4.1.0 but
+  more common since 5.0.0)
+* Opening a file in read-only mode would attempt to make a spurious write to
+  the file, causing errors if the file was in read-only storage (since 5.0.0).
+
+### Compatibility
+
+* File format: Generates Realms with format v10 (Reads and upgrades all previous formats)
+* Realm Object Server: 3.21.0 or later.
+* Realm Studio: 3.11 or later.
+* APIs are backwards compatible with all previous releases in the 5.x.y series.
+* Carthage release for Swift is built with Xcode 11.5.
+
+### Internal
+
+* Upgraded realm-core from v6.0.4 to v6.0.6
+* Upgraded realm-sync from v5.0.3 to v5.0.5
+
+5.0.1 Release notes (2020-05-27)
+=============================================================
+
+### Enhancements
+
+* Add prebuilt binary for Xcode 11.5 to the release package.
+
+### Fixed
+
+* Fix linker error when building a xcframework for Catalyst.
+  ([#6511](https://github.com/realm/realm-cocoa/issues/6511), since 4.3.1).
+* Fix building for iOS devices when using Swift Package Manager
+  ([#6522](https://github.com/realm/realm-cocoa/issues/6522), since 5.0.0).
+* `List` and `RealmOptional` properties on frozen objects were not initialized
+  correctly and would always report `nil` or an empty list.
+  ([#6527](https://github.com/realm/realm-cocoa/issues/6527), since 5.0.0).
+
+### Compatibility
+
+* File format: Generates Realms with format v10 (Reads and upgrades all previous formats)
+* Realm Object Server: 3.21.0 or later.
+* Realm Studio: 3.11 or later.
+* APIs are backwards compatible with all previous releases in the 5.x.y series.
+* Carthage release for Swift is built with Xcode 11.5.
+
+5.0.0 Release notes (2020-05-15)
+=============================================================
+
+NOTE: This version bumps the Realm file format to version 10. It is not
+possible to downgrade version 9 or earlier. Files created with older versions
+of Realm will be automatically upgraded. Only 
+[Studio 3.11](https://github.com/realm/realm-studio/releases/tag/v3.11.0) or later will be able
+to open the new file format.
+
+### Enhancements
+
+* Storing large binary blobs in Realm files no longer forces the file to be at
+  least 8x the size of the largest blob.
+* Reduce the size of transaction logs stored inside the Realm file, reducing
+  file size growth from large transactions.
+* Add support for frozen objects. `Realm`, `Results`, `List` and `Object` now
+  have `freeze()` methods which return a frozen copy of the object. These
+  objects behave similarly to creating unmanaged deep copies of the source
+  objects. They can be read from any thread and do not update when writes are
+  made to the Realm, but creating frozen objects does not actually copy data
+  out of the Realm and so can be much faster and use less memory. Frozen
+  objects cannot be mutated or observed for changes (as they never change).
+  ([PR #6427](https://github.com/realm/realm-cocoa/pull/6427)).
+* Add the `isFrozen` property to `Realm`, `Results`, `List` and `Object`.
+* Add `Realm.Configuration.maxNumberOfActiveVersions`. Each time a write
+  transaction is performed, a new version is created inside the Realm, and then
+  any versions which are no longer in use are cleaned up. If too many versions
+  are kept alive while performing writes (either due to a background thread
+  performing a long operation that doesn't let the Realm on that thread
+  refresh, or due to holding onto frozen versions for a long time) the Realm
+  file will grow in size, potentially to the point where it is too large to be
+  opened. Setting this configuration option will make write transactions which
+  would cause the live version count to exceed the limit to instead fail.
+* Add support for queue-confined Realms. Rather than being bound to a specific
+  thread, queue-confined Realms are bound to a serial dispatch queue and can be
+  used within blocks dispatched to that queue regardless of what thread they
+  happen to run on. In addition, change notifications will be delivered to that
+  queue rather than the thread's run loop. ([PR #6478](https://github.com/realm/realm-cocoa/pull/6478)).
+* Add an option to deliver object and collection notifications to a specific
+  serial queue rather than the current thread. ([PR #6478](https://github.com/realm/realm-cocoa/pull/6478)).
+* Add Combine publishers for Realm types. Realm collections have a `.publisher`
+  property which publishes the collection each time it changes, and a
+  `.changesetPublisher` which publishes a `RealmCollectionChange` each time the
+  collection changes. Corresponding publishers for Realm Objects can be
+  obtained with the `publisher()` and `changesetPublisher()` global functions.
+* Extend Combine publishers which output Realm types with a `.freeze()`
+  function which will make the publisher instead output frozen objects.
+* String primary keys no longer require a separate index, improving insertion
+  and deletion performance without hurting lookup performance.
+* Reduce the encrypted page reclaimer's impact on battery life when encryption
+  is used. ([Core #3461](https://github.com/realm/realm-core/pull/3461)).
+
+### Fixed
+
+* The uploaded bytes in sync progress notifications was sometimes incorrect and
+  wouldn't exactly equal the uploadable bytes when the uploaded completed.
+* macOS binaries were built with the incorrect deployment target (10.14 rather
+  than 10.9), resulting in linker warnings. ([#6299](https://github.com/realm/realm-cocoa/issues/6299), since 3.18.0).
+* An internal datastructure for List properties could be double-deleted if the
+  last reference was released from a thread other than the one which the List
+  was created on at the wrong time. This would typically manifest as
+  "pthread_mutex_destroy() failed", but could also result in other kinds of
+  crashes. ([#6333](https://github.com/realm/realm-cocoa/issues/6333)).
+* Sorting on float or double properties containing NaN values had inconsistent
+  results and would sometimes crash due to out-of-bounds memory accesses.
+  ([#6357](https://github.com/realm/realm-cocoa/issues/6357)).
+
+### Breaking Changes
+
+* The ObjectChange type in Swift is now generic and includes a reference to the
+  object which changed. When using `observe(on:)` to receive notifications on a
+  dispatch queue, the object will be confined to that queue.
+* The Realm instance passed in the callback to asyncOpen() is now confined to
+  the callback queue passed to asyncOpen() rather than the thread which the
+  callback happens to be called on. This means that the Realm instance may be
+  stored and reused in further blocks dispatched to that queue, but the queue
+  must now be a serial queue.
+* Files containing Date properties written by version of Realm prior to 1.0 can
+  no longer be opened.
+* Files containing Any properties can no longer be opened. This property type
+  was never documented and was deprecated in 1.0.
+* Deleting objects now preserves the order of objects reported by unsorted
+  Results rather than performing a swap operation before the delete. Note that
+  it is still not safe to assume that the order of objects in an unsorted
+  Results is the order that the objects were created in.
+* The minimum supported deployment target for iOS when using Swift Package
+  Manager to install Realm is now iOS 11.
+
+### Compatibility
+
+* File format: Generates Realms with format v10 (Reads and upgrades all previous formats)
+* Realm Object Server: 3.21.0 or later.
+* Realm Studio: 3.11 or later.
+* APIs are backwards compatible with all previous releases in the 5.x.y series.
+* Carthage release for Swift is built with Xcode 11.4.1.
+
+### Internal
+
+* Upgraded realm-core from v5.23.8 to v6.0.4
+* Upgraded realm-sync from v4.9.5 to v5.0.3
+
+5.0.0-beta.6 Release notes (2020-05-08)
+=============================================================
+
+### Enhancements
+
+* Add support for queue-confined Realms. Rather than being bound to a specific
+  thread, queue-confined Realms are bound to a serial dispatch queue and can be
+  used within blocks dispatched to that queue regardless of what thread they
+  happen to run on. In addition, change notifications will be delivered to that
+  queue rather than the thread's run loop. ([PR #6478](https://github.com/realm/realm-cocoa/pull/6478)).
+* Add an option to deliver object and collection notifications to a specific
+  serial queue rather than the current thread. ([PR #6478](https://github.com/realm/realm-cocoa/pull/6478)).
+
+### Fixed
+
+* The uploaded bytes in sync progress notifications was sometimes incorrect and
+  wouldn't exactly equal the uploadable bytes when the uploaded completed.
+
+### Breaking Changes
+
+* The Realm instance passed in the callback to asyncOpen() is now confined to
+  the callback queue passed to asyncOpen() rather than the thread which the
+  callback happens to be called on. This means that the Realm instance may be
+  stored and reused in further blocks dispatched to that queue, but the queue
+  must now be a serial queue.
+
+### Compatibility
+
+* File format: Generates Realms with format v10 (Reads and upgrades all previous formats)
+* Realm Object Server: 3.21.0 or later.
+* Carthage release for Swift is built with Xcode 11.4.1.
+
+### Internal
+
+* Upgraded realm-core from v6.0.3 to v6.0.4
+* Upgraded realm-sync from v5.0.1 to v5.0.3
+
 4.4.1 Release notes (2020-04-16)
 =============================================================
 
@@ -16,6 +239,157 @@
 * Realm Object Server: 3.21.0 or later.
 * Carthage release for Swift is built with Xcode 11.4.1.
 
+5.0.0-beta.3 Release notes (2020-02-26)
+=============================================================
+
+Based on 4.3.2 and also includes all changes since 4.3.0.
+
+### Enhancements
+
+* Add support for frozen objects. `Realm`, `Results`, `List` and `Object` now
+  have `freeze()` methods which return a frozen copy of the object. These
+  objects behave similarly to creating unmanaged deep copies of the source
+  objects. They can be read from any thread and do not update when writes are
+  made to the Realm, but creating frozen objects does not actually copy data
+  out of the Realm and so can be much faster and use less memory. Frozen
+  objects cannot be mutated or observed for changes (as they never change).
+  ([PR #6427](https://github.com/realm/realm-cocoa/pull/6427)).
+* Add the `isFrozen` property to `Realm`, `Results`, `List` and `Object`.
+* Add `Realm.Configuration.maxNumberOfActiveVersions`. Each time a write
+  transaction is performed, a new version is created inside the Realm, and then
+  any versions which are no longer in use are cleaned up. If too many versions
+  are kept alive while performing writes (either due to a background thread
+  performing a long operation that doesn't let the Realm on that thread
+  refresh, or due to holding onto frozen versions for a long time) the Realm
+  file will grow in size, potentially to the point where it is too large to be
+  opened. Setting this configuration option will make write transactions which
+  would cause the live version count to exceed the limit to instead fail.
+
+
+### Compatibility
+
+* File format: Generates Realms with format v10 (Reads and upgrades all previous formats)
+* Realm Object Server: 3.21.0 or later.
+* APIs are backwards compatible with all previous releases in the 5.x.y series.
+* Carthage release for Swift is built with Xcode 11.3.
+
+### Internal
+
+* Upgraded realm-core from v6.0.0-beta.3 to v6.0.3
+* Upgraded realm-sync from v5.0.0-beta.2 to v5.0.1
+
+5.0.0-beta.2 Release notes (2020-01-13)
+=============================================================
+
+Based on 4.3.0 and also includes all changes since 4.1.1.
+
+### Fixed
+
+* Fix compilation when using CocoaPods targeting iOS versions older than 11 (since 5.0.0-alpha).
+
+### Compatibility
+
+* File format: Generates Realms with format v10 (Reads and upgrades all previous formats)
+* Realm Object Server: 3.21.0 or later.
+* APIs are backwards compatible with all previous releases in the 5.x.y series.
+* Carthage release for Swift is built with Xcode 11.3.
+
+### Internal
+
+* Upgraded realm-core from v6.0.0-beta.2 to v6.0.0-beta.3
+* Upgraded realm-sync from v5.0.0-beta.1 to v5.0.0-beta.2
+
+5.0.0-beta.1 Release notes (2019-12-13)
+=============================================================
+
+Based on 4.1.1 and also includes all changes since 4.1.0.
+
+NOTE: This version bumps the Realm file format to version 10. It is not possible to downgrade version 9 or earlier. Files created with older versions of Realm will be automatically upgraded.
+
+### Enhancements
+
+* String primary keys no longer require a separate index, improving insertion
+  and deletion performance without hurting lookup performance.
+* Reduce the encrypted page reclaimer's impact on battery life when encryption
+  is used. ([Core #3461](https://github.com/realm/realm-core/pull/3461)).
+
+### Fixed
+
+* Fix an error when a table-backed Results was accessed immediately after
+  deleting the object previously at the index being accessed (since
+  5.0.0-alpha.1).
+* macOS binaries were built with the incorrect deployment target (10.14 rather
+  than 10.9), resulting in linker warnings. ([#6299](https://github.com/realm/realm-cocoa/issues/6299), since 3.18.0).
+* An internal datastructure for List properties could be double-deleted if the
+  last reference was released from a thread other than the one which the List
+  was created on at the wrong time. This would typically manifest as
+  "pthread_mutex_destroy() failed", but could also result in other kinds of
+  crashes. ([#6333](https://github.com/realm/realm-cocoa/issues/6333)).
+* Sorting on float or double properties containing NaN values had inconsistent
+  results and would sometimes crash due to out-of-bounds memory accesses.
+  ([#6357](https://github.com/realm/realm-cocoa/issues/6357)).
+
+### Known Issues
+
+* Changing which property of an object is the primary key in a migration will
+  break incoming links to objects of that type.
+* Changing the primary key of an object with Data properties in a migration
+  will crash.
+
+### Compatibility
+
+* File format: Generates Realms with format v10 (Reads and upgrades all previous formats)
+* Realm Object Server: 3.21.0 or later.
+* APIs are backwards compatible with all previous releases in the 5.x.y series.
+* Carthage release for Swift is built with Xcode 11.3.
+
+### Internal
+
+* Upgraded realm-core from v6.0.0-alpha.24 to v6.0.0-beta.2
+* Upgraded realm-sync from 4.7.1-core6.5 to v5.0.0-beta.1
+
+5.0.0-alpha.1 Release notes (2019-11-14)
+=============================================================
+
+Based on 4.1.0.
+
+### Enhancements
+
+* Add `-[RLMRealm fileExistsForConfiguration:]`/`Realm.fileExists(for:)`,
+  which checks if a local Realm file exists for the given configuration.
+* Add `-[RLMRealm deleteFilesForConfiguration:]`/`Realm.deleteFiles(for:)`
+  to delete the Realm file and all auxiliary files for the given configuration.
+* Storing large binary blobs in Realm files no longer forces the file to be at
+  least 8x the size of the largest blob.
+* Reduce the size of transaction logs stored inside the Realm file, reducing
+  file size growth from large transactions.
+
+NOTE: This version bumps the Realm file format to version 10. It is not
+possible to downgrade version 9 or earlier. Files created with older versions
+of Realm will be automatically upgraded. This automatic upgrade process is not
+yet well tested. Do not open Realm files with data you care about with this
+alpha version.
+
+### Breaking Changes
+
+* Files containing Date properties written by version of Realm prior to 1.0 can
+  no longer be opened.
+* Files containing Any properties can no longer be opened. This property type
+  was never documented and was deprecated in 1.0.
+
+### Compatibility
+
+* File format: Generates Realms with format v10 (Reads and upgrades v9)
+* Realm Object Server: 3.21.0 or later.
+* APIs are backwards compatible with all previous releases in the 4.x.y series.
+* Carthage release for Swift is built with Xcode 11.3.
+* Carthage release for Swift is built with Xcode 11.2.1.
+
+### Internal
+
+* Upgraded realm-core from 5.23.6 to v6.0.0-alpha.24.
+* Upgraded realm-sync from 4.8.2 to 4.7.1-core6.5.
+
 4.4.0 Release notes (2020-03-26)
 =============================================================
 

+ 2 - 1
Carthage/Checkouts/realm-cocoa/Configuration/Base.xcconfig

@@ -1,5 +1,5 @@
 ALWAYS_SEARCH_USER_PATHS = NO;
-CLANG_CXX_LANGUAGE_STANDARD = c++14;
+CLANG_CXX_LANGUAGE_STANDARD = c++17;
 CLANG_CXX_LIBRARY = libc++;
 CLANG_ENABLE_MODULES = YES;
 CLANG_ENABLE_OBJC_ARC = YES;
@@ -46,6 +46,7 @@ SWIFT_COMPILATION_MODE = wholemodule;
 SWIFT_OPTIMIZATION_LEVEL = -Owholemodule;
 WARNING_CFLAGS = -Wmismatched-tags -Wunused-private-field -Wpartial-availability;
 OTHER_CFLAGS = -fvisibility-inlines-hidden $(REALM_CATALYST_FLAGS);
+OTHER_CFLAGS[arch=armv7] = -fvisibility-inlines-hidden -fno-aligned-new $(REALM_CATALYST_FLAGS);
 OTHER_LDFLAGS = $(REALM_CATALYST_FLAGS);
 OTHER_SWIFT_FLAGS = $(REALM_CATALYST_FLAGS);
 

+ 1 - 1
Carthage/Checkouts/realm-cocoa/Configuration/Realm/Realm.xcconfig

@@ -33,7 +33,7 @@ REALM_PLATFORM_SUFFIX_macos = macosx;
 REALM_PLATFORM_SUFFIX_iosmac = maccatalyst;
 REALM_PLATFORM_SUFFIX = $(REALM_PLATFORM_SUFFIX_$(SDK_VARIANT));
 OTHER_LDFLAGS[sdk=macosx*] = -lrealm-$(REALM_PLATFORM_SUFFIX)$(REALM_LIBRARY_SUFFIX) $(REALM_CATALYST_FLAGS);
-OTHER_LIBTOOLFLAGS[sdk=macosx*] = -lrealm-$(REALM_PLATFORM_SUFFIX)$(REALM_LIBRARY_SUFFIX) $(REALM_CATALYST_FLAGS);
+OTHER_LIBTOOLFLAGS[sdk=macosx*] = -lrealm-$(REALM_PLATFORM_SUFFIX)$(REALM_LIBRARY_SUFFIX);
 OTHER_LDFLAGS[sdk=iphone*] = -lrealm-ios$(REALM_LIBRARY_SUFFIX);
 OTHER_LIBTOOLFLAGS[sdk=iphone*] = -lrealm-ios$(REALM_LIBRARY_SUFFIX);
 OTHER_LDFLAGS[sdk=watch*] = -lrealm-watchos$(REALM_LIBRARY_SUFFIX);

+ 26 - 18
Carthage/Checkouts/realm-cocoa/Jenkinsfile.releasability

@@ -1,10 +1,10 @@
-xcodeVersions = ['10.3', '11.1', '11.2.1', '11.3', '11.4.1']
+xcodeVersions = ['10.3', '11.1', '11.2.1', '11.3', '11.4.1', '11.5']
 platforms = ['osx', 'ios', 'watchos', 'tvos', 'catalyst']
 carthagePlatforms = ['osx', 'ios', 'watchos', 'tvos']
 platformNames = ['osx': 'macOS', 'ios': 'iOS', 'watchos': 'watchOS', 'tvos': 'tvOS', 'catalyst': 'Catalyst']
-carthageXcodeVersion = '11.4.1'
+carthageXcodeVersion = '11.5'
 objcXcodeVersion = '10.3'
-docsSwiftVersion = '5.1.2'
+docsSwiftVersion = '5.2.4'
 
 def installationTest(platform, test, language) {
   return {
@@ -17,10 +17,8 @@ def installationTest(platform, test, language) {
       }
 
       sh """
+      hostname
       export REALM_XCODE_VERSION=${carthageXcodeVersion}
-      if [ "${platform}" != osx ]; then
-        ./scripts/reset-simulators.sh
-      fi
       cd examples/installation
 
       archive=\$(echo \$PWD/realm-${language}-*.zip)
@@ -64,6 +62,7 @@ def doBuild() {
           deleteDir()
           unstash 'source'
           sh """
+          hostname
           export REALM_SWIFT_VERSION=${docsSwiftVersion}
           export PATH='/Users/realm/.rbenv/bin:/Users/realm/.rbenv/shims:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/realm/.gems/bin'
           ./scripts/reset-simulators.sh
@@ -82,7 +81,7 @@ def doBuild() {
         node('osx') {
           deleteDir()
           unstash 'source'
-          sh 'XCMODE=xcpretty ./build.sh package-examples'
+          sh './build.sh package-examples'
           stash includes: 'realm-examples.zip', name: 'examples'
         }
       },
@@ -91,7 +90,7 @@ def doBuild() {
         node('osx') {
           deleteDir()
           unstash 'source'
-          sh "XCMODE=xcpretty REALM_XCODE_VERSION=${objcXcodeVersion} ./build.sh package-ios-static"
+          sh "REALM_XCODE_VERSION=${objcXcodeVersion} ./build.sh package-ios-static"
           dir("build/ios-static") {
             stash includes: "realm-framework-ios-static.zip", name: "ios-static"
           }
@@ -107,13 +106,21 @@ def doBuild() {
           deleteDir()
           unstash 'source'
           sh """
+          hostname
           export REALM_XCODE_VERSION=${carthageXcodeVersion}
           . ./scripts/swift-version.sh
           set_xcode_and_swift_versions
 
-          if [ "${platform}" != osx ]; then
-            ./scripts/reset-simulators.rb
-          fi
+          # Carthage scans every xcodeproj in the directory looking for
+          # targets. This can be very slow and even spuriously time out, so
+          # remove the ones we don't want it to build.
+          rm -r examples plugin
+
+          # For whatever reason 'xcodebuild -list' is very slow sometimes which
+          # makes Carthage time out, but it's a lot faster if no simulators
+          # exist, so delete them all first and only create a single simulator
+          # for each platform.
+          ./scripts/reset-simulators.rb -firstOnly
 
           carthage build --no-skip-current --platform ${platform}
           carthage archive --output Carthage-${platform}.framework.zip
@@ -133,7 +140,7 @@ def doBuild() {
           node('osx') {
             deleteDir()
             unstash 'source'
-            sh "XCMODE=xcpretty REALM_XCODE_VERSION=${xcodeVersion} ./build.sh package ${platform}"
+            sh "REALM_XCODE_VERSION=${xcodeVersion} ./build.sh package ${platform}"
             dir("build/${platform}") {
               stash includes: "realm-framework-${platform}-${xcodeVersion}.zip",
                     name: "${platform}-${xcodeVersion}"
@@ -220,6 +227,7 @@ def doBuild() {
 
           def sha = params.sha
           sh """
+          hostname
           curl -O https://raw.githubusercontent.com/realm/realm-cocoa/${sha}/build.sh
           mkdir -p scripts
           curl https://raw.githubusercontent.com/realm/realm-cocoa/${sha}/scripts/swift-version.sh -o scripts/swift-version.sh
@@ -227,7 +235,7 @@ def doBuild() {
           curl https://raw.githubusercontent.com/realm/realm-cocoa/${sha}/scripts/reset-simulators.rb -o scripts/reset-simulators.rb
           chmod +x scripts/reset-simulators.rb
 
-          XCMODE=xcpretty sh build.sh package-test-examples-objc
+          sh build.sh package-test-examples-objc
           """
         }
       },
@@ -240,6 +248,7 @@ def doBuild() {
 
           def sha = params.sha
           sh """
+          hostname
           curl -O https://raw.githubusercontent.com/realm/realm-cocoa/${sha}/build.sh
           mkdir -p scripts
           curl https://raw.githubusercontent.com/realm/realm-cocoa/${sha}/scripts/swift-version.sh -o scripts/swift-version.sh
@@ -247,7 +256,7 @@ def doBuild() {
           curl https://raw.githubusercontent.com/realm/realm-cocoa/${sha}/scripts/reset-simulators.rb -o scripts/reset-simulators.rb
           chmod +x scripts/reset-simulators.rb
 
-          XCMODE=xcpretty sh build.sh package-test-examples-swift
+          sh build.sh package-test-examples-swift
           """
         }
       },
@@ -258,7 +267,7 @@ def doBuild() {
           unstash 'source'
 
           sh './scripts/reset-simulators.rb'
-          sh 'XCMODE=xcpretty sh build.sh test-ios-static'
+          sh 'sh build.sh test-ios-static'
         }
       },
 
@@ -266,8 +275,7 @@ def doBuild() {
         node('osx') {
           deleteDir()
           unstash 'source'
-
-          sh 'XCMODE=xcpretty sh build.sh test-osx'
+          sh 'sh build.sh test-osx'
         }
       }
     ]
@@ -284,7 +292,7 @@ def doBuild() {
 
     for (def platform in ["osx", "ios", "watchos"]) {
       def platformName = platformNames[platform]
-      for (def test in ["cocoapods", "carthage"]) {
+      for (def test in ["dynamic", "cocoapods", "carthage"]) {
         parallelBuilds["Installation - ${platformName} Swift ${test}"] = installationTest(platform, test, 'swift')
       }
     }

+ 2 - 1
Carthage/Checkouts/realm-cocoa/Realm.podspec

@@ -87,9 +87,10 @@ Pod::Spec.new do |s|
   s.private_header_files    = private_header_files
   s.header_mappings_dir     = 'include'
   s.pod_target_xcconfig     = { 'APPLICATION_EXTENSION_API_ONLY' => 'YES',
-                                'CLANG_CXX_LANGUAGE_STANDARD' => 'c++14',
+                                'CLANG_CXX_LANGUAGE_STANDARD' => 'c++17',
                                 'CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF' => 'NO',
                                 'OTHER_CPLUSPLUSFLAGS' => '-isystem "${PODS_ROOT}/Realm/include/core" -fvisibility-inlines-hidden',
+                                'OTHER_CPLUSPLUSFLAGS[arch=armv7]' => '-isystem "${PODS_ROOT}/Realm/include/core" -fvisibility-inlines-hidden -fno-aligned-new',
                                 'USER_HEADER_SEARCH_PATHS' => '"${PODS_ROOT}/Realm/include" "${PODS_ROOT}/Realm/include/Realm"',
                               }
   s.preserve_paths          = %w(build.sh include)

+ 15 - 48
Carthage/Checkouts/realm-cocoa/Realm/ObjectServerTests/RLMObjectServerTests.mm

@@ -1826,54 +1826,6 @@ static const NSInteger NUMBER_OF_BIG_OBJECTS = 2;
     XCTAssertLessThanOrEqual(finalSize, usedSize + 4096U);
 }
 
-#pragma mark - Offline Client Reset
-
-- (void)testOfflineClientReset {
-    NSError *error;
-    RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
-                                                                                            register:YES]
-                                               server:[RLMObjectServerTests authServerURL]];
-    NSURL *sourceFileURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"sync-1.x" withExtension:@"realm"];
-    NSString *fileName = [NSString stringWithFormat:@"%@.realm", [NSUUID new]];
-    NSURL *fileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]];
-    [NSFileManager.defaultManager copyItemAtURL:sourceFileURL toURL:fileURL error:&error];
-    XCTAssertNil(error);
-    if (error) {
-        return;
-    }
-
-    RLMRealmConfiguration *configuration = [user configurationWithURL:REALM_URL() fullSynchronization:true];
-    RLMSyncConfiguration *syncConfig = configuration.syncConfiguration;
-    syncConfig.customFileURL = fileURL;
-    configuration.syncConfiguration = syncConfig;
-
-    RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:&error];
-    XCTAssertNil(realm);
-    XCTAssertEqualObjects(error.domain, RLMErrorDomain);
-    XCTAssertEqual(error.code, RLMErrorIncompatibleSyncedFile);
-    RLMRealmConfiguration *backupConfiguration = error.userInfo[RLMBackupRealmConfigurationErrorKey];
-    XCTAssertNotNil(backupConfiguration);
-
-    // Open the backup Realm with a schema subset since it was created using the schema from .NET's unit tests.
-    // The Person class is declared in SwiftObjectServerTests.swift.
-    backupConfiguration.objectClasses = @[NSClassFromString(@"Person")];
-
-    error = nil;
-    RLMRealm *backupRealm = [RLMRealm realmWithConfiguration:backupConfiguration error:&error];
-    XCTAssertNotNil(backupRealm);
-    XCTAssertNil(error);
-
-    RLMResults *people = [backupRealm allObjects:@"Person"];
-    XCTAssertEqual(people.count, 1u);
-    XCTAssertEqualObjects([people[0] valueForKey:@"FirstName"], @"John");
-    XCTAssertEqualObjects([people[0] valueForKey:@"LastName"], @"Smith");
-
-    error = nil;
-    realm = [RLMRealm realmWithConfiguration:configuration error:&error];
-    XCTAssertNotNil(realm);
-    XCTAssertNil(error);
-}
-
 #pragma mark - Partial sync
 
 - (void)waitForKeyPath:(NSString *)keyPath object:(id)object value:(id)value {
@@ -1967,6 +1919,18 @@ static const NSInteger NUMBER_OF_BIG_OBJECTS = 2;
     return [self openRealmWithConfiguration:configuration];
 }
 
+- (void)testAllSubscriptionsChecksThatRealmIsQBS {
+    RLMRealm *nonsyncRealm = [RLMRealm defaultRealm];
+    RLMAssertThrowsWithReason(nonsyncRealm.subscriptions, @"query-based sync");
+
+    NSString *name = NSStringFromSelector(_cmd);
+    NSURL *server = [RLMObjectServerTests authServerURL];
+    RLMSyncCredentials *creds = [RLMObjectServerTests basicCredentialsWithName:name register:YES];
+    RLMSyncUser *user = [self logInUserForCredentials:creds server:server];
+    RLMRealm *fullsyncRealm = [self openRealmWithConfiguration:[user configurationWithURL:[NSURL URLWithString:@"realms://localhost:9443/~/default"] fullSynchronization:YES]];
+    RLMAssertThrowsWithReason(fullsyncRealm.subscriptions, @"query-based sync");
+}
+
 - (void)testAllSubscriptionsReportsNewlyCreatedSubscription {
     RLMRealm *realm = [self partialRealmWithName:_cmd];
     XCTAssertEqual(0U, realm.subscriptions.count);
@@ -2060,6 +2024,9 @@ static const NSInteger NUMBER_OF_BIG_OBJECTS = 2;
 }
 
 - (void)testSubscriptionWithName {
+    RLMRealm *nonsyncRealm = [RLMRealm defaultRealm];
+    XCTAssertThrows([nonsyncRealm subscriptionWithName:@"name"]);
+
     RLMRealm *realm = [self partialRealmWithName:_cmd];
     XCTAssertNil([realm subscriptionWithName:@"query"]);
 

+ 0 - 35
Carthage/Checkouts/realm-cocoa/Realm/ObjectServerTests/SwiftObjectServerTests.swift

@@ -516,41 +516,6 @@ class SwiftObjectServerTests: SwiftSyncTestCase {
         }
     }
 
-    // MARK: - Offline client reset
-
-    func testOfflineClientReset() {
-        let user = try! synchronouslyLogInUser(for: basicCredentials(), server: authURL)
-
-        let sourceFileURL = Bundle(for: type(of: self)).url(forResource: "sync-1.x", withExtension: "realm")!
-        let fileName = "\(UUID()).realm"
-        let fileURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(fileName)
-        try! FileManager.default.copyItem(at: sourceFileURL, to: fileURL)
-
-        let syncConfig = ObjectiveCSupport.convert(object: user.configuration(realmURL: realmURL, fullSynchronization: true).syncConfiguration!)
-        syncConfig.customFileURL = fileURL
-        let config = Realm.Configuration(syncConfiguration: ObjectiveCSupport.convert(object: syncConfig))
-        do {
-            _ = try Realm(configuration: config)
-        } catch let e as Realm.Error where e.code == .incompatibleSyncedFile {
-            var backupConfiguration = e.backupConfiguration
-            XCTAssertNotNil(backupConfiguration)
-
-            // Open the backup Realm with a schema subset since it was created using the schema from .NET's unit tests.
-            backupConfiguration!.objectTypes = [Person.self]
-            let backupRealm = try! Realm(configuration: backupConfiguration!)
-
-            let people = backupRealm.objects(Person.self)
-            XCTAssertEqual(people.count, 1)
-            XCTAssertEqual(people[0].FirstName, "John")
-            XCTAssertEqual(people[0].LastName, "Smith")
-
-            // Verify that we can now successfully open the original synced Realm.
-            _ = try! Realm(configuration: config)
-        } catch {
-            fatalError("Unexpected error: \(error)")
-        }
-    }
-
     // MARK: - Certificate Pinning
 
     func testSecureConnectionToLocalhostWithDefaultSecurity() {

+ 4 - 1
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/CMake/CompilerFlags.cmake

@@ -32,6 +32,7 @@ if(${CMAKE_CXX_COMPILER_ID} MATCHES "Clang" OR ${CMAKE_CXX_COMPILER_ID} STREQUAL
         -Wall
         -Wextra
         -Wno-missing-field-initializers
+        -Wno-unevaluated-expression
         -Wempty-body
         -Wparentheses
         -Wunknown-pragmas
@@ -72,11 +73,13 @@ if(${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")
         -Wconditional-uninitialized
         -Wconstant-conversion
         -Wenum-conversion
+        -Wimplicit-fallthrough
         -Wint-conversion
         -Wmissing-prototypes
         -Wnewline-eof
         -Wshorten-64-to-32
-        -Wimplicit-fallthrough
+        -Wthread-safety
+        -Wthread-safety-negative
     )
 endif()
 

+ 71 - 87
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/CMake/RealmCore.cmake

@@ -1,4 +1,3 @@
-###########################################################################
 #
 # Copyright 2016 Realm Inc.
 #
@@ -39,15 +38,34 @@ if(APPLE)
 
     set(CRYPTO_LIBRARIES "")
     set(SSL_LIBRARIES ${FOUNDATION_FRAMEWORK} ${SECURITY_FRAMEWORK})
-elseif(REALM_PLATFORM STREQUAL "Android")
-    set(CRYPTO_LIBRARIES crypto)
-    set(SSL_LIBRARIES ssl)
 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()
-    find_package(OpenSSL REQUIRED)
+    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)
@@ -61,7 +79,7 @@ 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(${core_prefix} ${sync_prefix})
+            build_existing_realm_sync(${sync_prefix})
         endif()
     elseif(enable_sync)
         # FIXME: Support building against prebuilt sync binaries.
@@ -110,26 +128,6 @@ function(download_realm_tarball url target libraries)
     endif()
 endfunction()
 
-function(download_android_openssl)
-    if(ANDROID)
-        string(TOLOWER "${CMAKE_BUILD_TYPE}" BUILD_TYPE)
-        set(OPENSSL_FILENAME "openssl-${BUILD_TYPE}-${ANDROID_OPENSSL_VERSION}-Android-${ANDROID_ABI}")
-        set(OPENSSL_URL "http://static.realm.io/downloads/openssl/${ANDROID_OPENSSL_VERSION}/Android/${ANDROID_ABI}/${OPENSSL_FILENAME}.tar.gz")
-
-        message(STATUS "Downloading OpenSSL...")
-        file(DOWNLOAD "${OPENSSL_URL}" "${CMAKE_BINARY_DIR}/${OPENSSL_FILENAME}.tar.gz")
-
-        message(STATUS "Uncompressing OpenSSL...")
-        execute_process(COMMAND ${CMAKE_COMMAND} -E tar xfz "${OPENSSL_FILENAME}.tar.gz")
-
-        message(STATUS "Importing OpenSSL...")
-        include(${CMAKE_BINARY_DIR}/${OPENSSL_FILENAME}/openssl.cmake)
-        get_target_property(OPENSSL_INCLUDE_DIR crypto INTERFACE_INCLUDE_DIRECTORIES)
-        get_target_property(CRYPTO_LIB crypto IMPORTED_LOCATION)
-        get_target_property(SSL_LIB ssl IMPORTED_LOCATION)
-    endif()
-endfunction()
-
 function(download_realm_core core_version)
     if(CMAKE_SYSTEM_NAME MATCHES "Windows")
         set(compression "tar.gz")
@@ -191,7 +189,6 @@ function(download_realm_core core_version)
         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}")
-        download_android_openssl()
     endif()
 
     add_custom_target(realm-core DEPENDS ${core_libraries})
@@ -221,25 +218,17 @@ endfunction()
 macro(build_realm_core)
     set(core_prefix_directory "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/realm-core")
 
-    separate_arguments(core_cfg_args UNIX_COMMAND "-D REALM_BUILD_LIB_ONLY=YES -D REALM_SKIP_SHARED_LIB=YES -G Ninja ${CORE_SANITIZER_FLAGS}")
     ExternalProject_Add(realm-core
         PREFIX ${core_prefix_directory}
         BUILD_IN_SOURCE 1
         UPDATE_DISCONNECTED 1
         INSTALL_COMMAND ""
-        CONFIGURE_COMMAND ${CMAKE_COMMAND} -E make_directory build.debug
-                        && cd build.debug
-                        && cmake -D CMAKE_BUILD_TYPE=Debug ${core_cfg_args} ..
-                        && cd ..
-                        && ${CMAKE_COMMAND} -E make_directory build.release
-                        && cd build.release
-                        && cmake -D CMAKE_BUILD_TYPE=RelWithDebInfo ${core_cfg_args} ..
-
-        BUILD_COMMAND cd build.debug
-                   && cmake --build .
-                   && cd ..
-                   && cd build.release
-                   && cmake --build .
+        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}
         )
@@ -268,7 +257,7 @@ macro(build_realm_core)
     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 Threads::Threads ${CRYPTO_LIBRARIES})
+    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.
@@ -303,52 +292,42 @@ function(build_existing_realm_core core_directory)
                      )
 endfunction()
 
-macro(build_realm_sync core_directory)
+macro(build_realm_sync)
     set(sync_prefix_directory "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/realm-sync")
 
-    separate_arguments(sync_cfg_args UNIX_COMMAND "-DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR} -DREALM_BUILD_TESTS=OFF -DREALM_BUILD_COMMANDLINE_TOOLS=OFF -G Ninja ${CORE_SANITIZER_FLAGS}")
+    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_COMMAND} -E make_directory build.debug
-                        && cd build.debug
-                        && cmake -D CMAKE_BUILD_TYPE=Debug -DREALM_CORE_BUILDTREE=${core_directory}/build.debug ${sync_cfg_args} -DREALM_BUILD_DOGLESS=OFF ..
-                        && cd ..
-                        && ${CMAKE_COMMAND} -E make_directory build.release
-                        && cd build.release
-                        && cmake -D CMAKE_BUILD_TYPE=RelWithDebInfo -DREALM_CORE_BUILDTREE=${core_directory}/build.release ${sync_cfg_args} -DREALM_BUILD_DOGLESS=OFF ..
-
-        BUILD_COMMAND cd build.debug
-                   && cmake --build .
-                   && cd ..
-                   && cd build.release
-                   && cmake --build .
-        ${USES_TERMINAL_BUILD}
+        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)
-    add_dependencies(realm-sync-lib realm-core)
 
     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}")
-    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-libraries
-        DEPENDEES build
         BYPRODUCTS ${sync_library_debug} ${sync_library_release}
-                   ${sync_server_library_debug} ${sync_server_library_release}
+        DEPENDEES build
         )
 
-    set(sync_generated_headers_dir_debug "${sync_debug_binary_dir}/src")
-    set(sync_generated_headers_dir_release "${sync_release_binary_dir}/src")
-
     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})
@@ -358,41 +337,46 @@ macro(build_realm_sync core_directory)
 
     # 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 "${sync_generated_headers_dir_debug}" "${sync_generated_headers_dir_release}" "${SOURCE_DIR}/src")
+    file(MAKE_DIRECTORY ${SOURCE_DIR}/src)
+    set_property(TARGET realm-sync PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${SOURCE_DIR}/src)
 
-    set_property(TARGET realm-sync PROPERTY INTERFACE_INCLUDE_DIRECTORIES
-        ${SOURCE_DIR}/src
-        $<$<CONFIG:Debug>:${sync_generated_headers_dir_debug}>
-        $<$<NOT:$<CONFIG:Debug>>:${sync_generated_headers_dir_release}>
-    )
+    # 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 realm-sync)
+    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 core_directory sync_directory)
+function(build_existing_realm_sync sync_directory)
     get_filename_component(sync_directory ${sync_directory} ABSOLUTE)
-    build_realm_sync(
-        ${core_directory}
-        URL ""
-        SOURCE_DIR ${sync_directory}
-        BUILD_ALWAYS 1
-        )
+    build_realm_sync(URL ""
+                     SOURCE_DIR ${sync_directory}
+                     BUILD_ALWAYS 1
+                     )
 
 endfunction()
 
 function(clone_and_build_realm_sync branch)
-    set(core_prefix_directory "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/realm-core/src/realm-core")
     set(cmake_files ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY})
-    build_realm_sync(
-        ${core_prefix_directory}
-        GIT_REPOSITORY "git@github.com:realm/realm-sync.git"
-        GIT_TAG ${branch}
-        CONFIGURE_COMMAND ${config_cmd}
-        )
+
+    build_realm_sync(GIT_REPOSITORY "git@github.com:realm/realm-sync.git"
+                     GIT_TAG ${branch}
+                     CONFIGURE_COMMAND ${config_cmd}
+                     )
 
 endfunction()

+ 1 - 1
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/CMakeLists.txt

@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.2.0)
 
 if(REALM_PLATFORM STREQUAL "Android")
     # This must be before project()
-    set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/CMake/android.toolchain.cmake")
+    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()

+ 1 - 1
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/Dockerfile

@@ -1,7 +1,7 @@
 FROM ubuntu:xenial
 
 RUN apt-get update && \
-    apt-get install -y wget build-essential lcov curl cmake gcovr libssl-dev \
+    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

+ 6 - 8
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/Jenkinsfile

@@ -76,20 +76,18 @@ def doAndroidDockerBuild() {
     node('docker') {
       getSourceArchive()
       wrap([$class: 'AnsiColorBuildWrapper']) {
-        def image = buildDockerEnv('ci/realm-object-store:android')
+        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 '''rm -rf build
-              mkdir build
-              cd build
-              cmake -DREALM_PLATFORM=Android -DANDROID_NDK=/opt/android-ndk -GNinja -DCMAKE_MAKE_PROGRAM=ninja ..
-              ninja
+            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 tests/tests /data/local/tmp
+              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
-            '''
+            """
           }
         }
       }

+ 3 - 3
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/dependencies.list

@@ -1,4 +1,4 @@
-REALM_CORE_VERSION=5.23.6
-REALM_SYNC_VERSION=4.9.0
-ANDROID_OPENSSL_VERSION=1.0.2k
+REALM_CORE_VERSION=6.0.1
+REALM_SYNC_VERSION=5.0.0
 REALM_CORE_PACKAGING=2
+OPENSSL_VERSION=1.1.1b

+ 10 - 14
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/CMakeLists.txt

@@ -4,6 +4,7 @@ set(SOURCES
     index_set.cpp
     list.cpp
     object.cpp
+    object_changeset.cpp
     object_schema.cpp
     object_store.cpp
     results.cpp
@@ -15,23 +16,23 @@ set(SOURCES
     impl/collection_notifier.cpp
     impl/list_notifier.cpp
     impl/object_notifier.cpp
-    impl/primitive_list_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
-    execution_context_id.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
@@ -52,21 +53,22 @@ set(HEADERS
     impl/notification_wrapper.hpp
     impl/object_accessor_impl.hpp
     impl/object_notifier.hpp
-    impl/primitive_list_notifier.hpp
     impl/realm_coordinator.hpp
     impl/results_notifier.hpp
     impl/transact_log_handler.hpp
     impl/weak_realm_notifier.hpp
 
-    util/android/event_loop_signal.hpp
-    util/apple/event_loop_signal.hpp
-    util/generic/event_loop_signal.hpp
-    util/uv/event_loop_signal.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/event_loop_signal.hpp
+    util/scheduler.hpp
     util/tagged_bool.hpp
     util/uuid.hpp)
 
@@ -80,10 +82,6 @@ else()
     list(APPEND SOURCES impl/generic/external_commit_helper.cpp)
 endif()
 
-if(NOT APPLE AND NOT REALM_PLATFORM STREQUAL "Android")
-    list(APPEND SOURCES util/generic/event_loop_signal.cpp)
-endif()
-
 set(INCLUDE_DIRS
     ${UV_INCLUDE_DIR}
     ${CMAKE_CURRENT_SOURCE_DIR})
@@ -95,7 +93,6 @@ if(REALM_ENABLE_SYNC)
         sync/subscription_state.hpp
         sync/sync_config.hpp
         sync/sync_manager.hpp
-        sync/sync_permission.hpp
         sync/sync_session.hpp
         sync/sync_user.hpp
         sync/impl/sync_client.hpp
@@ -107,7 +104,6 @@ if(REALM_ENABLE_SYNC)
         sync/partial_sync.cpp
         sync/sync_config.cpp
         sync/sync_manager.cpp
-        sync/sync_permission.cpp
         sync/sync_session.cpp
         sync/sync_user.cpp
         sync/impl/sync_file.cpp

+ 8 - 13
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/binding_context.hpp

@@ -21,8 +21,11 @@
 
 #include "index_set.hpp"
 
+#include <realm/keys.hpp>
+
 #include <memory>
 #include <tuple>
+#include <unordered_map>
 #include <vector>
 
 namespace realm {
@@ -75,10 +78,6 @@ public:
 
     std::weak_ptr<Realm> realm;
 
-    // If the user adds a notification handler to the Realm, will it ever
-    // actually be called?
-    virtual bool can_deliver_notifications() const noexcept { return true; }
-
     // Called 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.
@@ -132,9 +131,6 @@ public:
 
     // Change information for a single field of a row
     struct ColumnInfo {
-        // The index of this column prior to the changes in the tracked
-        // transaction, or -1 for newly inserted columns.
-        size_t initial_column_index = -1;
         // What kind of change occurred?
         // Always Set or None for everything but LinkList columns.
         enum class Kind {
@@ -157,10 +153,9 @@ public:
     // The Realm parses the transaction log, and populates the `changes` vector
     // in each ObserverState with information about what changes were made.
     struct ObserverState {
-        // Initial table and row which is observed
-        // May be updated by row insertions and removals
-        size_t table_ndx;
-        size_t row_ndx;
+        // Table and row which is observed
+        realm::TableKey table_key;
+        int64_t obj_key;
 
         // Opaque userdata for the delegate's use
         void* info;
@@ -168,12 +163,12 @@ public:
         // Populated with information about which columns were changed
         // May be shorter than the actual number of columns if the later columns
         // are not modified
-        std::vector<ColumnInfo> changes;
+        std::unordered_map<int64_t, ColumnInfo> changes;
 
         // Simple lexographic ordering
         friend bool operator<(ObserverState const& lft, ObserverState const& rgt)
         {
-            return std::tie(lft.table_ndx, lft.row_ndx) < std::tie(rgt.table_ndx, rgt.row_ndx);
+            return std::tie(lft.table_key, lft.obj_key) < std::tie(rgt.table_key, rgt.obj_key);
         }
     };
 };

+ 2 - 1
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/collection_notifications.hpp

@@ -25,6 +25,7 @@
 #include <exception>
 #include <memory>
 #include <type_traits>
+#include <unordered_map>
 #include <vector>
 
 namespace realm {
@@ -86,7 +87,7 @@ struct CollectionChangeSet {
     std::vector<Move> moves;
 
     // Per-column version of `modifications`
-    std::vector<IndexSet> columns;
+    std::unordered_map<int64_t, IndexSet> columns;
 
     bool empty() const noexcept
     {

+ 3 - 3
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/apple/external_commit_helper.cpp

@@ -19,7 +19,7 @@
 #include "impl/external_commit_helper.hpp"
 #include "impl/realm_coordinator.hpp"
 
-#include <realm/group_shared_options.hpp>
+#include <realm/db_options.hpp>
 #include <realm/util/fifo_helper.hpp>
 
 #include <asl.h>
@@ -109,7 +109,7 @@ ExternalCommitHelper::ExternalCommitHelper(RealmCoordinator& parent)
     // 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 `SharedGroupOptions::set_sys_tmp_dir()`
+    //  3) A location defined by `DBOptions::set_sys_tmp_dir()`
     //
     // Core has a similar policy for its named pipes.
     //
@@ -119,7 +119,7 @@ ExternalCommitHelper::ExternalCommitHelper(RealmCoordinator& 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(SharedGroupOptions::get_sys_tmp_dir());
+    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);

+ 1 - 1
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/apple/keychain_helper.cpp

@@ -62,7 +62,6 @@ CFPtr<CFMutableDictionaryRef> build_search_dictionary(CFStringRef account, CFStr
 
     CFDictionaryAddValue(d.get(), kSecClass, kSecClassGenericPassword);
     CFDictionaryAddValue(d.get(), kSecReturnData, kCFBooleanTrue);
-    CFDictionaryAddValue(d.get(), kSecAttrAccessible, kSecAttrAccessibleAlways);
     CFDictionaryAddValue(d.get(), kSecAttrAccount, account);
     CFDictionaryAddValue(d.get(), kSecAttrService, service);
 #if !TARGET_IPHONE_SIMULATOR
@@ -97,6 +96,7 @@ util::Optional<std::vector<char>> get_key(CFStringRef account, CFStringRef servi
 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();

+ 112 - 326
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/collection_change_builder.cpp

@@ -19,7 +19,6 @@
 #include "impl/collection_change_builder.hpp"
 
 #include <realm/util/assert.hpp>
-#include <algorithm>
 
 #include <algorithm>
 
@@ -50,15 +49,20 @@ void CollectionChangeBuilder::merge(CollectionChangeBuilder&& c)
     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) {
-            if (columns.size() < c.columns.size())
-                columns.resize(c.columns.size());
-            else if (columns.size() > c.columns.size())
-                c.columns.resize(columns.size());
-            for (size_t i = 0; i < columns.size(); ++i)
-                f(columns[i], c.columns[i]);
+            for (auto col : col_keys)
+                f(columns[col], c.columns[col]);
         }
     };
 
@@ -153,32 +157,11 @@ void CollectionChangeBuilder::clean_up_stale_moves()
     }), end(moves));
 }
 
-void CollectionChangeBuilder::parse_complete()
-{
-    moves.reserve(m_move_mapping.size());
-    for (auto move : m_move_mapping) {
-        REALM_ASSERT_DEBUG(deletions.contains(move.second));
-        REALM_ASSERT_DEBUG(insertions.contains(move.first));
-        if (move.first == move.second) {
-            deletions.remove(move.second);
-            insertions.remove(move.first);
-        }
-        else
-            moves.push_back({move.second, move.first});
-    }
-    m_move_mapping.clear();
-    std::sort(begin(moves), end(moves),
-              [](auto const& a, auto const& b) { return a.from < b.from; });
-}
-
 void CollectionChangeBuilder::modify(size_t ndx, size_t col)
 {
     modifications.add(ndx);
     if (!m_track_columns || col == IndexSet::npos)
         return;
-
-    if (col >= columns.size())
-        columns.resize(col + 1);
     columns[col].add(ndx);
 }
 
@@ -188,7 +171,7 @@ void CollectionChangeBuilder::for_each_col(Func&& f)
     f(modifications);
     if (m_track_columns) {
         for (auto& col : columns)
-            f(col);
+            f(col.second);
     }
 }
 
@@ -206,24 +189,6 @@ void CollectionChangeBuilder::insert(size_t index, size_t count, bool track_move
         if (move.to >= index)
             move.to += count;
     }
-
-    if (m_move_mapping.empty())
-        return;
-
-    // m_move_mapping is new_ndx -> old_ndx, so updating the keys requires
-    // deleting and re-inserting at the new index
-    std::vector<std::pair<size_t, size_t>> shifted;
-    for (auto it = m_move_mapping.begin(); it != m_move_mapping.end(); ) {
-        if (it->first >= index) {
-            shifted.emplace_back(it->first + count, it->second);
-            it = m_move_mapping.erase(it);
-        }
-        else {
-            ++it;
-        }
-    }
-    for (auto& pair : shifted)
-        m_move_mapping.insert(pair);
 }
 
 void CollectionChangeBuilder::erase(size_t index)
@@ -246,17 +211,14 @@ void CollectionChangeBuilder::erase(size_t index)
 
 void CollectionChangeBuilder::clear(size_t old_size)
 {
-    if (old_size != std::numeric_limits<size_t>::max()) {
-        for (auto range : deletions)
-            old_size += range.second - range.first;
-        for (auto range : insertions)
-            old_size -= range.second - range.first;
-    }
+    for (auto range : deletions)
+        old_size += range.second - range.first;
+    for (auto range : insertions)
+        old_size -= range.second - range.first;
 
     modifications.clear();
     insertions.clear();
     moves.clear();
-    m_move_mapping.clear();
     columns.clear();
     deletions.set(old_size);
 }
@@ -308,174 +270,6 @@ void CollectionChangeBuilder::move(size_t from, size_t to)
     });
 }
 
-void CollectionChangeBuilder::move_over(size_t row_ndx, size_t last_row, bool track_moves)
-{
-    REALM_ASSERT(row_ndx <= last_row);
-    REALM_ASSERT(insertions.empty() || prev(insertions.end())->second - 1 <= last_row);
-    REALM_ASSERT(modifications.empty() || prev(modifications.end())->second - 1 <= last_row);
-
-    if (row_ndx == last_row) {
-        if (track_moves) {
-            auto shifted_from = insertions.erase_or_unshift(row_ndx);
-            if (shifted_from != IndexSet::npos)
-                deletions.add_shifted(shifted_from);
-            m_move_mapping.erase(row_ndx);
-        }
-        for_each_col([=](auto& col) { col.remove(row_ndx); });
-        return;
-    }
-
-    for_each_col([=](auto& col) {
-        bool modified = col.contains(last_row);
-        if (modified) {
-            col.remove(last_row);
-            col.add(row_ndx);
-        }
-        else
-            col.remove(row_ndx);
-    });
-
-    if (!track_moves)
-        return;
-
-    bool row_is_insertion = insertions.contains(row_ndx);
-    bool last_is_insertion = !insertions.empty() && prev(insertions.end())->second == last_row + 1;
-    REALM_ASSERT_DEBUG(insertions.empty() || prev(insertions.end())->second <= last_row + 1);
-
-    // Collapse A -> B, B -> C into a single A -> C move
-    bool last_was_already_moved = false;
-    if (last_is_insertion) {
-        auto it = m_move_mapping.find(last_row);
-        if (it != m_move_mapping.end() && it->first == last_row) {
-            m_move_mapping[row_ndx] = it->second;
-            m_move_mapping.erase(it);
-            last_was_already_moved = true;
-        }
-    }
-
-    // Remove moves to the row being deleted
-    if (row_is_insertion && !last_was_already_moved) {
-        auto it = m_move_mapping.find(row_ndx);
-        if (it != m_move_mapping.end() && it->first == row_ndx)
-            m_move_mapping.erase(it);
-    }
-
-    // Don't report deletions/moves if last_row is newly inserted
-    if (last_is_insertion) {
-        insertions.remove(last_row);
-    }
-    // If it was previously moved, the unshifted source row has already been marked as deleted
-    else if (!last_was_already_moved) {
-        auto shifted_last_row = insertions.unshift(last_row);
-        shifted_last_row = deletions.add_shifted(shifted_last_row);
-        m_move_mapping[row_ndx] = shifted_last_row;
-    }
-
-    // Don't mark the moved-over row as deleted if it was a new insertion
-    if (!row_is_insertion) {
-        deletions.add_shifted(insertions.unshift(row_ndx));
-        insertions.add(row_ndx);
-    }
-    verify();
-}
-
-void CollectionChangeBuilder::swap(size_t ndx_1, size_t ndx_2, bool track_moves)
-{
-    REALM_ASSERT(ndx_1 != ndx_2);
-    // The order of the two indices doesn't matter semantically, but making them
-    // consistent simplifies the logic
-    if (ndx_1 > ndx_2)
-        std::swap(ndx_1, ndx_2);
-
-    for_each_col([=](auto& col) {
-        bool row_1_modified = col.contains(ndx_1);
-        bool row_2_modified = col.contains(ndx_2);
-        if (row_1_modified != row_2_modified) {
-            if (row_1_modified) {
-                col.remove(ndx_1);
-                col.add(ndx_2);
-            }
-            else {
-                col.remove(ndx_2);
-                col.add(ndx_1);
-            }
-        }
-    });
-
-    if (!track_moves)
-        return;
-
-    auto update_move = [&](auto existing_it, auto ndx_1, auto ndx_2) {
-        // update the existing move to ndx_2 to point at ndx_1
-        auto original = existing_it->second;
-        m_move_mapping.erase(existing_it);
-        m_move_mapping[ndx_1] = original;
-
-        // add a move from 1 -> 2 unless 1 was a new insertion
-        if (!insertions.contains(ndx_1)) {
-            m_move_mapping[ndx_2] = deletions.add_shifted(insertions.unshift(ndx_1));
-            insertions.add(ndx_1);
-        }
-        REALM_ASSERT_DEBUG(insertions.contains(ndx_2));
-    };
-
-    auto move_1 = m_move_mapping.find(ndx_1);
-    auto move_2 = m_move_mapping.find(ndx_2);
-    bool have_move_1 = move_1 != end(m_move_mapping) && move_1->first == ndx_1;
-    bool have_move_2 = move_2 != end(m_move_mapping) && move_2->first == ndx_2;
-    if (have_move_1 && have_move_2) {
-        // both are already moves, so just swap the destinations
-        std::swap(move_1->second, move_2->second);
-    }
-    else if (have_move_1) {
-        update_move(move_1, ndx_2, ndx_1);
-    }
-    else if (have_move_2) {
-        update_move(move_2, ndx_1, ndx_2);
-    }
-    else {
-        // ndx_2 needs to be done before 1 to avoid incorrect shifting
-        if (!insertions.contains(ndx_2)) {
-            m_move_mapping[ndx_1] = deletions.add_shifted(insertions.unshift(ndx_2));
-            insertions.add(ndx_2);
-        }
-        if (!insertions.contains(ndx_1)) {
-            m_move_mapping[ndx_2] = deletions.add_shifted(insertions.unshift(ndx_1));
-            insertions.add(ndx_1);
-        }
-    }
-}
-
-void CollectionChangeBuilder::subsume(size_t old_ndx, size_t new_ndx, bool track_moves)
-{
-    REALM_ASSERT(old_ndx != new_ndx);
-
-    for_each_col([=](auto& col) {
-        if (col.contains(old_ndx)) {
-            col.add(new_ndx);
-        }
-    });
-
-    if (!track_moves)
-        return;
-
-    REALM_ASSERT_DEBUG(insertions.contains(new_ndx));
-    REALM_ASSERT_DEBUG(!m_move_mapping.count(new_ndx));
-
-    // If the source row was already moved, update the existing move
-    auto it = m_move_mapping.find(old_ndx);
-    if (it != m_move_mapping.end() && it->first == old_ndx) {
-        m_move_mapping[new_ndx] = it->second;
-        m_move_mapping.erase(it);
-    }
-    // otherwise add a new move unless it was a new insertion
-    else if (!insertions.contains(old_ndx)) {
-        m_move_mapping[new_ndx] = deletions.shift(insertions.unshift(old_ndx));
-    }
-
-    verify();
-}
-
 void CollectionChangeBuilder::verify()
 {
 #ifdef REALM_DEBUG
@@ -486,32 +280,14 @@ void CollectionChangeBuilder::verify()
 #endif
 }
 
-void CollectionChangeBuilder::insert_column(size_t ndx)
-{
-    if (ndx < columns.size())
-        columns.insert(columns.begin() + ndx, IndexSet{});
-}
-
-void CollectionChangeBuilder::move_column(size_t from, size_t to)
-{
-    if (from >= columns.size() && to >= columns.size())
-        return;
-    if (from >= columns.size() || to >= columns.size())
-        columns.resize(std::max(from, to) + 1);
-    if (from < to)
-        std::rotate(begin(columns) + from, begin(columns) + from + 1, begin(columns) + to + 1);
-    else
-        std::rotate(begin(columns) + to, begin(columns) + from, begin(columns) + from + 1);
-}
-
 namespace {
 struct RowInfo {
-    size_t row_index;
+    int64_t key;
     size_t prev_tv_index;
     size_t tv_index;
-    size_t shifted_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
@@ -522,10 +298,10 @@ struct RowInfo {
 // 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 when the only row moves are due to move_last_over().
-void calculate_moves_unsorted(std::vector<RowInfo>& new_rows, IndexSet& removed,
-                              IndexSet const& move_candidates,
-                              CollectionChangeSet& changeset)
+// 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.
@@ -543,7 +319,7 @@ void calculate_moves_unsorted(std::vector<RowInfo>& new_rows, IndexSet& removed,
         // 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.row_index)) {
+        if (!move_candidates.contains(row.key)) {
             expected = row.shifted_tv_index + 1;
             continue;
         }
@@ -564,12 +340,13 @@ void calculate_moves_unsorted(std::vector<RowInfo>& new_rows, IndexSet& removed,
         removed.add(row.prev_tv_index);
     }
 }
+#endif
 
 class LongestCommonSubsequenceCalculator {
 public:
-    // A pair of an index in the table and an index in the table view
+    // A pair of an object key and an index in the table view
     struct Row {
-        size_t row_index;
+        int64_t key;
         size_t tv_index;
     };
 
@@ -600,7 +377,7 @@ private:
     IndexSet const& m_modified;
 
     // The two arrays of rows being diffed
-    // a is sorted by tv_index, b is sorted by row_index
+    // 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)
@@ -635,14 +412,14 @@ private:
         // Iterate over each `j` which has the same row index as a[i] and falls
         // within the range begin2 <= j < end2
         auto for_each_b_match = [&](size_t i, auto&& f) {
-            size_t ai = a[i].row_index;
+            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.row_index < rgt; });
-            REALM_ASSERT(it != end(b) && it->row_index == ai);
-            for (; it != end(b) && it->row_index == ai; ++it) {
+                                  [](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;
@@ -710,18 +487,17 @@ void calculate_moves_sorted(std::vector<RowInfo>& rows, CollectionChangeSet& cha
     std::vector<LongestCommonSubsequenceCalculator::Row> a, b;
 
     a.reserve(rows.size());
-    for (auto& row : rows) {
-        a.push_back({row.row_index, row.prev_tv_index});
-    }
+    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.row_index) < std::tie(rgt.tv_index, rgt.row_index);
+        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].row_index != rows[i].row_index) {
+        if (a[i].key != rows[i].key) {
             first_difference = i;
             break;
         }
@@ -729,12 +505,12 @@ void calculate_moves_sorted(std::vector<RowInfo>& rows, CollectionChangeSet& cha
     if (first_difference == IndexSet::npos)
         return;
 
-    // Note that `b` is sorted by row_index, while `a` is sorted by tv_index
+    // 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].row_index, i});
+        b.push_back({rows[i].key, i});
     std::sort(begin(b), end(b), [](auto lft, auto rgt) {
-        return std::tie(lft.row_index, lft.tv_index) < std::tie(rgt.row_index, rgt.tv_index);
+        return std::tie(lft.key, lft.tv_index) < std::tie(rgt.key, rgt.tv_index);
     });
 
     // Calculate the LCS of the two sequences
@@ -753,61 +529,51 @@ void calculate_moves_sorted(std::vector<RowInfo>& rows, CollectionChangeSet& cha
     }
 }
 
-} // Anonymous namespace
-
-CollectionChangeBuilder CollectionChangeBuilder::calculate(std::vector<size_t> const& prev_rows,
-                                                           std::vector<size_t> const& next_rows,
-                                                           std::function<bool (size_t)> row_did_change,
-                                                           util::Optional<IndexSet> const& move_candidates)
+template<typename T>
+void verify_changeset(std::vector<T> const& prev_rows,
+                      std::vector<T> const& next_rows,
+                      CollectionChangeBuilder const& changeset)
 {
-    REALM_ASSERT_DEBUG(!move_candidates || std::is_sorted(begin(next_rows), end(next_rows)));
-
-    CollectionChangeBuilder ret;
+#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);
+        }
 
-    size_t deleted = 0;
-    std::vector<RowInfo> old_rows;
-    old_rows.reserve(prev_rows.size());
-    for (size_t i = 0; i < prev_rows.size(); ++i) {
-        if (prev_rows[i] == IndexSet::npos) {
-            ++deleted;
-            ret.deletions.add(i);
+        for (auto i : changeset.insertions.as_indexes()) {
+            rows.insert(rows.begin() + i, next_rows[i]);
         }
-        else
-            old_rows.push_back({prev_rows[i], IndexSet::npos, i, i - deleted});
-    }
-    std::sort(begin(old_rows), end(old_rows), [](auto& lft, auto& rgt) {
-        return lft.row_index < rgt.row_index;
-    });
 
-    std::vector<RowInfo> new_rows;
-    new_rows.reserve(next_rows.size());
-    for (size_t i = 0; i < next_rows.size(); ++i) {
-        new_rows.push_back({next_rows[i], IndexSet::npos, i, 0});
+        REALM_ASSERT(rows == next_rows);
     }
-    std::sort(begin(new_rows), end(new_rows), [](auto& lft, auto& rgt) {
-        return lft.row_index < rgt.row_index;
-    });
-
-    // Don't add rows which were modified to not match the query to `deletions`
-    // immediately because the unsorted move logic needs to be able to
-    // distinguish them from rows which were outright deleted
-    IndexSet removed;
+#else
+    static_cast<void>(prev_rows);
+    static_cast<void>(next_rows);
+    static_cast<void>(changeset);
+#endif
+}
 
-    // Now that our old and new sets of rows are sorted by row index, we can
+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.row_index == new_index.row_index) {
-            new_rows[j].prev_tv_index = old_rows[i].tv_index;
-            new_rows[j].shifted_tv_index = old_rows[i].shifted_tv_index;
+        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.row_index < new_index.row_index) {
-            removed.add(old_index.tv_index);
+        else if (old_index.key < new_index.key) {
+            ret.deletions.add(old_index.tv_index);
             ++i;
         }
         else {
@@ -817,7 +583,7 @@ CollectionChangeBuilder CollectionChangeBuilder::calculate(std::vector<size_t> c
     }
 
     for (; i < old_rows.size(); ++i)
-        removed.add(old_rows[i].tv_index);
+        ret.deletions.add(old_rows[i].tv_index);
     for (; j < new_rows.size(); ++j)
         ret.insertions.add(new_rows[j].tv_index);
 
@@ -830,37 +596,57 @@ CollectionChangeBuilder CollectionChangeBuilder::calculate(std::vector<size_t> c
               [](auto& lft, auto& rgt) { return lft.tv_index < rgt.tv_index; });
 
     for (auto& row : new_rows) {
-        if (row_did_change(row.row_index)) {
+        if (key_did_change(row.key)) {
             ret.modifications.add(row.tv_index);
         }
     }
 
-    if (move_candidates) {
-        calculate_moves_unsorted(new_rows, removed, *move_candidates, ret);
-    }
-    else {
+    if (!in_table_order)
         calculate_moves_sorted(new_rows, ret);
-    }
-    ret.deletions.add(removed);
-    ret.verify();
+}
 
-#ifdef REALM_DEBUG
-    { // Verify that applying the calculated change to prev_rows actually produces next_rows
-        auto rows = prev_rows;
-        auto it = util::make_reverse_iterator(ret.deletions.end());
-        auto end = util::make_reverse_iterator(ret.deletions.begin());
-        for (; it != end; ++it) {
-            rows.erase(rows.begin() + it->first, rows.begin() + it->second);
-        }
+} // Anonymous namespace
 
-        for (auto i : ret.insertions.as_indexes()) {
-            rows.insert(rows.begin() + i, next_rows[i]);
-        }
+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)
+{
 
-        REALM_ASSERT(rows == next_rows);
-    }
-#endif
+    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;
 }
 

+ 10 - 21
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/collection_change_builder.hpp

@@ -21,12 +21,15 @@
 
 #include "collection_notifications.hpp"
 
-#include <realm/util/optional.hpp>
+#include <realm/keys.hpp>
 
-#include <unordered_map>
+#include <functional>
+#include <unordered_set>
+#include <vector>
 
 namespace realm {
 namespace _impl {
+
 class CollectionChangeBuilder : public CollectionChangeSet {
 public:
     CollectionChangeBuilder(CollectionChangeBuilder const&) = default;
@@ -41,13 +44,13 @@ public:
 
     // 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
-    // If `move_candidates` is supplied they it will be used to do more accurate
-    // determination of which rows moved. This is only supported when the rows
-    // are in table order (i.e. not sorted or from a LinkList)
+    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 (size_t)> row_did_change,
-                                             util::Optional<IndexSet> const& move_candidates = util::none);
+                                             std::function<bool (int64_t)> key_did_change);
 
     // generic operations {
     CollectionChangeSet finalize() &&;
@@ -64,21 +67,7 @@ public:
     void move(size_t from, size_t to);
     // }
 
-    // operations only implemented for Row semantics {
-    void move_over(size_t ndx, size_t last_ndx, bool track_moves=true);
-    // must be followed by move_over(old_ndx, ...)
-    // precondition: `new_ndx` must be a new insertion
-    void subsume(size_t old_ndx, size_t new_ndx, bool track_moves=true);
-    void swap(size_t ndx_1, size_t ndx_2, bool track_moves=true);
-
-    void parse_complete();
-    // }
-
-    void insert_column(size_t ndx);
-    void move_column(size_t from, size_t to);
-
 private:
-    std::unordered_map<size_t, size_t> m_move_mapping;
     bool m_track_columns = true;
 
     template<typename Func>

+ 105 - 102
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/collection_notifier.cpp

@@ -21,15 +21,34 @@
 #include "impl/realm_coordinator.hpp"
 #include "shared_realm.hpp"
 
-#include <realm/group_shared.hpp>
-#include <realm/link_view.hpp>
+#include <realm/db.hpp>
 
 using namespace realm;
 using namespace realm::_impl;
 
-std::function<bool (size_t)>
+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,
-                                             Table const& root_table)
+                                             ConstTableRef root_table)
 {
     if (info.schema_changed)
         set_table(root_table);
@@ -38,26 +57,24 @@ CollectionNotifier::get_modification_checker(TransactionChangeInfo const& info,
     // actually modified. This can be false if there were only insertions, or
     // deletions which were not linked to by any row in the linking table
     auto table_modified = [&](auto& tbl) {
-        return tbl.table_ndx < info.tables.size()
-            && !info.tables[tbl.table_ndx].modifications.empty();
+        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 [](size_t) { return false; };
+        return [](ObjectChangeSet::ObjectKeyType) { return false; };
     }
     if (m_related_tables.size() == 1) {
-        auto& modifications = info.tables[m_related_tables[0].table_ndx].modifications;
-        return [&](size_t row) { return modifications.contains(row); };
+        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);
+    return DeepChangeChecker(info, *root_table, m_related_tables);
 }
 
 void DeepChangeChecker::find_related_tables(std::vector<RelatedTable>& out, Table const& table)
 {
-    auto table_ndx = table.get_index_in_group();
-    if (table_ndx == npos)
-        return;
-    if (any_of(begin(out), end(out), [=](auto& tbl) { return tbl.table_ndx == table_ndx; }))
+    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
@@ -65,13 +82,13 @@ void DeepChangeChecker::find_related_tables(std::vector<RelatedTable>& out, Tabl
     // because the recursive calls may resize `out`, so instead look it up by
     // index every time
     size_t out_index = out.size();
-    out.push_back({table_ndx, {}});
+    out.push_back({table_key, {}});
 
-    for (size_t i = 0, count = table.get_column_count(); i != count; ++i) {
-        auto type = table.get_column_type(i);
+    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({i, type == type_LinkList});
-            find_related_tables(out, *table.get_link_target(i));
+            out[out_index].links.push_back({col_key.value, type == type_LinkList});
+            find_related_tables(out, *table.get_link_target(col_key));
         }
     }
 }
@@ -81,60 +98,61 @@ DeepChangeChecker::DeepChangeChecker(TransactionChangeInfo const& info,
                                      std::vector<RelatedTable> const& related_tables)
 : m_info(info)
 , m_root_table(root_table)
-, m_root_table_ndx(root_table.get_index_in_group())
-, m_root_modifications(m_root_table_ndx < info.tables.size() ? &info.tables[m_root_table_ndx].modifications : nullptr)
+, m_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(size_t table_ndx,
-                                             Table const& table,
-                                             size_t row_ndx, size_t depth)
+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_ndx == table_ndx; });
+                      [&](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 = [&](size_t col) {
+    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.table == table_ndx && p.row == row_ndx && p.col == col;
+            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] = {table_ndx, row_ndx, col, false};
+        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_ndx))
+        if (already_checking(link.col_key))
             return false;
         if (!link.is_list) {
-            if (table.is_null_link(link.col_ndx, row_ndx))
+            if (obj.is_null(ColKey(link.col_key)))
                 return false;
-            auto dst = table.get_link(link.col_ndx, row_ndx);
-            return check_row(*table.get_link_target(link.col_ndx), dst, depth + 1);
+            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(link.col_ndx);
-        auto lvr = table.get_linklist(link.col_ndx, row_ndx);
-        for (size_t j = 0, size = lvr->size(); j < size; ++j) {
-            size_t dst = lvr->get(j).get_index();
-            if (check_row(target, dst, depth + 1))
-                return true;
-        }
-        return false;
+        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, size_t idx, size_t depth)
+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()) {
@@ -145,31 +163,33 @@ bool DeepChangeChecker::check_row(Table const& table, size_t idx, size_t depth)
         return false;
     }
 
-    size_t table_ndx = table.get_index_in_group();
-    if (depth > 0 && table_ndx < m_info.tables.size() && m_info.tables[table_ndx].modifications.contains(idx))
-        return true;
-
-    if (m_not_modified.size() <= table_ndx)
-        m_not_modified.resize(table_ndx + 1);
-    if (m_not_modified[table_ndx].contains(idx))
+    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_ndx, table, idx, depth);
+    bool ret = check_outgoing_links(table_key, table, key, depth);
     if (!ret && (depth == 0 || !m_current_path[depth - 1].depth_exceeded))
-        m_not_modified[table_ndx].add(idx);
+        not_modified.insert(key);
     return ret;
 }
 
-bool DeepChangeChecker::operator()(size_t ndx)
+bool DeepChangeChecker::operator()(ObjKeyType key)
 {
-    if (m_root_modifications && m_root_modifications->contains(ndx))
+    if (m_root_object_changes && m_root_object_changes->modifications_contains(key))
         return true;
-    return check_row(m_root_table, ndx, 0);
+    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_shared_group(*m_realm)->get_version_of_current_transaction())
+, m_sg_version(Realm::Internal::get_transaction(*m_realm).get_version_of_current_transaction())
 {
 }
 
@@ -180,11 +200,16 @@ CollectionNotifier::~CollectionNotifier()
     unregister();
 }
 
+void CollectionNotifier::release_data() noexcept
+{
+    m_sg = nullptr;
+}
+
 uint64_t CollectionNotifier::add_callback(CollectionChangeCallback callback)
 {
     m_realm->verify_thread();
 
-    std::lock_guard<std::mutex> lock(m_callback_mutex);
+    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
@@ -200,7 +225,7 @@ void CollectionNotifier::remove_callback(uint64_t token)
     // it could cause user code to be called
     Callback old;
     {
-        std::lock_guard<std::mutex> lock(m_callback_mutex);
+        util::CheckedLockGuard lock(m_callback_mutex);
         auto it = find_callback(token);
         if (it == end(m_callbacks)) {
             return;
@@ -229,7 +254,7 @@ void CollectionNotifier::suppress_next_notification(uint64_t token)
         m_realm->verify_in_write();
     }
 
-    std::lock_guard<std::mutex> lock(m_callback_mutex);
+    util::CheckedLockGuard lock(m_callback_mutex);
     auto it = find_callback(token);
     if (it != end(m_callbacks)) {
         it->skip_next = true;
@@ -264,10 +289,10 @@ std::unique_lock<std::mutex> CollectionNotifier::lock_target()
     return std::unique_lock<std::mutex>{m_realm_mutex};
 }
 
-void CollectionNotifier::set_table(Table const& table)
+void CollectionNotifier::set_table(ConstTableRef table)
 {
     m_related_tables.clear();
-    DeepChangeChecker::find_related_tables(m_related_tables, table);
+    DeepChangeChecker::find_related_tables(m_related_tables, *table);
 }
 
 void CollectionNotifier::add_required_change_info(TransactionChangeInfo& info)
@@ -276,14 +301,9 @@ void CollectionNotifier::add_required_change_info(TransactionChangeInfo& info)
         return;
     }
 
-    auto max = max_element(begin(m_related_tables), end(m_related_tables),
-                           [](auto&& a, auto&& b) { return a.table_ndx < b.table_ndx; });
-
-    if (max->table_ndx >= info.table_modifications_needed.size())
-        info.table_modifications_needed.resize(max->table_ndx + 1, false);
-    for (auto& tbl : m_related_tables) {
-        info.table_modifications_needed[tbl.table_ndx] = true;
-    }
+    info.tables.reserve(m_related_tables.size());
+    for (auto& tbl : m_related_tables)
+        info.tables[tbl.table_key.value];
 }
 
 void CollectionNotifier::prepare_handover()
@@ -291,10 +311,12 @@ 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
-    std::lock_guard<std::mutex> lock(m_callback_mutex);
+    util::CheckedLockGuard lock(m_callback_mutex);
     for (auto& callback : m_callbacks)
         REALM_ASSERT(!callback.skip_next);
 #endif
@@ -311,7 +333,7 @@ void CollectionNotifier::before_advance()
         // 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();
+        lock.unlock_unchecked();
         cb.before(changes);
     });
 }
@@ -328,7 +350,7 @@ void CollectionNotifier::after_advance()
         // 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();
+        lock.unlock_unchecked();
         cb.after(changes);
     });
 }
@@ -344,7 +366,7 @@ void CollectionNotifier::deliver_error(std::exception_ptr error)
         // callback from within it can't result in a dangling pointer
         auto cb = std::move(callback.fn);
         auto token = callback.token;
-        lock.unlock();
+        lock.unlock_unchecked();
         cb.error(error);
 
         // We never want to call the callback again after this, so just remove it
@@ -362,7 +384,7 @@ bool CollectionNotifier::package_for_delivery()
 {
     if (!prepare_to_deliver())
         return false;
-    std::lock_guard<std::mutex> l(m_callback_mutex);
+    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();
@@ -372,40 +394,31 @@ bool CollectionNotifier::package_for_delivery()
 template<typename Fn>
 void CollectionNotifier::for_each_callback(Fn&& fn)
 {
-    std::unique_lock<std::mutex> callback_lock(m_callback_mutex);
+    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();
+            callback_lock.lock_unchecked();
     }
 
     m_callback_index = npos;
 }
 
-void CollectionNotifier::attach_to(SharedGroup& sg)
-{
-    REALM_ASSERT(!m_sg);
-
-    m_sg = &sg;
-    do_attach_to(sg);
-}
-
-void CollectionNotifier::detach()
+void CollectionNotifier::attach_to(std::shared_ptr<Transaction> sg)
 {
-    REALM_ASSERT(m_sg);
-    do_detach_from(*m_sg);
-    m_sg = nullptr;
+    do_attach_to(*sg);
+    m_sg = std::move(sg);
 }
 
-SharedGroup& CollectionNotifier::source_shared_group()
+Transaction& CollectionNotifier::source_shared_group()
 {
-    return *Realm::Internal::get_shared_group(*m_realm);
+    return Realm::Internal::get_transaction(*m_realm);
 }
 
 void CollectionNotifier::add_changes(CollectionChangeBuilder change)
 {
-    std::lock_guard<std::mutex> lock(m_callback_mutex);
+    util::CheckedLockGuard lock(m_callback_mutex);
     for (auto& callback : m_callbacks) {
         if (callback.skip_next) {
             REALM_ASSERT_DEBUG(callback.accumulated_changes.empty());
@@ -429,7 +442,8 @@ NotifierPackage::NotifierPackage(std::exception_ptr error,
 {
 }
 
-void NotifierPackage::package_and_wait(util::Optional<VersionID::version_type> target_version)
+// 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;
@@ -468,24 +482,13 @@ void NotifierPackage::before_advance()
         notifier->before_advance();
 }
 
-void NotifierPackage::deliver(SharedGroup& sg)
+void NotifierPackage::after_advance()
 {
     if (m_error) {
         for (auto& notifier : m_notifiers)
             notifier->deliver_error(m_error);
         return;
     }
-    // Can't deliver while in a write transaction
-    if (sg.get_transact_stage() != SharedGroup::transact_Reading)
-        return;
-    for (auto& notifier : m_notifiers)
-        notifier->deliver(sg);
-}
-
-void NotifierPackage::after_advance()
-{
-    if (m_error)
-        return;
     for (auto& notifier : m_notifiers)
         notifier->after_advance();
 }

+ 53 - 54
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/collection_notifier.hpp

@@ -19,10 +19,14 @@
 #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>
@@ -30,29 +34,29 @@
 #include <functional>
 #include <mutex>
 #include <unordered_map>
+#include <unordered_set>
 
 namespace realm {
 class Realm;
-class SharedGroup;
-class Table;
+class Transaction;
 
 namespace _impl {
 class RealmCoordinator;
 
 struct ListChangeInfo {
-    size_t table_ndx;
-    size_t row_ndx;
-    size_t col_ndx;
+    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<bool> table_modifications_needed;
-    std::vector<bool> table_moves_needed;
     std::vector<ListChangeInfo> lists;
-    std::vector<CollectionChangeBuilder> tables;
-    std::vector<std::vector<size_t>> column_indices;
-    std::vector<size_t> table_indices;
+    std::unordered_map<TableKeyType, ObjectChangeSet> tables;
     bool track_all;
     bool schema_changed;
 };
@@ -60,18 +64,18 @@ struct TransactionChangeInfo {
 class DeepChangeChecker {
 public:
     struct OutgoingLink {
-        size_t col_ndx;
+        int64_t col_key;
         bool is_list;
     };
     struct RelatedTable {
-        size_t table_ndx;
+        TableKey table_key;
         std::vector<OutgoingLink> links;
     };
 
     DeepChangeChecker(TransactionChangeInfo const& info, Table const& root_table,
                       std::vector<RelatedTable> const& related_tables);
 
-    bool operator()(size_t row_ndx);
+    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
@@ -80,22 +84,21 @@ public:
 private:
     TransactionChangeInfo const& m_info;
     Table const& m_root_table;
-    const size_t m_root_table_ndx;
-    IndexSet const* const m_root_modifications;
-    std::vector<IndexSet> m_not_modified;
+    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 {
-        size_t table;
-        size_t row;
-        size_t col;
+        int64_t obj_key;
+        int64_t col_key;
         bool depth_exceeded;
     };
     std::array<Path, 4> m_current_path;
 
-    bool check_row(Table const& table, size_t row_ndx, size_t depth = 0);
-    bool check_outgoing_links(size_t table_ndx, Table const& table,
-                              size_t row_ndx, size_t depth = 0);
+    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
@@ -118,13 +121,13 @@ public:
     // 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);
+    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);
+    void remove_callback(uint64_t token) REQUIRES(!m_callback_mutex);
 
-    void suppress_next_notification(uint64_t token);
+    void suppress_next_notification(uint64_t token) REQUIRES(!m_callback_mutex);
 
     // ------------------------------------------------------------------------
     // API for RealmCoordinator to manage running things and calling callbacks
@@ -132,7 +135,7 @@ public:
     bool is_for_realm(Realm&) const noexcept;
     Realm* get_realm() const noexcept { return m_realm.get(); }
 
-    // Get the SharedGroup version which this collection can attach to (if it's
+    // 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; }
@@ -141,38 +144,30 @@ public:
     // This is called on the worker thread to ensure that non-thread-safe things
     // can be destroyed on the correct thread, even if the last reference to the
     // CollectionNotifier is released on a different thread
-    virtual void release_data() noexcept = 0;
+    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();
-
-    // Deliver the new state to the target collection using the given SharedGroup
-    // precondition: RealmCoordinator::m_notifier_mutex is unlocked
-    virtual void deliver(SharedGroup&) { }
+    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);
+    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();
-    void after_advance();
+    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 SharedGroup.
-    // precondition: RealmCoordinator::m_notifier_mutex is locked
-    void attach_to(SharedGroup& sg);
-    // Create a new query handover object and stop using the previously attached
-    // SharedGroup
+    // Attach the handed-over query to `sg`. Must not be already attached to a Transaction.
     // precondition: RealmCoordinator::m_notifier_mutex is locked
-    void detach();
+    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
@@ -183,24 +178,27 @@ public:
     virtual void run() = 0;
 
     // precondition: RealmCoordinator::m_notifier_mutex is locked
-    void prepare_handover();
+    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);
-    void set_table(Table const& table);
+    void add_changes(CollectionChangeBuilder change) REQUIRES(!m_callback_mutex);
+    void set_table(ConstTableRef table);
     std::unique_lock<std::mutex> lock_target();
-    SharedGroup& source_shared_group();
+    Transaction& source_shared_group();
+
+    bool all_related_tables_covered(const TableVersions& versions);
+    std::function<bool (ObjectChangeSet::ObjectKeyType)> get_modification_checker(TransactionChangeInfo const&, ConstTableRef);
 
-    std::function<bool (size_t)> get_modification_checker(TransactionChangeInfo const&, Table const&);
+    // The actual change, calculated in run() and delivered in prepare_handover()
+    CollectionChangeBuilder m_change;
 
 private:
-    virtual void do_attach_to(SharedGroup&) = 0;
-    virtual void do_detach_from(SharedGroup&) = 0;
-    virtual void do_prepare_handover(SharedGroup&) = 0;
+    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; }
 
@@ -208,7 +206,7 @@ private:
     std::shared_ptr<Realm> m_realm;
 
     VersionID m_sg_version;
-    SharedGroup* m_sg = nullptr;
+    std::shared_ptr<Transaction> m_sg;
 
     bool m_has_run = false;
     bool m_error = false;
@@ -225,7 +223,7 @@ private:
 
     // Currently registered callbacks and a mutex which must always be held
     // while doing anything with them or m_callback_index
-    std::mutex m_callback_mutex;
+    util::CheckedMutex m_callback_mutex;
     std::vector<Callback> m_callbacks;
 
     // Cached value for if m_callbacks is empty, needed to avoid deadlocks in
@@ -246,7 +244,7 @@ private:
     uint64_t m_next_token = 0;
 
     template<typename Fn>
-    void for_each_callback(Fn&& fn);
+    void for_each_callback(Fn&& fn) REQUIRES(!m_callback_mutex);
 
     std::vector<Callback>::iterator find_callback(uint64_t token);
 };
@@ -279,7 +277,8 @@ public:
         return *this;
     }
 
-    Handle& operator=(std::shared_ptr<T>&& other)
+    template<typename U>
+    Handle& operator=(std::shared_ptr<U>&& other)
     {
         reset();
         std::shared_ptr<T>::operator=(std::move(other));
@@ -318,7 +317,7 @@ public:
     // Send the before-change notifications
     void before_advance();
     // Deliver the payload associated with the contained notifiers and/or the error
-    void deliver(SharedGroup& sg);
+    void deliver(Transaction& sg);
     // Send the after-change notifications
     void after_advance();
 

+ 3 - 3
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/epoll/external_commit_helper.cpp

@@ -21,7 +21,7 @@
 #include <realm/util/fifo_helper.hpp>
 
 #include <realm/util/assert.hpp>
-#include <realm/group_shared_options.hpp>
+#include <realm/db.hpp>
 
 #include <algorithm>
 #include <errno.h>
@@ -115,7 +115,7 @@ ExternalCommitHelper::ExternalCommitHelper(RealmCoordinator& 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(SharedGroupOptions::get_sys_tmp_dir());
+    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
@@ -126,7 +126,7 @@ ExternalCommitHelper::ExternalCommitHelper(RealmCoordinator& parent)
     // 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 `SharedGroupOptions::set_sys_tmp_dir()`
+    //  3) A location defined by `DBOptions::set_sys_tmp_dir()`
     //
     // Core has a similar policy for its named pipes.
     //

+ 2 - 2
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/generic/external_commit_helper.cpp

@@ -29,8 +29,8 @@ 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, SharedGroupOptions(parent.is_in_memory() ? SharedGroupOptions::Durability::MemOnly
-                                                            : SharedGroupOptions::Durability::Full,
+, 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();

+ 1 - 1
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/generic/external_commit_helper.hpp

@@ -39,7 +39,7 @@ private:
 
     // A shared group used to listen for changes
     std::unique_ptr<Replication> m_history;
-    SharedGroup m_sg;
+    Transaction m_sg;
 
     // The listener thread
     std::future<void> m_thread;

+ 42 - 47
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/list_notifier.cpp

@@ -18,53 +18,50 @@
 
 #include "impl/list_notifier.hpp"
 
-#include "shared_realm.hpp"
+#include "list.hpp"
 
-#include <realm/link_view.hpp>
+#include <realm/db.hpp>
+#include <realm/group.hpp>
 
 using namespace realm;
 using namespace realm::_impl;
 
-ListNotifier::ListNotifier(LinkViewRef lv, std::shared_ptr<Realm> realm)
+ListNotifier::ListNotifier(std::shared_ptr<Realm> realm, LstBase const& list,
+                           PropertyType type)
 : CollectionNotifier(std::move(realm))
-, m_prev_size(lv->size())
+, 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())
 {
-    set_table(lv->get_target_table());
-    m_lv_handover = source_shared_group().export_linkview_for_handover(lv);
+    if (m_type == PropertyType::Object) {
+        set_table(static_cast<const LnkLst&>(list).get_target_table());
+    }
 }
 
 void ListNotifier::release_data() noexcept
 {
-    m_lv.reset();
-}
-
-void ListNotifier::do_attach_to(SharedGroup& sg)
-{
-    REALM_ASSERT(m_lv_handover);
-    REALM_ASSERT(!m_lv);
-    m_lv = sg.import_linkview_from_handover(std::move(m_lv_handover));
+    m_list = {};
+    CollectionNotifier::release_data();
 }
 
-void ListNotifier::do_detach_from(SharedGroup& sg)
+void ListNotifier::do_attach_to(Transaction& sg)
 {
-    REALM_ASSERT(!m_lv_handover);
-    if (m_lv) {
-        m_lv_handover = sg.export_linkview_for_handover(m_lv);
-        m_lv = {};
+    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)
 {
-    REALM_ASSERT(!m_lv_handover);
-    if (!m_lv || !m_lv->is_attached()) {
+    if (!m_list->is_attached())
         return false; // origin row was deleted after the notification was added
-    }
 
-    auto& table = m_lv->get_origin_table();
-    size_t row_ndx = m_lv->get_origin_row_index();
-    size_t col_ndx = find_container_column(table, row_ndx, m_lv, type_LinkList, &Table::get_linklist);
-    info.lists.push_back({table.get_index_in_group(), row_ndx, col_ndx, &m_change});
+    info.lists.push_back({m_table, m_obj.value, m_col.value, &m_change});
 
     m_info = &info;
     return true;
@@ -72,8 +69,8 @@ bool ListNotifier::do_add_required_change_info(TransactionChangeInfo& info)
 
 void ListNotifier::run()
 {
-    if (!m_lv || !m_lv->is_attached()) {
-        // LV was deleted, so report all of the rows being removed if this is
+    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);
@@ -85,25 +82,23 @@ void ListNotifier::run()
         return;
     }
 
-    auto row_did_change = get_modification_checker(*m_info, m_lv->get_target_table());
-    for (size_t i = 0; i < m_lv->size(); ++i) {
-        if (m_change.modifications.contains(i))
-            continue;
-        if (row_did_change(m_lv->get(i).get_index()))
-            m_change.modifications.add(i);
-    }
-
-    for (auto const& move : m_change.moves) {
-        if (m_change.modifications.contains(move.to))
-            continue;
-        if (row_did_change(m_lv->get(move.to).get_index()))
-            m_change.modifications.add(move.to);
-    }
+    m_prev_size = m_list->size();
 
-    m_prev_size = m_lv->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);
+        }
 
-void ListNotifier::do_prepare_handover(SharedGroup&)
-{
-    add_changes(std::move(m_change));
+        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);
+        }
+    }
 }

+ 11 - 12
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/list_notifier.hpp

@@ -21,34 +21,33 @@
 
 #include "impl/collection_notifier.hpp"
 
-#include <realm/group_shared.hpp>
+#include "property.hpp"
+
+#include <realm/list.hpp>
 
 namespace realm {
 namespace _impl {
 class ListNotifier : public CollectionNotifier {
 public:
-    ListNotifier(LinkViewRef lv, std::shared_ptr<Realm> realm);
+    ListNotifier(std::shared_ptr<Realm> realm, LstBase const& list, PropertyType type);
 
 private:
-    // The linkview, in handover form if this has not been attached to the main
-    // SharedGroup yet
-    LinkViewRef m_lv;
-    std::unique_ptr<SharedGroup::Handover<LinkView>> m_lv_handover;
+    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;
 
-    // The actual change, calculated in run() and delivered in prepare_handover()
-    CollectionChangeBuilder m_change;
     TransactionChangeInfo* m_info;
 
     void run() override;
 
-    void do_prepare_handover(SharedGroup&) override;
-
-    void do_attach_to(SharedGroup& sg) override;
-    void do_detach_from(SharedGroup& sg) override;
+    void do_attach_to(Transaction& sg) override;
 
     void release_data() noexcept override;
     bool do_add_required_change_info(TransactionChangeInfo& info) override;

+ 20 - 20
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/object_accessor_impl.hpp

@@ -105,7 +105,7 @@ public:
     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(RowExpr) const;
+    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
@@ -113,12 +113,12 @@ public:
     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 `RowExpr` and optional
+    // 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<RowExpr>`. If
+    // `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 row expr, while if it's
+    // 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.
@@ -127,7 +127,7 @@ public:
     // 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, size_t /*current_row*/ = realm::npos) const { return any_cast<T>(v); }
+    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 {}; }
@@ -152,14 +152,14 @@ private:
 
 };
 
-inline util::Any CppContext::box(RowExpr row) const
+inline util::Any CppContext::box(Obj obj) const
 {
     REALM_ASSERT(object_schema);
-    return Object(realm, *object_schema, row);
+    return Object(realm, *object_schema, obj);
 }
 
 template<>
-inline StringData CppContext::unbox(util::Any& v, CreatePolicy, size_t) const
+inline StringData CppContext::unbox(util::Any& v, CreatePolicy, ObjKey) const
 {
     if (!v.has_value())
         return StringData();
@@ -168,7 +168,7 @@ inline StringData CppContext::unbox(util::Any& v, CreatePolicy, size_t) const
 }
 
 template<>
-inline BinaryData CppContext::unbox(util::Any& v, CreatePolicy, size_t) const
+inline BinaryData CppContext::unbox(util::Any& v, CreatePolicy, ObjKey) const
 {
     if (!v.has_value())
         return BinaryData();
@@ -177,45 +177,45 @@ inline BinaryData CppContext::unbox(util::Any& v, CreatePolicy, size_t) const
 }
 
 template<>
-inline RowExpr CppContext::unbox(util::Any& v, CreatePolicy policy, size_t current_row) const
+inline Obj CppContext::unbox(util::Any& v, CreatePolicy policy, ObjKey current_obj) const
 {
     if (auto object = any_cast<Object>(&v))
-        return object->row();
-    if (auto row = any_cast<RowExpr>(&v))
-        return *row;
+        return object->obj();
+    if (auto obj = any_cast<Obj>(&v))
+        return *obj;
     if (policy == CreatePolicy::Skip)
-        return RowExpr();
+        return Obj();
 
     REALM_ASSERT(object_schema);
-    return Object::create(const_cast<CppContext&>(*this), realm, *object_schema, v, policy, current_row).row();
+    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, size_t) const
+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, size_t) const
+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, size_t) const
+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, size_t) const
+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, size_t) const
+inline Mixed CppContext::unbox(util::Any&, CreatePolicy, ObjKey) const
 {
     throw std::logic_error("'Any' type is unsupported");
 }

+ 18 - 51
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/object_notifier.cpp

@@ -23,75 +23,42 @@
 using namespace realm;
 using namespace realm::_impl;
 
-ObjectNotifier::ObjectNotifier(Row const& row, std::shared_ptr<Realm> realm)
+ObjectNotifier::ObjectNotifier(std::shared_ptr<Realm> realm, TableKey table, ObjKey obj)
 : CollectionNotifier(std::move(realm))
+, m_table(table)
+, m_obj(obj)
 {
-    REALM_ASSERT(row.get_table());
-    set_table(*row.get_table());
-
-    m_handover = source_shared_group().export_for_handover(row);
-}
-
-void ObjectNotifier::release_data() noexcept
-{
-    m_row = nullptr;
-}
-
-void ObjectNotifier::do_attach_to(SharedGroup& sg)
-{
-    REALM_ASSERT(m_handover);
-    REALM_ASSERT(!m_row);
-    m_row = sg.import_from_handover(std::move(m_handover));
-}
-
-void ObjectNotifier::do_detach_from(SharedGroup& sg)
-{
-    REALM_ASSERT(!m_handover);
-    if (m_row) {
-        m_handover = sg.export_for_handover(*m_row);
-        m_row = nullptr;
-    }
 }
 
 bool ObjectNotifier::do_add_required_change_info(TransactionChangeInfo& info)
 {
-    REALM_ASSERT(!m_handover);
     m_info = &info;
-    if (m_row && m_row->is_attached()) {
-        size_t table_ndx = m_row->get_table()->get_index_in_group();
-        if (table_ndx >= info.table_modifications_needed.size())
-            info.table_modifications_needed.resize(table_ndx + 1);
-        info.table_modifications_needed[table_ndx] = true;
-    }
+    info.tables[m_table.value];
     return false;
 }
 
 void ObjectNotifier::run()
 {
-    if (!m_row)
+    if (!m_table)
         return;
-    if (!m_row->is_attached()) {
+
+    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_row = nullptr;
+        m_table = {};
+        m_obj = {};
         return;
     }
 
-    size_t table_ndx = m_row->get_table()->get_index_in_group();
-    if (table_ndx >= m_info->tables.size())
-        return;
-    auto& change = m_info->tables[table_ndx];
-    if (!change.modifications.contains(m_row->get_index()))
+    auto column_modifications = change.get_columns_modified(m_obj.value);
+    if (!column_modifications)
         return;
     m_change.modifications.add(0);
-    m_change.columns.reserve(change.columns.size());
-    for (auto& col : change.columns) {
-        m_change.columns.emplace_back();
-        if (col.contains(m_row->get_index()))
-            m_change.columns.back().add(0);
+    for (auto col : *column_modifications) {
+        m_change.columns[col].add(0);
     }
 }
-
-void ObjectNotifier::do_prepare_handover(SharedGroup&)
-{
-    add_changes(std::move(m_change));
-}

+ 5 - 13
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/object_notifier.hpp

@@ -21,30 +21,22 @@
 
 #include "impl/collection_notifier.hpp"
 
-#include <realm/group_shared.hpp>
+#include <realm/keys.hpp>
 
 namespace realm {
+
 namespace _impl {
 class ObjectNotifier : public CollectionNotifier {
 public:
-    ObjectNotifier(Row const& row, std::shared_ptr<Realm> realm);
+    ObjectNotifier(std::shared_ptr<Realm> realm, TableKey table, ObjKey obj);
 
 private:
-    std::unique_ptr<Row> m_row;
-    std::unique_ptr<SharedGroup::Handover<Row>> m_handover;
-
-    // The actual change, calculated in run() and delivered in prepare_handover()
-    CollectionChangeBuilder m_change;
+    TableKey m_table;
+    ObjKey m_obj;
     TransactionChangeInfo* m_info;
 
     void run() override;
 
-    void do_prepare_handover(SharedGroup&) override;
-
-    void do_attach_to(SharedGroup& sg) override;
-    void do_detach_from(SharedGroup& sg) override;
-
-    void release_data() noexcept override;
     bool do_add_required_change_info(TransactionChangeInfo& info) override;
 };
 }

文件差异内容过多而无法显示
+ 340 - 221
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/realm_coordinator.cpp


+ 82 - 58
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/realm_coordinator.hpp

@@ -21,17 +21,20 @@
 
 #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 SharedGroup;
 class StringData;
 class SyncSession;
+class Transaction;
 
 namespace _impl {
 class CollectionNotifier;
@@ -53,40 +56,45 @@ public:
     // 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 thread-local shared Realm with the given configuration
-    // If the Realm is already open on another thread, validates that the given
-    // configuration is compatible with the existing one
-    std::shared_ptr<Realm> get_realm(Realm::Config config);
-    std::shared_ptr<Realm> get_realm();
+    // 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);
+    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);
+    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<Realm> get_unbound_realm();
-
-    // Get the existing cached Realm for the given execution context if it exists
-    std::shared_ptr<Realm> get_cached_realm(Realm::Config const&, AnyExecutionContextID);
+    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, AnyExecutionContextID);
+    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 { return m_schema_version; }
+    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
@@ -97,15 +105,15 @@ public:
 
     // 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;
+    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);
+                      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);
-    void clear_schema_cache_and_set_schema_version(uint64_t new_schema_version);
+    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
@@ -130,38 +138,50 @@ public:
 
     // Called by Realm's destructor to ensure the cache is cleaned up promptly
     // Do not call directly
-    void unregister_realm(Realm* realm);
+    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();
+    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);
+    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);
+    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);
+    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)>);
+    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);
+    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);
+    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>
-    std::unique_lock<std::mutex> wait_for_notifiers(Pred&& wait_predicate);
+    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.
@@ -171,37 +191,40 @@ public:
     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 std::mutex m_schema_cache_mutex;
-    util::Optional<Schema> m_cached_schema;
-    uint64_t m_schema_version = -1;
-    uint64_t m_schema_transaction_version_min = 0;
-    uint64_t m_schema_transaction_version_max = 0;
+    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;
 
-    std::mutex m_realm_mutex;
-    std::vector<WeakRealmNotifier> m_weak_realm_notifiers;
+    util::CheckedMutex m_realm_mutex;
+    std::vector<WeakRealmNotifier> m_weak_realm_notifiers GUARDED_BY(m_realm_mutex);
 
-    std::mutex m_notifier_mutex;
+    util::CheckedMutex m_notifier_mutex;
     std::condition_variable m_notifier_cv;
-    std::vector<std::shared_ptr<_impl::CollectionNotifier>> m_new_notifiers;
-    std::vector<std::shared_ptr<_impl::CollectionNotifier>> m_notifiers;
-    VersionID m_notifier_skip_version = {0, 0};
+    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};
 
-    // SharedGroup used for actually running async notifiers
+    // Transaction used for actually running async notifiers
     // Will have a read transaction iff m_notifiers is non-empty
-    std::unique_ptr<Replication> m_notifier_history;
-    std::unique_ptr<SharedGroup> m_notifier_sg;
+    std::shared_ptr<Transaction> m_notifier_sg;
 
-    // SharedGroup used to advance notifiers in m_new_notifiers to the main shared
+    // 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::unique_ptr<Replication> m_advancer_history;
-    std::unique_ptr<SharedGroup> m_advancer_sg;
+    std::shared_ptr<Transaction> m_advancer_sg;
     std::exception_ptr m_async_error;
 
     std::unique_ptr<_impl::ExternalCommitHelper> m_notifier;
-    std::function<void(VersionID, VersionID)> m_transaction_callback;
+    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;
@@ -210,29 +233,30 @@ private:
 
     std::shared_ptr<AuditInterface> m_audit_context;
 
-    // must be called with m_notifier_mutex locked
-    void pin_version(VersionID version);
+    void open_db();
 
-    void set_config(const Realm::Config&);
-    void create_sync_session(bool force_client_resync, bool validate_sync_history);
-    void do_get_realm(Realm::Config config, std::shared_ptr<Realm>& realm,
-                      std::unique_lock<std::mutex>& realm_lock, bool bind_to_context=true);
+    void pin_version(VersionID version) REQUIRES(m_notifier_mutex);
 
-    void run_async_notifiers();
-    void open_helper_shared_group();
+    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();
+    void clean_up_dead_notifiers() REQUIRES(m_notifier_mutex);
 
-    std::vector<std::shared_ptr<_impl::CollectionNotifier>> notifiers_for_realm(Realm&);
+    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>
-std::unique_lock<std::mutex> RealmCoordinator::wait_for_notifiers(Pred&& wait_predicate)
+util::CheckedUniqueLock RealmCoordinator::wait_for_notifiers(Pred&& wait_predicate)
 {
-    std::unique_lock<std::mutex> lock(m_notifier_mutex);
+    util::CheckedUniqueLock lock(m_notifier_mutex);
     bool first = true;
-    m_notifier_cv.wait(lock, [&] {
+    m_notifier_cv.wait(lock.native_handle(), [&] {
         if (wait_predicate())
             return true;
         if (first) {

+ 246 - 144
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/results_notifier.cpp

@@ -20,151 +20,133 @@
 
 #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)
-: CollectionNotifier(target.get_realm())
-, m_target_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())
 {
-    Query q = target.get_query();
-    set_table(*q.get_table());
-    m_query_handover = source_shared_group().export_for_handover(q, MutableSourcePayload::Move);
-    DescriptorOrdering::generate_patch(target.get_descriptor_ordering(), m_ordering_handover);
+    auto table = m_query->get_table();
+    if (table) {
+        set_table(table);
+    }
 }
 
-void ResultsNotifier::target_results_moved(Results& old_target, Results& new_target)
+void ResultsNotifier::release_data() noexcept
 {
-    auto lock = lock_target();
-
-    REALM_ASSERT(m_target_results == &old_target);
-    m_target_results = &new_target;
+    m_query = {};
+    m_run_tv = {};
+    m_handover_tv = {};
+    m_handover_transaction = {};
+    m_delivered_tv = {};
+    m_delivered_transaction = {};
+    CollectionNotifier::release_data();
 }
 
-void ResultsNotifier::release_data() noexcept
+bool ResultsNotifier::get_tableview(TableView& out)
 {
-    m_query = nullptr;
-}
+    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;
 
-// Most of the inter-thread synchronization for run(), prepare_handover(),
-// attach_to(), detach(), release_data() and deliver() is done by
-// RealmCoordinator external to this code, which has some potentially
-// non-obvious results on which members are and are not safe to use without
-// holding a lock.
-//
-// add_required_change_info(), attach_to(), detach(), run(),
-// prepare_handover(), and release_data() are all only ever called on a single
-// background worker thread. call_callbacks() and deliver() are called on the
-// target thread. Calls to prepare_handover() and deliver() are guarded by a
-// lock.
-//
-// In total, this means that the safe data flow is as follows:
-//  - add_Required_change_info(), prepare_handover(), attach_to(), detach() and
-//    release_data() can read members written by each other
-//  - deliver() can read members written to in prepare_handover(), deliver(),
-//    and call_callbacks()
-//  - call_callbacks() and read members written to in deliver()
-//
-// Separately from the handover data flow, m_target_results is guarded by the target lock
+    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)
 {
-    REALM_ASSERT(m_query);
     m_info = &info;
-
-    auto& table = *m_query->get_table();
-    if (!table.is_attached())
-        return false;
-
-    auto table_ndx = table.get_index_in_group();
-    if (table_ndx == npos) { // is a subtable
-        auto& parent = *table.get_parent_table();
-        size_t row_ndx = table.get_parent_row_index();
-        size_t col_ndx = find_container_column(parent, row_ndx, &table, type_Table, &Table::get_subtable);
-        info.lists.push_back({parent.get_index_in_group(), row_ndx, col_ndx, &m_changes});
-    }
-    else { // is a top-level table
-        if (info.table_moves_needed.size() <= table_ndx)
-            info.table_moves_needed.resize(table_ndx + 1);
-        info.table_moves_needed[table_ndx] = true;
-    }
-
-    return has_run() && have_callbacks();
+    return m_query->get_table() && has_run() && have_callbacks();
 }
 
 bool ResultsNotifier::need_to_run()
 {
     REALM_ASSERT(m_info);
-    REALM_ASSERT(!m_tv.is_attached());
 
     {
         auto lock = lock_target();
         // Don't run the query if the results aren't actually going to be used
-        if (!get_realm() || (!have_callbacks() && !m_target_results->wants_background_updates())) {
+        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) {
-        return false;
+        // 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()
 {
-    size_t table_ndx = m_query->get_table()->get_index_in_group();
     if (has_run() && have_callbacks()) {
-        CollectionChangeBuilder* changes = nullptr;
-        if (table_ndx == npos)
-            changes = &m_changes;
-        else if (table_ndx < m_info->tables.size())
-            changes = &m_info->tables[table_ndx];
-
-        std::vector<size_t> next_rows;
-        next_rows.reserve(m_tv.size());
-        for (size_t i = 0; i < m_tv.size(); ++i)
-            next_rows.push_back(m_tv[i].get_index());
-
-        util::Optional<IndexSet> move_candidates;
-        if (changes) {
-            auto const& moves = changes->moves;
-            for (auto& idx : m_previous_rows) {
-                if (changes->deletions.contains(idx)) {
-                    // check if this deletion was actually a move
-                    auto it = lower_bound(begin(moves), end(moves), idx,
-                                          [](auto const& a, auto b) { return a.from < b; });
-                    idx = it != moves.end() && it->from == idx ? it->to : npos;
-                }
-                else
-                    idx = changes->insertions.shift(changes->deletions.unshift(idx));
-            }
-            if (m_target_is_in_table_order && !m_descriptor_ordering.will_apply_sort())
-                move_candidates = changes->insertions;
-        }
+        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_changes = CollectionChangeBuilder::calculate(m_previous_rows, next_rows,
-                                                       get_modification_checker(*m_info, *m_query->get_table()),
-                                                       move_candidates);
+        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_tv.size());
-        for (size_t i = 0; i < m_tv.size(); ++i)
-            m_previous_rows[i] = m_tv[i].get_index();
+        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()->is_attached()) {
-        m_changes = {};
-        m_changes.deletions.set(m_previous_rows.size());
+    if (!m_query->get_table()) {
+        m_change = {};
+        m_change.deletions.set(m_previous_rows.size());
         m_previous_rows.clear();
         return;
     }
@@ -173,80 +155,200 @@ void ResultsNotifier::run()
         return;
 
     m_query->sync_view_if_needed();
-    m_tv = m_query->find_all();
-    m_tv.apply_descriptor_ordering(m_descriptor_ordering);
-    m_last_seen_version = m_tv.sync_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(SharedGroup& sg)
+void ResultsNotifier::do_prepare_handover(Transaction& sg)
 {
-    if (!m_tv.is_attached()) {
-        // if the table version didn't change we can just reuse the same handover
-        // object and bump its version to the current SG version
-        if (m_tv_handover)
-            m_tv_handover->version = sg.get_version_of_current_transaction();
+    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 = {};
+    }
+}
 
-        // add_changes() needs to be called even if there are no changes to
-        // clear the skip flag on the callbacks
-        add_changes(std::move(m_changes));
-        return;
+bool ResultsNotifier::prepare_to_deliver()
+{
+    auto lock = lock_target();
+    if (!get_realm()) {
+        m_handover_tv.reset();
+        m_delivered_tv.reset();
+        return false;
     }
+    if (!m_handover_tv)
+        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;
+}
 
-    REALM_ASSERT(m_tv.is_in_sync());
+void ResultsNotifier::do_attach_to(Transaction& sg)
+{
+    if (m_query->get_table())
+        m_query = sg.import_copy_of(*m_query, PayloadPolicy::Move);
+}
 
-    m_tv_handover = sg.export_for_handover(m_tv, MutableSourcePayload::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;
+    }
 
-    add_changes(std::move(m_changes));
-    REALM_ASSERT(m_changes.empty());
+}
 
-    // detach the TableView as we won't need it again and keeping it around
-    // makes advance_read() much more expensive
-    m_tv = {};
+void ListResultsNotifier::release_data() noexcept
+{
+    m_list = {};
+    CollectionNotifier::release_data();
 }
 
-void ResultsNotifier::deliver(SharedGroup& sg)
+bool ListResultsNotifier::get_list_indices(ListIndices& out)
 {
-    auto lock = lock_target();
+    if (!m_delivered_indices)
+        return false;
+    auto& transaction = source_shared_group();
+    if (m_delivered_transaction_version != transaction.get_version_of_current_transaction())
+        return false;
 
-    // Target realm being null here indicates that we were unregistered while we
-    // were in the process of advancing the Realm version and preparing for
-    // delivery, i.e. the results was destroyed from the "wrong" thread
-    if (!get_realm()) {
-        return;
+    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;
     }
 
-    REALM_ASSERT(!m_query_handover);
-    if (m_tv_to_deliver) {
-        Results::Internal::set_table_view(*m_target_results,
-                                          std::move(*sg.import_from_handover(std::move(m_tv_to_deliver))));
+    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));
+        });
     }
-    REALM_ASSERT(!m_tv_to_deliver);
+
+    m_previous_indices = *m_run_indices;
 }
 
-bool ResultsNotifier::prepare_to_deliver()
+void ListResultsNotifier::run()
 {
-    auto lock = lock_target();
-    if (!get_realm())
-        return false;
-    m_tv_to_deliver = std::move(m_tv_handover);
-    return true;
+    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 ResultsNotifier::do_attach_to(SharedGroup& sg)
+void ListResultsNotifier::do_prepare_handover(Transaction& sg)
 {
-    REALM_ASSERT(m_query_handover);
-    m_query = sg.import_from_handover(std::move(m_query_handover));
-    m_descriptor_ordering = DescriptorOrdering::create_from_and_consume_patch(m_ordering_handover, *m_query->get_table());
+    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();
 }
 
-void ResultsNotifier::do_detach_from(SharedGroup& sg)
+bool ListResultsNotifier::prepare_to_deliver()
 {
-    REALM_ASSERT(m_query);
-    REALM_ASSERT(!m_tv.is_attached());
+    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;
+}
 
-    DescriptorOrdering::generate_patch(m_descriptor_ordering, m_ordering_handover);
-    m_query_handover = sg.export_for_handover(*m_query, MutableSourcePayload::Move);
-    m_query = nullptr;
+void ListResultsNotifier::do_attach_to(Transaction& sg)
+{
+    if (m_list->is_attached())
+        m_list = sg.import_copy_of(*m_list);
 }

+ 58 - 23
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/results_notifier.hpp

@@ -22,58 +22,93 @@
 #include "collection_notifier.hpp"
 #include "results.hpp"
 
-#include <realm/group_shared.hpp>
+#include <realm/db.hpp>
 
 namespace realm {
 namespace _impl {
-class ResultsNotifier : public CollectionNotifier {
+class ResultsNotifierBase : public CollectionNotifier {
 public:
-    ResultsNotifier(Results& target);
+    using ListIndices = util::Optional<std::vector<size_t>>;
+    using CollectionNotifier::CollectionNotifier;
 
-    void target_results_moved(Results& old_target, Results& new_target);
+    virtual bool get_tableview(TableView&) { return false; }
+    virtual bool get_list_indices(ListIndices&) { return false; }
+};
 
-private:
-    // Target Results to update
-    // Can only be used with lock_target() held
-    Results* m_target_results;
+class ResultsNotifier : public ResultsNotifierBase {
+public:
+    ResultsNotifier(Results& target);
+    bool get_tableview(TableView& out) override;
 
-    // The source Query, in handover form iff m_sg is null
-    std::unique_ptr<SharedGroup::Handover<Query>> m_query_handover;
+private:
     std::unique_ptr<Query> m_query;
-
-    DescriptorOrdering::HandoverPatch m_ordering_handover;
     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_tv;
-    std::unique_ptr<SharedGroup::Handover<TableView>> m_tv_handover;
-    std::unique_ptr<SharedGroup::Handover<TableView>> m_tv_to_deliver;
+    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.
-    uint_fast64_t m_last_seen_version = -1;
+    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_rows;
+    std::vector<size_t> m_previous_indices;
 
-    // The changeset calculated during run() and delivered in do_prepare_handover()
-    CollectionChangeBuilder m_changes;
     TransactionChangeInfo* m_info = nullptr;
+    bool m_results_were_used = true;
 
     bool need_to_run();
     void calculate_changes();
-    void deliver(SharedGroup&) override;
 
     void run() override;
-    void do_prepare_handover(SharedGroup&) 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(SharedGroup& sg) override;
-    void do_detach_from(SharedGroup& sg) override;
+    void do_attach_to(Transaction& sg) override;
 };
 
 } // namespace _impl

+ 148 - 459
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/transact_log_handler.cpp

@@ -23,8 +23,7 @@
 #include "index_set.hpp"
 #include "shared_realm.hpp"
 
-#include <realm/group_shared.hpp>
-#include <realm/lang_bind_helper.hpp>
+#include <realm/db.hpp>
 
 #include <algorithm>
 #include <numeric>
@@ -37,8 +36,8 @@ class KVOAdapter : public _impl::TransactionChangeInfo {
 public:
     KVOAdapter(std::vector<BindingContext::ObserverState>& observers, BindingContext* context);
 
-    void before(SharedGroup& sg);
-    void after(SharedGroup& sg);
+    void before(Transaction& sg);
+    void after(Transaction& sg);
 
 private:
     BindingContext* m_context;
@@ -48,13 +47,10 @@ private:
     struct ListInfo {
         BindingContext::ObserverState* observer;
         _impl::CollectionChangeBuilder builder;
-        size_t col;
-        size_t initial_size;
+        ColKey col;
     };
     std::vector<ListInfo> m_lists;
     VersionID m_version;
-
-    size_t new_table_ndx(size_t ndx) const { return ndx < table_indices.size() ? table_indices[ndx] : ndx; }
 };
 
 KVOAdapter::KVOAdapter(std::vector<BindingContext::ObserverState>& observers, BindingContext* context)
@@ -65,40 +61,33 @@ KVOAdapter::KVOAdapter(std::vector<BindingContext::ObserverState>& observers, Bi
     if (m_observers.empty())
         return;
 
-    std::vector<size_t> tables_needed;
+    std::vector<TableKey> tables_needed;
     for (auto& observer : observers) {
-        tables_needed.push_back(observer.table_ndx);
+        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));
+    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(observer.table_ndx);
-        for (size_t i = 0, count = table->get_column_count(); i < count; ++i) {
-            auto type = table->get_column_type(i);
-            if (type == type_LinkList)
-                m_lists.push_back({&observer, {}, i, size_t(-1)});
-            else if (type == type_Table)
-                m_lists.push_back({&observer, {}, i, table->get_subtable_size(i, observer.row_ndx)});
+        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});
         }
     }
 
-    auto max = std::max_element(begin(tables_needed), end(tables_needed));
-    if (*max >= table_modifications_needed.size())
-        table_modifications_needed.resize(*max + 1, false);
-    if (*max >= table_moves_needed.size())
-        table_moves_needed.resize(*max + 1, false);
-    for (auto& tbl : tables_needed) {
-        table_modifications_needed[tbl] = true;
-        table_moves_needed[tbl] = true;
-    }
+    tables.reserve(tables_needed.size());
+    for (auto& tbl : tables_needed)
+        tables[tbl.value] = {};
     for (auto& list : m_lists)
-        lists.push_back({list.observer->table_ndx, list.observer->row_ndx, list.col, &list.builder});
+        lists.push_back({list.observer->table_key,
+            list.observer->obj_key, list.col.value, &list.builder});
 }
 
-void KVOAdapter::before(SharedGroup& sg)
+void KVOAdapter::before(Transaction& sg)
 {
     if (!m_context)
         return;
@@ -108,37 +97,20 @@ void KVOAdapter::before(SharedGroup& sg)
         return;
 
     for (auto& observer : m_observers) {
-        size_t table_ndx = new_table_ndx(observer.table_ndx);
-        if (table_ndx >= tables.size())
+        auto it = tables.find(observer.table_key.value);
+        if (it == tables.end())
             continue;
 
-        auto const& table = tables[table_ndx];
-        auto const& moves = table.moves;
-        auto idx = observer.row_ndx;
-        auto it = lower_bound(begin(moves), end(moves), idx,
-                              [](auto const& a, auto b) { return a.from < b; });
-        if (it != moves.end() && it->from == idx)
-            idx = it->to;
-        else if (table.deletions.contains(idx)) {
+        auto const& table = it->second;
+        auto key = observer.obj_key;
+        if (table.deletions_contains(key)) {
             m_invalidated.push_back(observer.info);
             continue;
         }
-        else
-            idx = table.insertions.shift(table.deletions.unshift(idx));
-        if (table.modifications.contains(idx)) {
-            observer.changes.resize(table.columns.size());
-            size_t i = 0;
-            for (auto& c : table.columns) {
-                auto& change = observer.changes[i];
-                if (table_ndx >= column_indices.size() || column_indices[table_ndx].empty())
-                    change.initial_column_index = i;
-                else if (i >= column_indices[table_ndx].size())
-                    change.initial_column_index = i - column_indices[table_ndx].size() + column_indices[table_ndx].back() + 1;
-                else
-                    change.initial_column_index = column_indices[table_ndx][i];
-                if (change.initial_column_index != npos && c.contains(idx))
-                    change.kind = BindingContext::ColumnInfo::Kind::Set;
-                ++i;
+        auto column_modifications = table.get_columns_modified(key);
+        if (column_modifications) {
+            for (auto col : *column_modifications) {
+                observer.changes[col].kind = BindingContext::ColumnInfo::Kind::Set;
             }
         }
     }
@@ -148,19 +120,19 @@ void KVOAdapter::before(SharedGroup& sg)
             // 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
-            if (list.col < list.observer->changes.size())
-                list.observer->changes[list.col].kind = BindingContext::ColumnInfo::Kind::None;
+            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[new_table_ndx(list.observer->table_ndx)].deletions.contains(list.observer->row_ndx));
+            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
-        REALM_ASSERT(list.col < list.observer->changes.size());
+        auto it = list.observer->changes.find(list.col.value);
+        REALM_ASSERT(it != list.observer->changes.end());
         auto& builder = list.builder;
-        auto& changes = list.observer->changes[list.col];
+        auto& changes = it->second;
 
         builder.modifications.remove(builder.insertions);
 
@@ -212,18 +184,13 @@ void KVOAdapter::before(SharedGroup& sg)
         else {
             REALM_ASSERT(!builder.deletions.empty());
             changes.kind = BindingContext::ColumnInfo::Kind::Remove;
-            // Table clears don't come with the size, so we need to fix up the
-            // notification to make it just delete all rows that actually existed
-            if (std::prev(builder.deletions.end())->second > list.initial_size)
-                changes.indices.set(list.initial_size);
-            else
-                changes.indices = builder.deletions;
+            changes.indices = builder.deletions;
         }
     }
     m_context->will_change(m_observers, m_invalidated);
 }
 
-void KVOAdapter::after(SharedGroup& sg)
+void KVOAdapter::after(Transaction& sg)
 {
     if (!m_context)
         return;
@@ -232,44 +199,9 @@ void KVOAdapter::after(SharedGroup& sg)
                           m_version != sg.get_version_of_current_transaction());
 }
 
-template<typename Derived>
-struct MarkDirtyMixin  {
-    bool mark_dirty(size_t row, size_t col, _impl::Instruction instr=_impl::instr_Set)
-    {
-        // Ignore SetDefault and SetUnique as those conceptually cannot be
-        // changes to existing rows
-        if (instr == _impl::instr_Set)
-            static_cast<Derived *>(this)->mark_dirty(row, col);
-        return true;
-    }
-
-    bool set_int(size_t c, size_t r, int_fast64_t, _impl::Instruction i, size_t) { return mark_dirty(r, c, i); }
-    bool set_bool(size_t c, size_t r, bool, _impl::Instruction i) { return mark_dirty(r, c, i); }
-    bool set_float(size_t c, size_t r, float, _impl::Instruction i) { return mark_dirty(r, c, i); }
-    bool set_double(size_t c, size_t r, double, _impl::Instruction i) { return mark_dirty(r, c, i); }
-    bool set_string(size_t c, size_t r, StringData, _impl::Instruction i, size_t) { return mark_dirty(r, c, i); }
-    bool set_binary(size_t c, size_t r, BinaryData, _impl::Instruction i) { return mark_dirty(r, c, i); }
-    bool set_olddatetime(size_t c, size_t r, OldDateTime, _impl::Instruction i) { return mark_dirty(r, c, i); }
-    bool set_timestamp(size_t c, size_t r, Timestamp, _impl::Instruction i) { return mark_dirty(r, c, i); }
-    bool set_table(size_t c, size_t r, _impl::Instruction i) { return mark_dirty(r, c, i); }
-    bool set_mixed(size_t c, size_t r, const Mixed&, _impl::Instruction i) { return mark_dirty(r, c, i); }
-    bool set_link(size_t c, size_t r, size_t, size_t, _impl::Instruction i) { return mark_dirty(r, c, i); }
-    bool set_null(size_t c, size_t r, _impl::Instruction i, size_t) { return mark_dirty(r, c, i); }
-
-    bool add_int(size_t col, size_t row, int_fast64_t) { return mark_dirty(row, col); }
-    bool nullify_link(size_t col, size_t row, size_t) { return mark_dirty(row, col); }
-    bool insert_substring(size_t col, size_t row, size_t, StringData) { return mark_dirty(row, col); }
-    bool erase_substring(size_t col, size_t row, size_t, size_t) { return mark_dirty(row, col); }
-
-    bool set_int_unique(size_t, size_t, size_t, int_fast64_t) { return true; }
-    bool set_string_unique(size_t, size_t, size_t, StringData) { return true; }
-
-    bool add_row_with_key(size_t, size_t, size_t, int64_t) { return true; }
-};
-
 class TransactLogValidationMixin {
-    // Index of currently selected table
-    size_t m_current_table = 0;
+    // The currently selected table
+    TableKey m_current_table;
 
     REALM_NORETURN
     REALM_NOINLINE
@@ -279,139 +211,62 @@ class TransactLogValidationMixin {
     }
 
 protected:
-    size_t current_table() const noexcept { return m_current_table; }
+    TableKey current_table() const noexcept { return m_current_table; }
 
 public:
 
-    bool select_table(size_t group_level_ndx, int, const size_t*) noexcept
+    bool select_table(TableKey key) noexcept
     {
-        m_current_table = group_level_ndx;
+        m_current_table = key;
         return true;
     }
 
     // Removing or renaming things while a Realm is open is never supported
-    bool erase_group_level_table(size_t, size_t) { schema_error(); }
-    bool rename_group_level_table(size_t, StringData) { schema_error(); }
-    bool erase_column(size_t) { schema_error(); }
-    bool erase_link_column(size_t, size_t, size_t) { schema_error(); }
-    bool rename_column(size_t, StringData) { schema_error(); }
-
-    // Schema changes which don't involve a change in the schema version are
-    // allowed
-    bool add_search_index(size_t) { return true; }
-    bool remove_search_index(size_t) { return true; }
+    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(size_t, size_t, StringData) { return true; }
-    bool insert_column(size_t, DataType, StringData, bool) { return true; }
-    bool insert_link_column(size_t, DataType, StringData, size_t, size_t) { return true; }
-    bool set_link_type(size_t, LinkType) { return true; }
-    bool move_column(size_t, size_t) { return true; }
-    bool move_group_level_table(size_t, size_t) { return true; }
+    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 select_descriptor(int, const size_t*) { return true; }
-    bool select_link_list(size_t, size_t, size_t) { return true; }
-    bool insert_empty_rows(size_t, size_t, size_t, bool) { return true; }
-    bool erase_rows(size_t, size_t, size_t, bool) { return true; }
-    bool swap_rows(size_t, size_t) { return true; }
-    bool move_row(size_t, size_t) { return true; }
+    bool create_object(ObjKey) { return true; }
+    bool remove_object(ObjKey) { return true; }
     bool clear_table(size_t=0) noexcept { return true; }
-    bool link_list_set(size_t, size_t, size_t) { return true; }
-    bool link_list_insert(size_t, size_t, size_t) { return true; }
-    bool link_list_erase(size_t, size_t) { return true; }
-    bool link_list_nullify(size_t, size_t) { return true; }
-    bool link_list_clear(size_t) { return true; }
-    bool link_list_move(size_t, size_t) { return true; }
-    bool link_list_swap(size_t, size_t) { return true; }
-    bool merge_rows(size_t, size_t) { return true; }
-    bool optimize_table() { 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, public MarkDirtyMixin<TransactLogValidator> {
-    void mark_dirty(size_t, size_t) { }
+struct TransactLogValidator : public TransactLogValidationMixin {
+    bool modify_object(ColKey, ObjKey) { return true; }
+    bool select_list(ColKey, ObjKey) { return true; }
 };
 
-// Move the value at container[from] to container[to], shifting everything in
-// between, or do nothing if either are out of bounds
-template<typename Container>
-void rotate(Container& container, size_t from, size_t to)
-{
-    REALM_ASSERT(from != to);
-    if (from >= container.size() && to >= container.size())
-        return;
-    if (from >= container.size() || to >= container.size())
-        container.resize(std::max(from, to) + 1);
-    if (from < to)
-        std::rotate(begin(container) + from, begin(container) + from + 1, begin(container) + to + 1);
-    else
-        std::rotate(begin(container) + to, begin(container) + from, begin(container) + from + 1);
-}
-
-// Insert a default-initialized value at pos if there is anything after pos in the container.
-template<typename Container>
-void insert_empty_at(Container& container, size_t pos)
-{
-    if (pos < container.size())
-        container.emplace(container.begin() + pos);
-}
-
-// Shift `value` to reflect a move from `from` to `to`
-void adjust_for_move(size_t& value, size_t from, size_t to)
-{
-    if (value == from)
-        value = to;
-    else if (value > from && value <= to)
-        --value;
-    else if (value < from && value >= to)
-        ++value;
-}
-
-void adjust_for_move(std::vector<size_t>& values, size_t from, size_t to)
-{
-    for (auto& value : values)
-        adjust_for_move(value, from, to);
-}
-
-void expand_to(std::vector<size_t>& cols, size_t i)
-{
-    auto old_size = cols.size();
-    if (old_size > i)
-        return;
-
-    cols.resize(std::max(old_size * 2, i + 1));
-    std::iota(begin(cols) + old_size, end(cols), old_size == 0 ? 0 : cols[old_size - 1] + 1);
-}
-
-void adjust_ge(std::vector<size_t>& values, size_t i)
-{
-    for (auto& value : values) {
-        if (value >= i)
-            ++value;
-    }
-}
-
 // Extends TransactLogValidator to track changes made to LinkViews
-class TransactLogObserver : public TransactLogValidationMixin, public MarkDirtyMixin<TransactLogObserver> {
+class TransactLogObserver : public TransactLogValidationMixin {
     _impl::TransactionChangeInfo& m_info;
     _impl::CollectionChangeBuilder* m_active_list = nullptr;
-    _impl::CollectionChangeBuilder* m_active_table = nullptr;
-    _impl::CollectionChangeBuilder* m_active_descriptor = nullptr;
+    ObjectChangeSet* m_active_table = nullptr;
 
-    bool m_need_move_info = false;
-    bool m_is_top_level_table = true;
-
-    _impl::CollectionChangeBuilder* find_list(size_t tbl, size_t col, size_t row)
+    _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_ndx == tbl && it->row_ndx == row && it->col_ndx == col)
+            if (it->table_key == table && it->row_key == obj.value && it->col_key == col.value)
                 return it->changes;
         }
         return nullptr;
@@ -421,322 +276,160 @@ public:
     TransactLogObserver(_impl::TransactionChangeInfo& info)
     : m_info(info) { }
 
-    void mark_dirty(size_t row, size_t col)
-    {
-        if (m_active_table)
-            m_active_table->modify(row, col);
-    }
-
     void parse_complete()
     {
-        for (auto& table : m_info.tables)
-            table.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_descriptor(int levels, const size_t*) noexcept
-    {
-        if (levels == 0) // schema of selected table is being modified
-            m_active_descriptor = m_active_table;
-        else // schema of subtable is being modified; currently don't need to track this
-            m_active_descriptor = nullptr;
-        return true;
-    }
-
-    bool select_table(size_t group_level_ndx, int len, size_t const* path) noexcept
+    bool select_table(TableKey key) noexcept
     {
-        TransactLogValidationMixin::select_table(group_level_ndx, len, path);
-        m_active_table = nullptr;
-        m_is_top_level_table = true;
+        TransactLogValidationMixin::select_table(key);
 
-        // Nested subtables currently not supported
-        if (len > 1) {
-            m_is_top_level_table = false;
-            return true;
-        }
-
-        auto tbl_ndx = current_table();
-        if (!m_info.track_all && (tbl_ndx >= m_info.table_modifications_needed.size() || !m_info.table_modifications_needed[tbl_ndx]))
-            return true;
-
-        m_need_move_info = m_info.track_all || (tbl_ndx < m_info.table_moves_needed.size() &&
-                                                m_info.table_moves_needed[tbl_ndx]);
-        if (m_info.tables.size() <= tbl_ndx)
-            m_info.tables.resize(std::max(m_info.tables.size() * 2, tbl_ndx + 1));
-        m_active_table = &m_info.tables[tbl_ndx];
-
-        if (len == 1) {
-            // Mark the cell containing the subtable as modified since selecting
-            // a table is always followed by a modification of some sort
-            size_t col = path[0];
-            size_t row = path[1];
-            mark_dirty(row, col);
-
-            m_active_table = nullptr;
-            m_is_top_level_table = false;
-            if (auto table = find_list(current_table(), col, row)) {
-                m_active_table = table;
-                m_need_move_info = true;
-            }
+        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_link_list(size_t col, size_t row, size_t)
+    bool select_list(ColKey col, ObjKey obj)
     {
-        mark_dirty(row, col);
-        m_active_list = find_list(current_table(), col, row);
+        modify_object(col, obj);
+        m_active_list = find_list(obj, col);
         return true;
     }
 
-    bool link_list_set(size_t index, size_t, size_t)
+    bool list_set(size_t index)
     {
         if (m_active_list)
             m_active_list->modify(index);
         return true;
     }
 
-    bool link_list_insert(size_t index, size_t, size_t)
+    bool list_insert(size_t index)
     {
         if (m_active_list)
             m_active_list->insert(index);
         return true;
     }
 
-    bool link_list_erase(size_t index, size_t)
+    bool list_erase(size_t index)
     {
         if (m_active_list)
             m_active_list->erase(index);
         return true;
     }
 
-    bool link_list_nullify(size_t index, size_t prior_size)
-    {
-        return link_list_erase(index, prior_size);
-    }
-
-    bool link_list_swap(size_t index1, size_t index2)
+    bool list_swap(size_t index1, size_t index2)
     {
-        link_list_set(index1, 0, npos);
-        link_list_set(index2, 0, npos);
+        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 link_list_clear(size_t old_size)
+    bool list_clear(size_t old_size)
     {
         if (m_active_list)
             m_active_list->clear(old_size);
         return true;
     }
 
-    bool link_list_move(size_t from, size_t to)
+    bool list_move(size_t from, size_t to)
     {
         if (m_active_list)
             m_active_list->move(from, to);
         return true;
     }
 
-    bool insert_empty_rows(size_t row_ndx, size_t num_rows_to_insert, size_t, bool)
+    bool create_object(ObjKey key)
     {
         if (m_active_table)
-            m_active_table->insert(row_ndx, num_rows_to_insert, m_need_move_info);
-        if (!m_is_top_level_table)
-            return true;
-        for (auto& list : m_info.lists) {
-            if (list.table_ndx == current_table() && list.row_ndx >= row_ndx)
-                list.row_ndx += num_rows_to_insert;
-        }
-        return true;
-    }
-
-    bool add_row_with_key(size_t row_ndx, size_t prior_num_rows, size_t, int64_t)
-    {
-        insert_empty_rows(row_ndx, 1, prior_num_rows, false);
+            m_active_table->insertions_add(key.value);
         return true;
     }
 
-    bool erase_rows(size_t row_ndx, size_t, size_t prior_num_rows, bool unordered)
+    bool remove_object(ObjKey key)
     {
-        if (!unordered) {
-            if (m_active_table)
-                m_active_table->erase(row_ndx);
+        if (!m_active_table)
             return true;
-        }
-        size_t last_row = prior_num_rows - 1;
-        if (m_active_table)
-            m_active_table->move_over(row_ndx, last_row, m_need_move_info);
+        if (!m_active_table->insertions_remove(key.value))
+            m_active_table->deletions_add(key.value);
+        m_active_table->modifications_remove(key.value);
 
-        if (!m_is_top_level_table)
-            return true;
         for (size_t i = 0; i < m_info.lists.size(); ++i) {
             auto& list = m_info.lists[i];
-            if (list.table_ndx != current_table())
+            if (list.table_key != current_table())
                 continue;
-            if (list.row_ndx == row_ndx) {
+            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;
             }
-            if (list.row_ndx == last_row)
-                list.row_ndx = row_ndx;
-        }
-
-        return true;
-    }
-
-    bool swap_rows(size_t row_ndx_1, size_t row_ndx_2) {
-        REALM_ASSERT(row_ndx_1 < row_ndx_2);
-        if (!m_is_top_level_table) {
-            if (m_active_table) {
-                m_active_table->move(row_ndx_1, row_ndx_2);
-                if (row_ndx_1 + 1 != row_ndx_2)
-                    m_active_table->move(row_ndx_2 - 1, row_ndx_1);
-            }
-            return true;
-        }
-
-        if (m_active_table)
-            m_active_table->swap(row_ndx_1, row_ndx_2, m_need_move_info);
-        for (auto& list : m_info.lists) {
-            if (list.table_ndx == current_table()) {
-                if (list.row_ndx == row_ndx_1)
-                    list.row_ndx = row_ndx_2;
-                else if (list.row_ndx == row_ndx_2)
-                    list.row_ndx = row_ndx_1;
-            }
         }
-        return true;
-    }
-
-    bool move_row(size_t from_ndx, size_t to_ndx) {
-        // Move row is not supported for top level tables:
-        REALM_ASSERT(!m_active_table || !m_is_top_level_table);
 
-        if (m_active_table)
-            m_active_table->move(from_ndx, to_ndx);
         return true;
     }
 
-    bool merge_rows(size_t from, size_t to)
+    bool modify_object(ColKey col, ObjKey key)
     {
         if (m_active_table)
-            m_active_table->subsume(from, to, m_need_move_info);
-        if (!m_is_top_level_table)
-            return true;
-        for (auto& list : m_info.lists) {
-            if (list.table_ndx == current_table() && list.row_ndx == from)
-                list.row_ndx = to;
-        }
+            m_active_table->modifications_add(key.value, col.value);
         return true;
     }
 
-    bool clear_table(size_t=0)
+    bool clear_table(size_t old_size)
     {
-        auto tbl_ndx = current_table();
+        auto cur_table = current_table();
         if (m_active_table)
-            m_active_table->clear(std::numeric_limits<size_t>::max());
-        if (!m_is_top_level_table)
-            return true;
+            m_active_table->clear(old_size);
         auto it = remove_if(begin(m_info.lists), end(m_info.lists),
-                            [&](auto const& lv) { return lv.table_ndx == tbl_ndx; });
+                            [&](auto const& lv) { return lv.table_key == cur_table; });
         m_info.lists.erase(it, end(m_info.lists));
         return true;
     }
 
-    bool insert_column(size_t ndx, DataType, StringData, bool)
+    bool insert_column(ColKey)
     {
         m_info.schema_changed = true;
-
-        if (m_active_descriptor)
-            m_active_descriptor->insert_column(ndx);
-        if (m_active_descriptor != m_active_table || !m_is_top_level_table)
-            return true;
-        for (auto& list : m_info.lists) {
-            if (list.table_ndx == current_table() && list.col_ndx >= ndx)
-                ++list.col_ndx;
-        }
-        if (m_info.column_indices.size() <= current_table())
-            m_info.column_indices.resize(current_table() + 1);
-        auto& indices = m_info.column_indices[current_table()];
-        expand_to(indices, ndx);
-        insert_empty_at(indices, ndx);
-        indices[ndx] = npos;
         return true;
     }
 
-    void prepare_table_indices()
-    {
-        if (m_info.table_indices.empty() && !m_info.table_modifications_needed.empty()) {
-            m_info.table_indices.resize(m_info.table_modifications_needed.size());
-            std::iota(begin(m_info.table_indices), end(m_info.table_indices), 0);
-        }
-    }
-
-    bool insert_group_level_table(size_t ndx, size_t, StringData)
+    bool insert_group_level_table(TableKey)
     {
         m_info.schema_changed = true;
-
-        for (auto& list : m_info.lists) {
-            if (list.table_ndx >= ndx)
-                ++list.table_ndx;
-        }
-        prepare_table_indices();
-        adjust_ge(m_info.table_indices, ndx);
-        insert_empty_at(m_info.tables, ndx);
-        insert_empty_at(m_info.table_moves_needed, ndx);
-        insert_empty_at(m_info.table_modifications_needed, ndx);
         return true;
     }
-
-    bool move_column(size_t from, size_t to)
-    {
-        m_info.schema_changed = true;
-
-        if (m_active_descriptor)
-            m_active_descriptor->move_column(from, to);
-        if (m_active_descriptor != m_active_table || !m_is_top_level_table)
-            return true;
-        for (auto& list : m_info.lists) {
-            if (list.table_ndx == current_table())
-                adjust_for_move(list.col_ndx, from, to);
-        }
-        if (m_info.column_indices.size() <= current_table())
-            m_info.column_indices.resize(current_table() + 1);
-        expand_to(m_info.column_indices[current_table()], std::max(from, to) + 1);
-        rotate(m_info.column_indices[current_table()], from, to);
-        return true;
-    }
-
-    bool move_group_level_table(size_t from, size_t to)
-    {
-        m_info.schema_changed = true;
-
-        for (auto& list : m_info.lists)
-            adjust_for_move(list.table_ndx, from, to);
-
-        prepare_table_indices();
-        adjust_for_move(m_info.table_indices, from, to);
-        rotate(m_info.tables, from, to);
-        rotate(m_info.table_modifications_needed, from, to);
-        rotate(m_info.table_moves_needed, from, to);
-        return true;
-    }
-
-    bool insert_link_column(size_t ndx, DataType type, StringData name, size_t, size_t) { return insert_column(ndx, type, name, false); }
 };
 
 class KVOTransactLogObserver : public TransactLogObserver {
     KVOAdapter m_adapter;
     _impl::NotifierPackage& m_notifiers;
-    SharedGroup& m_sg;
+    Transaction& m_sg;
 
 public:
     KVOTransactLogObserver(std::vector<BindingContext::ObserverState>& observers,
                            BindingContext* context,
                            _impl::NotifierPackage& notifiers,
-                           SharedGroup& sg)
+                           Transaction& sg)
     : TransactLogObserver(m_adapter)
     , m_adapter(observers, context)
     , m_notifiers(notifiers)
@@ -754,14 +447,14 @@ public:
         TransactLogObserver::parse_complete();
         m_adapter.before(m_sg);
 
-        using sgf = _impl::SharedGroupFriend;
-        m_notifiers.package_and_wait(sgf::get_version_of_latest_snapshot(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::unique_ptr<SharedGroup>& sg,
+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();
@@ -775,31 +468,34 @@ void advance_with_notifications(BindingContext* context, const std::unique_ptr<S
     // version we're going to before we actually advance to that version
     if (observers.empty() && (!notifiers || notifiers.version())) {
         notifiers.before_advance();
-        func(TransactLogValidator());
+        TransactLogValidator validator;
+        func(&validator);
         auto new_version = sg->get_version_of_current_transaction();
         if (context && old_version != new_version)
             context->did_change({}, {});
-        if (!sg) // did_change() could close the Realm. Just return if it does.
+        // 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();
-        if (!sg) // will_send_notifications() could close the Realm. Just return if it does.
+        // will_send_notifications() could close the Realm. Just return if it does.
+        if (sg->get_transact_stage() == DB::transact_Ready)
             return;
-        // did_change() can change the read version, and if it does we can't
-        // deliver notifiers
-        if (new_version == sg->get_version_of_current_transaction())
-            notifiers.deliver(*sg);
         notifiers.after_advance();
-        if (sg && context)
+        if (sg->get_transact_stage() == DB::transact_Ready)
+            return;
+        if (context)
             context->did_send_notifications();
         return;
     }
 
     if (context)
         context->will_send_notifications();
-    func(KVOTransactLogObserver(observers, context, notifiers, *sg));
+    {
+        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.deliver(*sg);
     notifiers.after_advance();
     if (context)
         context->did_send_notifications();
@@ -816,57 +512,50 @@ UnsupportedSchemaChange::UnsupportedSchemaChange()
 }
 
 namespace transaction {
-void advance(SharedGroup& sg, BindingContext*, VersionID version)
+void advance(Transaction& tr, BindingContext*, VersionID version)
 {
-    LangBindHelper::advance_read(sg, TransactLogValidator(), version);
+    TransactLogValidator validator;
+    tr.advance_read(&validator, version);
 }
 
-void advance(const std::unique_ptr<SharedGroup>& sg, BindingContext* context, NotifierPackage& notifiers)
+void advance(const std::shared_ptr<Transaction>& tr, BindingContext* context, NotifierPackage& notifiers)
 {
-    advance_with_notifications(context, sg, [&](auto&&... args) {
-        LangBindHelper::advance_read(*sg, std::move(args)..., notifiers.version().value_or(VersionID{}));
+    advance_with_notifications(context, tr, [&](auto&&... args) {
+        tr->advance_read(std::move(args)..., notifiers.version().value_or(VersionID{}));
     }, notifiers);
 }
 
-void begin_without_validation(SharedGroup& sg)
-{
-    LangBindHelper::promote_to_write(sg);
-}
-
-void begin(const std::unique_ptr<SharedGroup>& sg, BindingContext* context, NotifierPackage& notifiers)
+void begin(const std::shared_ptr<Transaction>& tr, BindingContext* context, NotifierPackage& notifiers)
 {
-    advance_with_notifications(context, sg, [&](auto&&... args) {
-        LangBindHelper::promote_to_write(*sg, std::move(args)...);
+    advance_with_notifications(context, tr, [&](auto&&... args) {
+        tr->promote_to_write(std::move(args)...);
     }, notifiers);
 }
 
-void commit(SharedGroup& sg)
-{
-    LangBindHelper::commit_and_continue_as_read(sg);
-}
-
-void cancel(SharedGroup& sg, BindingContext* context)
+void cancel(Transaction& tr, BindingContext* context)
 {
     std::vector<BindingContext::ObserverState> observers;
     if (context) {
         observers = context->get_observed_rows();
     }
     if (observers.empty()) {
-        LangBindHelper::rollback_and_continue_as_read(sg);
+        tr.rollback_and_continue_as_read();
         return;
     }
 
     _impl::NotifierPackage notifiers;
-    LangBindHelper::rollback_and_continue_as_read(sg, KVOTransactLogObserver(observers, context, notifiers, sg));
+    KVOTransactLogObserver o(observers, context, notifiers, tr);
+    tr.rollback_and_continue_as_read(&o);
 }
 
-void advance(SharedGroup& sg, TransactionChangeInfo& info, VersionID version)
+void advance(Transaction& tr, TransactionChangeInfo& info, VersionID version)
 {
-    if (!info.track_all && info.table_modifications_needed.empty() && info.lists.empty()) {
-        LangBindHelper::advance_read(sg, version);
+    if (!info.track_all && info.tables.empty() && info.lists.empty()) {
+        tr.advance_read(version);
     }
     else {
-        LangBindHelper::advance_read(sg, TransactLogObserver(info), version);
+        TransactLogObserver o(info);
+        tr.advance_read(&o, version);
     }
 }
 

+ 7 - 10
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/transact_log_handler.hpp

@@ -27,7 +27,7 @@
 
 namespace realm {
 class BindingContext;
-class SharedGroup;
+class Transaction;
 
 namespace _impl {
 class NotifierPackage;
@@ -40,24 +40,21 @@ struct UnsupportedSchemaChange : std::logic_error {
 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::unique_ptr<SharedGroup>& sg, BindingContext* binding_context, NotifierPackage&);
-void advance(SharedGroup& sg, BindingContext* binding_context, VersionID);
+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::unique_ptr<SharedGroup>& sg, BindingContext* binding_context, NotifierPackage&);
-void begin_without_validation(SharedGroup& sg);
-
-// Commit a write transaction
-void commit(SharedGroup& sg);
+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(SharedGroup& sg, BindingContext* binding_context);
+void cancel(Transaction& sg, BindingContext* binding_context);
 
 // Advance the read transaction version, with change information gathered in info
-void advance(SharedGroup& sg, TransactionChangeInfo& info, VersionID version=VersionID{});
+void advance(Transaction& sg, TransactionChangeInfo& info, VersionID version=VersionID{});
 } // namespace transaction
 } // namespace _impl
 } // namespace realm

+ 16 - 18
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/weak_realm_notifier.cpp

@@ -19,38 +19,36 @@
 #include "impl/weak_realm_notifier.hpp"
 
 #include "shared_realm.hpp"
-#include "util/event_loop_signal.hpp"
+#include "util/scheduler.hpp"
 
 using namespace realm;
 using namespace realm::_impl;
 
-WeakRealmNotifier::WeakRealmNotifier(const std::shared_ptr<Realm>& realm, bool cache, bool bind_to_context)
+
+WeakRealmNotifier::WeakRealmNotifier(const std::shared_ptr<Realm>& realm)
 : m_realm(realm)
-, m_execution_context(realm->config().execution_context)
 , m_realm_key(realm.get())
-, m_cache(cache)
-, m_signal(bind_to_context ? std::make_shared<util::EventLoopSignal<Callback>>(Callback{realm}) : nullptr)
 {
+    bind_to_scheduler();
 }
 
 WeakRealmNotifier::~WeakRealmNotifier() = default;
 
-void WeakRealmNotifier::Callback::operator()() const
-{
-    if (auto realm = weak_realm.lock()) {
-        realm->notify();
-    }
-}
-
 void WeakRealmNotifier::notify()
 {
-    if (m_signal)
-        m_signal->notify();
+    if (m_scheduler)
+        m_scheduler->notify();
 }
 
-void WeakRealmNotifier::bind_to_execution_context(AnyExecutionContextID context)
+void WeakRealmNotifier::bind_to_scheduler()
 {
-    REALM_ASSERT(!m_signal);
-    m_signal = std::make_shared<util::EventLoopSignal<Callback>>(Callback{m_realm});
-    m_execution_context = context;
+    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();
+            }
+        });
+    }
 }

+ 6 - 19
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/weak_realm_notifier.hpp

@@ -19,8 +19,6 @@
 #ifndef REALM_WEAK_REALM_NOTIFIER_HPP
 #define REALM_WEAK_REALM_NOTIFIER_HPP
 
-#include "execution_context_id.hpp"
-
 #include <memory>
 #include <thread>
 
@@ -28,7 +26,7 @@ namespace realm {
 class Realm;
 
 namespace util {
-template<typename> class EventLoopSignal;
+class Scheduler;
 }
 
 namespace _impl {
@@ -39,39 +37,28 @@ namespace _impl {
 // a Realm instance is released from within a function holding the cache lock.
 class WeakRealmNotifier {
 public:
-    WeakRealmNotifier(const std::shared_ptr<Realm>& realm, bool cache, bool bind_to_context);
+    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(); }
 
-    // Does this WeakRealmNotifier store a Realm instance that should be used on the current thread?
-    bool is_cached_for_execution_context(const AnyExecutionContextID& execution_context) const
-    {
-        return m_cache && m_execution_context == execution_context;
-    }
-
     // 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();
 
-    void bind_to_execution_context(AnyExecutionContextID context);
+    // Bind this notifier to the Realm's scheduler.
+    void bind_to_scheduler();
 
 private:
     std::weak_ptr<Realm> m_realm;
-    AnyExecutionContextID m_execution_context;
     void* m_realm_key;
-    bool m_cache = false;
-
-    struct Callback {
-        const std::weak_ptr<Realm> weak_realm;
-        void operator()() const;
-    };
-    std::shared_ptr<util::EventLoopSignal<Callback>> m_signal;
+    std::shared_ptr<util::Scheduler> m_scheduler;
 };
 
 } // namespace _impl

+ 1 - 1
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/impl/windows/external_commit_helper.hpp

@@ -16,7 +16,7 @@
 //
 ////////////////////////////////////////////////////////////////////////////
 
-#include <realm/group_shared.hpp>
+#include <realm/db.hpp>
 
 #include <future>
 #include <windows.h>

+ 206 - 167
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/list.cpp

@@ -19,7 +19,6 @@
 #include "list.hpp"
 
 #include "impl/list_notifier.hpp"
-#include "impl/primitive_list_notifier.hpp"
 #include "impl/realm_coordinator.hpp"
 #include "object_schema.hpp"
 #include "object_store.hpp"
@@ -27,10 +26,23 @@
 #include "schema.hpp"
 #include "shared_realm.hpp"
 
-#include <realm/link_view.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 realm::_impl;
+using namespace _impl;
 
 List::List() noexcept = default;
 List::~List() = default;
@@ -40,30 +52,17 @@ List& List::operator=(const List&) = default;
 List::List(List&&) = default;
 List& List::operator=(List&&) = default;
 
-List::List(std::shared_ptr<Realm> r, Table& parent_table, size_t col, size_t row)
+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))
 {
-    auto type = parent_table.get_column_type(col);
-    REALM_ASSERT(type == type_LinkList || type == type_Table);
-    if (type == type_LinkList) {
-        m_link_view = parent_table.get_linklist(col, row);
-        m_table.reset(&m_link_view->get_target_table());
-    }
-    else {
-        m_table = parent_table.get_subtable(col, row);
-    }
 }
 
-List::List(std::shared_ptr<Realm> r, LinkViewRef l) noexcept
+List::List(std::shared_ptr<Realm> r, const LstBase& list)
 : m_realm(std::move(r))
-, m_link_view(std::move(l))
-{
-    m_table.reset(&m_link_view->get_target_table());
-}
-
-List::List(std::shared_ptr<Realm> r, TableRef t) noexcept
-: m_realm(std::move(r))
-, m_table(std::move(t))
+, m_type(ObjectSchema::from_core_type(*list.get_table(), list.get_col_key()) & ~PropertyType::Array)
+, m_list_base(list.clone())
 {
 }
 
@@ -75,28 +74,42 @@ static StringData object_name(Table const& table)
 ObjectSchema const& List::get_object_schema() const
 {
     verify_attached();
-    REALM_ASSERT(m_link_view);
 
-    if (!m_object_schema) {
-        REALM_ASSERT(get_type() == PropertyType::Object);
-        auto object_type = object_name(m_link_view->get_target_table());
+    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 = &*it;
+        m_object_schema = object_schema = &*it;
     }
-    return *m_object_schema;
+    return *object_schema;
 }
 
 Query List::get_query() const
 {
     verify_attached();
-    return m_link_view ? m_table->where(m_link_view) : m_table->where();
+    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();
 }
 
-size_t List::get_origin_row_index() const
+ColKey List::get_parent_column_key() const
 {
     verify_attached();
-    return m_link_view ? m_link_view->get_origin_row_index() : m_table->get_parent_row_index();
+    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
@@ -107,14 +120,15 @@ void List::verify_valid_row(size_t row_ndx, bool insertion) const
     }
 }
 
-void List::validate(RowExpr row) const
+void List::validate(const Obj& obj) const
 {
-    if (!row.is_attached())
+    if (!obj.is_valid())
         throw std::invalid_argument("Object has been deleted or invalidated");
-    if (row.get_table() != &m_link_view->get_target_table())
+    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(*row.get_table()),
-                                                 object_name(m_link_view->get_target_table())));
+                                                 object_name(*obj.get_table()),
+                                                 object_name(*target)));
 }
 
 bool List::is_valid() const
@@ -122,9 +136,9 @@ bool List::is_valid() const
     if (!m_realm)
         return false;
     m_realm->verify_thread();
-    if (m_link_view)
-        return m_link_view->is_attached();
-    return m_table && m_table->is_attached();
+    if (!m_realm->is_in_read_transaction())
+        return false;
+    return m_list_base->is_attached();
 }
 
 void List::verify_attached() const
@@ -143,101 +157,65 @@ void List::verify_in_transaction() const
 size_t List::size() const
 {
     verify_attached();
-    return m_link_view ? m_link_view->size() : m_table->size();
+    return m_list_base->size();
 }
 
-size_t List::to_table_ndx(size_t row) const noexcept
-{
-    return m_link_view ? m_link_view->get(row).get_index() : row;
-}
-
-PropertyType List::get_type() const
-{
-    verify_attached();
-    return m_link_view ? PropertyType::Object
-                       : ObjectSchema::from_core_type(*m_table->get_descriptor(), 0);
-}
-
-namespace {
 template<typename T>
-auto get(Table& table, size_t row)
+T List::get(size_t row_ndx) const
 {
-    return table.get<T>(0, row);
+    verify_valid_row(row_ndx);
+    return as<T>().get(row_ndx);
 }
 
 template<>
-auto get<RowExpr>(Table& table, size_t row)
-{
-    return table.get(row);
-}
-}
-
-template<typename T>
-T List::get(size_t row_ndx) const
+Obj List::get(size_t row_ndx) const
 {
     verify_valid_row(row_ndx);
-    return realm::get<T>(*m_table, to_table_ndx(row_ndx));
+    auto& list = as<Obj>();
+    return list.get_target_table()->get_object(list.get(row_ndx));
 }
 
-template RowExpr List::get(size_t) const;
-
 template<typename T>
 size_t List::find(T const& value) const
 {
     verify_attached();
-    return m_table->find_first(0, value);
+    return as<T>().find_first(value);
 }
 
 template<>
-size_t List::find(RowExpr const& row) const
+size_t List::find(Obj const& o) const
 {
     verify_attached();
-    if (!row.is_attached())
+    if (!o.is_valid())
         return not_found;
-    validate(row);
+    validate(o);
 
-    return m_link_view ? m_link_view->find(row.get_index()) : row.get_index();
+    return as<Obj>().ConstLstIf<ObjKey>::find_first(o.get_key());
 }
 
 size_t List::find(Query&& q) const
 {
     verify_attached();
-    if (m_link_view) {
-        size_t index = get_query().and_query(std::move(q)).find();
-        return index == not_found ? index : m_link_view->find(index);
+    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;
     }
-    return q.find();
+    throw std::runtime_error("not implemented");
 }
 
 template<typename T>
 void List::add(T value)
 {
     verify_in_transaction();
-    m_table->set(0, m_table->add_empty_row(), value);
-}
-
-template<>
-void List::add(size_t target_row_ndx)
-{
-    verify_in_transaction();
-    m_link_view->add(target_row_ndx);
-}
-
-template<>
-void List::add(RowExpr row)
-{
-    validate(row);
-    add(row.get_index());
+    as<T>().add(value);
 }
 
 template<>
-void List::add(int value)
+void List::add(Obj o)
 {
     verify_in_transaction();
-    if (m_link_view)
-        add(static_cast<size_t>(value));
-    else
-        add(static_cast<int64_t>(value));
+    validate(o);
+    as<Obj>().add(o.get_key());
 }
 
 template<typename T>
@@ -245,23 +223,16 @@ void List::insert(size_t row_ndx, T value)
 {
     verify_in_transaction();
     verify_valid_row(row_ndx, true);
-    m_table->insert_empty_row(row_ndx);
-    m_table->set(0, row_ndx, value);
+    as<T>().insert(row_ndx, value);
 }
 
 template<>
-void List::insert(size_t row_ndx, size_t target_row_ndx)
+void List::insert(size_t row_ndx, Obj o)
 {
     verify_in_transaction();
     verify_valid_row(row_ndx, true);
-    m_link_view->insert(row_ndx, target_row_ndx);
-}
-
-template<>
-void List::insert(size_t row_ndx, RowExpr row)
-{
-    validate(row);
-    insert(row_ndx, row.get_index());
+    validate(o);
+    as<Obj>().insert(row_ndx, o.get_key());
 }
 
 void List::move(size_t source_ndx, size_t dest_ndx)
@@ -272,29 +243,20 @@ void List::move(size_t source_ndx, size_t dest_ndx)
     if (source_ndx == dest_ndx)
         return;
 
-    if (m_link_view)
-        m_link_view->move(source_ndx, dest_ndx);
-    else
-        m_table->move_row(source_ndx, dest_ndx);
+    m_list_base->move(source_ndx, dest_ndx);
 }
 
 void List::remove(size_t row_ndx)
 {
     verify_in_transaction();
     verify_valid_row(row_ndx);
-    if (m_link_view)
-        m_link_view->remove(row_ndx);
-    else
-        m_table->remove(row_ndx);
+    m_list_base->remove(row_ndx, row_ndx + 1);
 }
 
 void List::remove_all()
 {
     verify_in_transaction();
-    if (m_link_view)
-        m_link_view->clear();
-    else
-        m_table->clear();
+    m_list_base->clear();
 }
 
 template<typename T>
@@ -302,22 +264,17 @@ void List::set(size_t row_ndx, T value)
 {
     verify_in_transaction();
     verify_valid_row(row_ndx);
-    m_table->set(0, row_ndx, value);
+//    validate(row);
+    as<T>().set(row_ndx, value);
 }
 
 template<>
-void List::set(size_t row_ndx, size_t target_row_ndx)
+void List::set(size_t row_ndx, Obj o)
 {
     verify_in_transaction();
     verify_valid_row(row_ndx);
-    m_link_view->set(row_ndx, target_row_ndx);
-}
-
-template<>
-void List::set(size_t row_ndx, RowExpr row)
-{
-    validate(row);
-    set(row_ndx, row.get_index());
+    validate(o);
+    as<Obj>().set(row_ndx, o.get_key());
 }
 
 void List::swap(size_t ndx1, size_t ndx2)
@@ -325,40 +282,39 @@ void List::swap(size_t ndx1, size_t ndx2)
     verify_in_transaction();
     verify_valid_row(ndx1);
     verify_valid_row(ndx2);
-    if (m_link_view)
-        m_link_view->swap(ndx1, ndx2);
-    else
-        m_table->swap_rows(ndx1, 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_link_view)
-        m_link_view->remove_target_row(row_ndx);
+    if (m_type == PropertyType::Object)
+        as<Obj>().remove_target_row(row_ndx);
     else
-        m_table->remove(row_ndx);
+        m_list_base->remove(row_ndx, row_ndx + 1);
 }
 
 void List::delete_all()
 {
     verify_in_transaction();
-    if (m_link_view)
-        m_link_view->remove_all_target_rows();
+    if (m_type == PropertyType::Object)
+        as<Obj>().remove_all_target_rows();
     else
-        m_table->clear();
+        m_list_base->clear();
 }
 
 Results List::sort(SortDescriptor order) const
 {
     verify_attached();
-    if (m_link_view)
-        return Results(m_realm, m_link_view, util::none, std::move(order));
-
-    DescriptorOrdering new_order;
-    new_order.append_sort(std::move(order));
-    return Results(m_realm, get_query(), std::move(new_order));
+    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
@@ -369,15 +325,15 @@ Results List::sort(std::vector<std::pair<std::string, bool>> const& keypaths) co
 Results List::filter(Query q) const
 {
     verify_attached();
-    if (m_link_view)
-        return Results(m_realm, m_link_view, get_query().and_query(std::move(q)));
-    return Results(m_realm, get_query().and_query(std::move(q)));
+    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_link_view ? Results(m_realm, m_link_view) : Results(m_realm, *m_table);
+    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
@@ -385,37 +341,99 @@ Results List::snapshot() const
     return as_results().snapshot();
 }
 
-util::Optional<Mixed> List::max(size_t column)
-{
-    return as_results().max(column);
+// 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(size_t column)
+util::Optional<Mixed> List::min(ColKey col) const
 {
-    return as_results().min(column);
+    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(size_t column)
+Mixed List::sum(ColKey col) const
 {
-    // Results::sum() returns none only for Mode::Empty Results, so we can
-    // safely ignore that possibility here
-    return *as_results().sum(column);
+    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(size_t column)
+util::Optional<double> List::average(ColKey col) const
 {
-    return as_results().average(column);
+    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());
 }
 
-// These definitions rely on that LinkViews and Tables are interned by core
 bool List::operator==(List const& rgt) const noexcept
 {
-    return m_link_view == rgt.m_link_view && m_table.get() == rgt.m_table.get();
+    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.
@@ -425,15 +443,22 @@ NotificationToken List::add_notification_callback(CollectionChangeCallback cb) &
     if (m_notifier && !m_notifier->have_callbacks())
         m_notifier.reset();
     if (!m_notifier) {
-        if (get_type() == PropertyType::Object)
-            m_notifier = std::static_pointer_cast<_impl::CollectionNotifier>(std::make_shared<ListNotifier>(m_link_view, m_realm));
-        else
-            m_notifier = std::static_pointer_cast<_impl::CollectionNotifier>(std::make_shared<PrimitiveListNotifier>(m_table, m_realm));
+        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) {}
@@ -452,6 +477,7 @@ 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>)
@@ -460,9 +486,22 @@ 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<realm::List>::operator()(realm::List const& list) const
+size_t hash<List>::operator()(List const& list) const
 {
-    return std::hash<void*>()(list.m_link_view ? list.m_link_view.get() : (void*)list.m_table.get());
+    auto& impl = *list.m_list_base;
+    return hash_combine(impl.get_key().value, impl.get_table()->get_key().value,
+                        impl.get_col_key().value);
 }
 }

+ 57 - 29
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/list.hpp

@@ -23,21 +23,24 @@
 #include "impl/collection_notifier.hpp"
 #include "object.hpp"
 #include "property.hpp"
+#include "util/copyable_atomic.hpp"
 
-#include <realm/link_view_fwd.hpp>
-#include <realm/row.hpp>
-#include <realm/table_ref.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;
-template <typename T> class ThreadSafeReference;
+class ThreadSafeReference;
+struct ColKey;
+struct ObjKey;
 
 namespace _impl {
 class ListNotifier;
@@ -46,9 +49,8 @@ class ListNotifier;
 class List {
 public:
     List() noexcept;
-    List(std::shared_ptr<Realm> r, Table& parent_table, size_t col, size_t row);
-    List(std::shared_ptr<Realm> r, LinkViewRef l) noexcept;
-    List(std::shared_ptr<Realm> r, TableRef l) 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&);
@@ -58,10 +60,13 @@ public:
 
     const std::shared_ptr<Realm>& get_realm() const { return m_realm; }
     Query get_query() const;
-    size_t get_origin_row_index() 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;
+    PropertyType get_type() const { return m_type; }
 
     // Get the ObjectSchema of the values in this List
     // Only valid if get_type() returns PropertyType::Object
@@ -80,7 +85,7 @@ public:
     void delete_at(size_t list_ndx);
     void delete_all();
 
-    template<typename T = RowExpr>
+    template<typename T = Obj>
     T get(size_t row_ndx) const;
     template<typename T>
     size_t find(T const& value) const;
@@ -105,15 +110,21 @@ public:
     // 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(size_t column=0);
-    util::Optional<Mixed> min(size_t column=0);
-    util::Optional<double> average(size_t column=0);
-    Mixed sum(size_t column=0);
+    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;
 
@@ -150,31 +161,42 @@ public:
     };
 
 private:
-    friend ThreadSafeReference<List>;
-
     std::shared_ptr<Realm> m_realm;
-    mutable const ObjectSchema* m_object_schema = nullptr;
-    LinkViewRef m_link_view;
-    TableRef m_table;
-    _impl::CollectionNotifier::Handle<_impl::CollectionNotifier> m_notifier;
+    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(RowExpr) 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);
 
-    size_t to_table_ndx(size_t row) const noexcept;
-
     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));
 }
 
@@ -210,15 +232,21 @@ void List::set(Context& ctx, size_t row_ndx, T&& value, CreatePolicy policy)
 
 namespace _impl {
 template <class T>
-inline size_t help_get_current_row(const T&)
+inline ObjKey help_get_current_row(const T&)
+{
+    return ObjKey();
+}
+
+template <>
+inline ObjKey help_get_current_row(const ConstObj& v)
 {
-    return size_t(-1);
+    return v.get_key();
 }
 
 template <>
-inline size_t help_get_current_row(const RowExpr& v)
+inline ObjKey help_get_current_row(const Obj& v)
 {
-    return v.get_index();
+    return v.get_key();
 }
 
 template <class T>
@@ -227,9 +255,9 @@ inline bool help_compare_values(const T& v1, const T& v2)
     return v1 != v2;
 }
 template <>
-inline bool help_compare_values(const RowExpr& v1, const RowExpr& v2)
+inline bool help_compare_values(const Obj& v1, const Obj& v2)
 {
-    return v1.get_table() != v2.get_table() || v1.get_index() != v2.get_index();
+    return v1.get_table() != v2.get_table() || v1.get_key() != v2.get_key();
 }
 }
 

+ 48 - 22
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object.cpp

@@ -23,8 +23,20 @@
 #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)
@@ -50,17 +62,32 @@ ReadOnlyPropertyException::ReadOnlyPropertyException(const std::string& object_t
 , 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) {}
+: 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, ObjectSchema const& s, RowExpr const& o)
-: m_realm(std::move(r)), m_object_schema(&s), m_row(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 ndx)
+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_row(ObjectStore::table_for_object_type(m_realm->read_group(), object_type)->get(ndx))
-{ }
+, m_obj(ObjectStore::table_for_object_type(m_realm->read_group(), object_type)->get_object(index))
+{
+}
 
 Object::Object() = default;
 Object::~Object() = default;
@@ -72,8 +99,9 @@ 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_row, m_realm);
+        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))};
@@ -82,7 +110,7 @@ NotificationToken Object::add_notification_callback(CollectionChangeCallback cal
 void Object::verify_attached() const
 {
     m_realm->verify_thread();
-    if (!m_row.is_attached()) {
+    if (!m_obj.is_valid()) {
         throw InvalidatedObjectException(m_object_schema->name);
     }
 }
@@ -99,22 +127,20 @@ Property const& Object::property_for_name(StringData prop_name) const
 #if REALM_ENABLE_SYNC
 void Object::ensure_user_in_everyone_role()
 {
-    auto role_table = m_realm->read_group().get_table("class___Role");
-    if (!role_table)
-        return;
-    size_t ndx = role_table->find_first_string(role_table->get_column_index("name"), "everyone");
-    if (ndx == npos)
-        return;
-    auto users = role_table->get_linklist(role_table->get_column_index("members"), ndx);
-    if (users->find(m_row.get_index()) != not_found)
-        return;
-
-    users->add(m_row.get_index());
+    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_row.get<StringData>(m_row.get_table()->get_column_index("id"));
-    ObjectStore::ensure_private_role_exists_for_user(m_realm->read_group(), user_id);
+    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

+ 29 - 14
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object.hpp

@@ -21,12 +21,11 @@
 
 #include "impl/collection_notifier.hpp"
 
-#include <realm/row.hpp>
+#include <realm/obj.hpp>
 
 namespace realm {
 class ObjectSchema;
 struct Property;
-using RowExpr = BasicRowExpr<Table>;
 
 namespace _impl {
     class ObjectNotifier;
@@ -47,8 +46,10 @@ enum class CreatePolicy : int8_t {
 class Object {
 public:
     Object();
-    Object(std::shared_ptr<Realm> r, ObjectSchema const& s, RowExpr const& o);
-    Object(std::shared_ptr<Realm> r, StringData object_type, size_t ndx);
+    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&&);
@@ -58,16 +59,29 @@ public:
     ~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; }
-    RowExpr row() const { return m_row; }
+    Obj obj() const { return m_obj; }
 
-    bool is_valid() const { return m_row.is_attached(); }
+    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.
@@ -80,23 +94,23 @@ public:
                             ValueType value, CreatePolicy policy = CreatePolicy::ForceCreate);
 
     template<typename ValueType, typename ContextType>
-    ValueType get_property_value(ContextType& ctx, StringData prop_name);
+    ValueType get_property_value(ContextType& ctx, StringData prop_name) const;
 
     template<typename ValueType, typename ContextType>
-    ValueType get_property_value(ContextType& ctx, const Property& property);
+    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,
-                         size_t current_row = size_t(-1), Row* = nullptr);
+                         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,
-                         size_t current_row = size_t(-1), Row* = nullptr);
+                         ObjKey current_obj = ObjKey(), Obj* = nullptr);
 
     template<typename ValueType, typename ContextType>
     static Object get_for_primary_key(ContextType& ctx,
@@ -115,7 +129,7 @@ private:
 
     std::shared_ptr<Realm> m_realm;
     const ObjectSchema *m_object_schema;
-    Row m_row;
+    Obj m_obj;
     _impl::CollectionNotifier::Handle<_impl::ObjectNotifier> m_notifier;
 
 
@@ -123,11 +137,12 @@ private:
     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);
+    ValueType get_property_value_impl(ContextType& ctx, const Property &property) const;
 
     template<typename ValueType, typename ContextType>
-    static size_t get_for_primary_key_impl(ContextType& ctx, Table const& table,
-                                           const Property &primary_prop, ValueType primary_value);
+    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;

+ 75 - 91
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object_accessor.hpp

@@ -29,7 +29,6 @@
 #include "schema.hpp"
 #include "shared_realm.hpp"
 
-#include <realm/link_view.hpp>
 #include <realm/util/assert.hpp>
 #include <realm/table_view.hpp>
 
@@ -41,7 +40,8 @@
 
 namespace realm {
 template <typename ValueType, typename ContextType>
-void Object::set_property_value(ContextType& ctx, StringData prop_name, ValueType value, CreatePolicy policy)
+void Object::set_property_value(ContextType& ctx, StringData prop_name,
+                                ValueType value, CreatePolicy policy)
 {
     verify_attached();
     m_realm->verify_in_write();
@@ -57,13 +57,13 @@ void Object::set_property_value(ContextType& ctx, StringData prop_name, ValueTyp
 }
 
 template <typename ValueType, typename ContextType>
-ValueType Object::get_property_value(ContextType& ctx, const Property& property)
+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)
+ValueType Object::get_property_value(ContextType& ctx, StringData prop_name) const
 {
     return get_property_value_impl<ValueType>(ctx, property_for_name(prop_name));
 }
@@ -74,18 +74,18 @@ struct ValueUpdater {
     ContextType& ctx;
     Property const& property;
     ValueType& value;
-    RowExpr row;
-    size_t col;
+    Obj& obj;
+    ColKey col;
     CreatePolicy policy;
     bool is_default;
 
-    void operator()(RowExpr*)
+    void operator()(Obj*)
     {
         ContextType child_ctx(ctx, property);
-        auto curr_link = row.get_link(col);
-        auto link = child_ctx.template unbox<RowExpr>(value, policy, curr_link);
-        if (policy != CreatePolicy::UpdateModified || curr_link != link.get_index()) {
-            row.set_link(col, link.get_index());
+        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());
         }
     }
 
@@ -93,8 +93,8 @@ struct ValueUpdater {
     void operator()(T*)
     {
         auto new_val = ctx.template unbox<T>(value);
-        if (policy != CreatePolicy::UpdateModified || row.get<T>(col) != new_val) {
-            row.set(col, new_val, is_default);
+        if (policy != CreatePolicy::UpdateModified || obj.get<T>(col) != new_val) {
+            obj.set(col, new_val, is_default);
         }
     }
 };
@@ -106,17 +106,15 @@ void Object::set_property_value_impl(ContextType& ctx, const Property &property,
 {
     ctx.will_change(*this, property);
 
-    auto& table = *m_row.get_table();
-    size_t col = property.table_column;
-    size_t row = m_row.get_index();
+    ColKey col{property.column_key};
     if (is_nullable(property.type) && ctx.is_null(value)) {
-        if (policy != CreatePolicy::UpdateModified || !table.is_null(col, row)) {
+        if (policy != CreatePolicy::UpdateModified || !m_obj.is_null(col)) {
             if (property.type == PropertyType::Object) {
                 if (!is_default)
-                    table.nullify_link(col, row);
+                    m_obj.set_null(col);
             }
             else {
-                table.set_null(col, row, is_default);
+                m_obj.set_null(col, is_default);
             }
         }
 
@@ -129,48 +127,48 @@ void Object::set_property_value_impl(ContextType& ctx, const Property &property,
             throw ReadOnlyPropertyException(m_object_schema->name, property.name);
 
         ContextType child_ctx(ctx, property);
-        List list(m_realm, table, col, m_row.get_index());
+        List list(m_realm, m_obj, col);
         list.assign(child_ctx, value, policy);
         ctx.did_change();
         return;
     }
 
     ValueUpdater<ValueType, ContextType> updater{ctx, property, value,
-        table.get(row),col, policy, is_default};
+        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)
+ValueType Object::get_property_value_impl(ContextType& ctx, const Property &property) const
 {
     verify_attached();
 
-    size_t column = property.table_column;
-    if (is_nullable(property.type) && m_row.is_null(column))
+    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_row.get_table(), column, m_row.get_index()));
+        return ctx.box(List(m_realm, m_obj, column));
 
     switch (property.type & ~PropertyType::Flags) {
-        case PropertyType::Bool:   return ctx.box(m_row.get_bool(column));
-        case PropertyType::Int:    return ctx.box(m_row.get_int(column));
-        case PropertyType::Float:  return ctx.box(m_row.get_float(column));
-        case PropertyType::Double: return ctx.box(m_row.get_double(column));
-        case PropertyType::String: return ctx.box(m_row.get_string(column));
-        case PropertyType::Data:   return ctx.box(m_row.get_binary(column));
-        case PropertyType::Date:   return ctx.box(m_row.get_timestamp(column));
-        case PropertyType::Any:    return ctx.box(m_row.get_mixed(column));
+        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);
-            TableRef table = ObjectStore::table_for_object_type(m_realm->read_group(), property.object_type);
-            return ctx.box(Object(m_realm, *linkObjectSchema, table->get(m_row.get_link(column))));
+            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);
-            TableRef table = ObjectStore::table_for_object_type(m_realm->read_group(), target_object_schema->name);
-            auto tv = m_row.get_table()->get_backlink_view(m_row.get_index(), table.get(), link_property->table_column);
+            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();
@@ -180,17 +178,17 @@ ValueType Object::get_property_value_impl(ContextType& ctx, const Property &prop
 template<typename ValueType, typename ContextType>
 Object Object::create(ContextType& ctx, std::shared_ptr<Realm> const& realm,
                       StringData object_type, ValueType value,
-                      CreatePolicy policy, size_t current_row, Row* out_row)
+                      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_row, out_row);
+    return create(ctx, realm, *object_schema, value, policy, current_obj, out_row);
 }
 
 template<typename ValueType, typename ContextType>
 Object Object::create(ContextType& ctx, std::shared_ptr<Realm> const& realm,
                       ObjectSchema const& object_schema, ValueType value,
-                      CreatePolicy policy, size_t current_row, Row* out_row)
+                      CreatePolicy policy, ObjKey current_obj, Obj* out_row)
 {
     realm->verify_in_write();
 
@@ -198,8 +196,8 @@ Object Object::create(ContextType& ctx, std::shared_ptr<Realm> const& realm,
     bool created = false;
 
     // try to get existing row if updating
-    size_t row_index = realm::not_found;
-    TableRef table = ObjectStore::table_for_object_type(realm->read_group(), object_schema.name);
+    Obj obj;
+    auto table = realm->read_group().get_table(object_schema.table_key);
 
     bool skip_primary = true;
     if (auto primary_prop = object_schema.primary_key_property()) {
@@ -213,40 +211,15 @@ Object Object::create(ContextType& ctx, std::shared_ptr<Realm> const& realm,
                 throw MissingPropertyValueException(object_schema.name, primary_prop->name);
             primary_value = ctx.null_value();
         }
-        row_index = get_for_primary_key_impl(ctx, *table, *primary_prop, *primary_value);
-
-        if (row_index == realm::not_found) {
-            created = true;
-            if (primary_prop->type == PropertyType::Int) {
-#if REALM_ENABLE_SYNC
-                row_index = sync::create_object_with_primary_key(realm->read_group(), *table, ctx.template unbox<util::Optional<int64_t>>(*primary_value));
-#else
-                row_index = table->add_empty_row();
-                if (ctx.is_null(*primary_value))
-                    table->set_null_unique(primary_prop->table_column, row_index);
-                else
-                    table->set_unique(primary_prop->table_column, row_index, ctx.template unbox<int64_t>(*primary_value));
-#endif // REALM_ENABLE_SYNC
-            }
-            else if (primary_prop->type == PropertyType::String) {
-                auto value = ctx.template unbox<StringData>(*primary_value);
-#if REALM_ENABLE_SYNC
-                row_index = sync::create_object_with_primary_key(realm->read_group(), *table, value);
-#else
-                row_index = table->add_empty_row();
-                table->set_unique(primary_prop->table_column, row_index, value);
-#endif // REALM_ENABLE_SYNC
-            }
-            else {
-                REALM_TERMINATE("Unsupported primary key type.");
-            }
-        }
-        else if (policy == CreatePolicy::ForceCreate) {
-            if (realm->is_in_migration()) {
+        auto key = get_for_primary_key_impl(ctx, *table, *primary_prop, *primary_value);
+        if (key) {
+            if (policy != CreatePolicy::ForceCreate)
+                obj = table->get_object(key);
+            else if (realm->is_in_migration()) {
                 // Creating objects with duplicate primary keys is allowed in migrations
                 // as long as there are no duplicates at the end, as adding an entirely
                 // new column which is the PK will inherently result in duplicates at first
-                row_index = table->add_empty_row();
+                obj = table->create_object();
                 created = true;
                 skip_primary = false;
             }
@@ -255,25 +228,35 @@ Object Object::create(ContextType& ctx, std::shared_ptr<Realm> const& realm,
                                                     object_schema.name, ctx.print(*primary_value)));
             }
         }
+        else {
+            created = true;
+            Mixed primary_key;
+            if (primary_prop->type == PropertyType::Int) {
+                primary_key = ctx.template unbox<util::Optional<int64_t>>(*primary_value);
+            }
+            else if (primary_prop->type == PropertyType::String) {
+                primary_key = ctx.template unbox<StringData>(*primary_value);
+            }
+            else {
+                REALM_TERMINATE("Unsupported primary key type.");
+            }
+            obj = table->create_object_with_primary_key(primary_key);
+        }
     }
     else {
-        if (policy == CreatePolicy::UpdateModified && current_row != realm::not_found) {
-            row_index = current_row;
+        if (policy == CreatePolicy::UpdateModified && current_obj) {
+            obj = table->get_object(current_obj);
         }
         else {
-#if REALM_ENABLE_SYNC
-            row_index = sync::create_object(realm->read_group(), *table);
-#else
-            row_index = table->add_empty_row();
-#endif // REALM_ENABLE_SYNC
+        obj = table->create_object();
             created = true;
         }
     }
 
     // populate
-    Object object(realm, object_schema, table->get(row_index));
+    Object object(realm, object_schema, obj);
     if (out_row)
-        *out_row = object.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)
@@ -323,29 +306,30 @@ Object Object::get_for_primary_key(ContextType& ctx, std::shared_ptr<Realm> cons
         throw MissingPrimaryKeyException(object_schema.name);
     }
 
-    auto table = ObjectStore::table_for_object_type(realm->read_group(), 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, RowExpr());
-    auto row_index = get_for_primary_key_impl(ctx, *table, *primary_prop, primary_value);
-
-    return Object(realm, object_schema, row_index == realm::not_found ? Row() : Row(table->get(row_index)));
+        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>
-size_t Object::get_for_primary_key_impl(ContextType& ctx, Table const& table,
+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.table_column,
+        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.table_column,
+        return table.find_first(primary_prop.column_key,
                                 ctx.template unbox<util::Optional<int64_t>>(primary_value));
-    return table.find_first(primary_prop.table_column,
+    return table.find_first(primary_prop.column_key,
                             ctx.template unbox<int64_t>(primary_value));
 }
 

+ 52 - 28
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object_schema.cpp

@@ -23,9 +23,7 @@
 #include "property.hpp"
 #include "schema.hpp"
 
-
 #include <realm/data_type.hpp>
-#include <realm/descriptor.hpp>
 #include <realm/group.hpp>
 #include <realm/table.hpp>
 
@@ -53,47 +51,73 @@ ObjectSchema::ObjectSchema(std::string name, std::initializer_list<Property> per
     }
 }
 
-PropertyType ObjectSchema::from_core_type(Descriptor const& table, size_t col)
+PropertyType ObjectSchema::from_core_type(Table const& table, ColKey col)
 {
-    auto optional = table.is_nullable(col) ? PropertyType::Nullable : PropertyType::Required;
+    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 | optional;
-        case type_Float:     return PropertyType::Float | optional;
-        case type_Double:    return PropertyType::Double | optional;
-        case type_Bool:      return PropertyType::Bool | optional;
-        case type_String:    return PropertyType::String | optional;
-        case type_Binary:    return PropertyType::Data | optional;
-        case type_Timestamp: return PropertyType::Date | optional;
-        case type_Mixed:     return PropertyType::Any | optional;
+        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;
-        case type_Table:     return from_core_type(*table.get_subdescriptor(col), 0) | PropertyType::Array;
         default: REALM_UNREACHABLE();
     }
 }
 
-ObjectSchema::ObjectSchema(Group const& group, StringData name, size_t index) : name(name) {
+ObjectSchema::ObjectSchema(Group const& group, StringData name, TableKey key)
+: name(name)
+{
     ConstTableRef table;
-    if (index < group.size()) {
-        table = group.get_table(index);
+    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 (size_t col = 0; col < count; col++) {
-        if (auto property = ObjectStore::property_for_column_index(table, col)) {
-            persisted_properties.push_back(std::move(property.value()));
+
+    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 = realm::ObjectStore::get_primary_key_for_object(group, name);
+    primary_key = ObjectStore::get_primary_key_for_object(group, name);
     set_primary_key_property();
 }
 
-Property *ObjectSchema::property_for_name(StringData name)
+Property *ObjectSchema::property_for_name(StringData name) noexcept
 {
     for (auto& prop : persisted_properties) {
         if (StringData(prop.name) == name) {
@@ -108,7 +132,7 @@ Property *ObjectSchema::property_for_name(StringData name)
     return nullptr;
 }
 
-Property *ObjectSchema::property_for_public_name(StringData public_name)
+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) {
@@ -120,29 +144,29 @@ Property *ObjectSchema::property_for_public_name(StringData public_name)
     // 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 ((prop.public_name.empty() ? StringData(prop.name) :  StringData(prop.public_name)) == public_name)
+        if (StringData(prop.public_name.empty() ? prop.name : prop.public_name) == public_name)
             return &prop;
     }
     return nullptr;
 }
 
-const Property *ObjectSchema::property_for_public_name(StringData public_name) const
+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
+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
+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()
+void ObjectSchema::set_primary_key_property() noexcept
 {
     if (primary_key.length()) {
         if (auto primary_key_prop = primary_key_property()) {
@@ -310,7 +334,7 @@ void ObjectSchema::validate(Schema const& schema, std::vector<ObjectSchemaValida
 }
 
 namespace realm {
-bool operator==(ObjectSchema const& a, ObjectSchema const& b)
+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);

+ 20 - 13
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object_schema.hpp

@@ -19,15 +19,16 @@
 #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 Descriptor;
 class Group;
 class Schema;
+class Table;
 enum class PropertyType: unsigned char;
 struct ObjectSchemaValidationException;
 struct Property;
@@ -40,35 +41,41 @@ public:
                  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 is provided it is looked up in the group
-    ObjectSchema(Group const& group, StringData name, size_t index=-1);
+    // 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);
-    const Property *property_for_public_name(StringData public_name) const;
-    Property *property_for_name(StringData name);
-    const Property *property_for_name(StringData name) const;
-    Property *primary_key_property() {
+    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 {
+    const Property *primary_key_property() const noexcept {
         return property_for_name(primary_key);
     }
-    bool property_is_computed(Property const& property) const;
+    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);
+    friend bool operator==(ObjectSchema const& a, ObjectSchema const& b) noexcept;
 
-    static PropertyType from_core_type(Descriptor const& table, size_t col);
+    static PropertyType from_core_type(Table const& table, ColKey col);
 
 private:
-    void set_primary_key_property();
+    void set_primary_key_property() noexcept;
 };
 }
 

+ 116 - 234
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object_store.cpp

@@ -24,7 +24,6 @@
 #include "shared_realm.hpp"
 #include "sync/partial_sync.hpp"
 
-#include <realm/descriptor.hpp>
 #include <realm/group.hpp>
 #include <realm/table.hpp>
 #include <realm/table_view.hpp>
@@ -45,42 +44,21 @@ constexpr uint64_t ObjectStore::NotVersioned;
 namespace {
 const char * const c_metadataTableName = "metadata";
 const char * const c_versionColumnName = "version";
-const size_t c_versionColumnIndex = 0;
-
-const char * const c_primaryKeyTableName = "pk";
-const char * const c_primaryKeyObjectClassColumnName = "pk_table";
-const size_t c_primaryKeyObjectClassColumnIndex =  0;
-const char * const c_primaryKeyPropertyNameColumnName = "pk_property";
-const size_t c_primaryKeyPropertyNameColumnIndex =  1;
-
-const size_t c_zeroRowIndex = 0;
 
 const char c_object_table_prefix[] = "class_";
 
 void create_metadata_tables(Group& group) {
-    // The tables 'pk' and 'metadata' are treated specially by Sync. The 'pk' table
-    // is populated by `sync::create_table` and friends, while the 'metadata' table
-    // is simply ignored.
-    TableRef pk_table = group.get_or_add_table(c_primaryKeyTableName);
+    // 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->insert_column(c_versionColumnIndex, type_Int, c_versionColumnName);
-        metadata_table->add_empty_row();
-        // set initial version
-        metadata_table->set_int(c_versionColumnIndex, c_zeroRowIndex, ObjectStore::NotVersioned);
-    }
-
-    if (pk_table->get_column_count() == 0) {
-        pk_table->insert_column(c_primaryKeyObjectClassColumnIndex, type_String, c_primaryKeyObjectClassColumnName);
-        pk_table->insert_column(c_primaryKeyPropertyNameColumnIndex, type_String, c_primaryKeyPropertyNameColumnName);
+        metadata_table->add_column(type_Int, c_versionColumnName);
+        metadata_table->create_object().set(c_versionColumnName, int64_t(ObjectStore::NotVersioned));
     }
-    pk_table->add_search_index(c_primaryKeyObjectClassColumnIndex);
 }
 
 void set_schema_version(Group& group, uint64_t version) {
-    TableRef table = group.get_table(c_metadataTableName);
-    table->set_int(c_versionColumnIndex, c_zeroRowIndex, version);
+    group.get_table(c_metadataTableName)->get_object(0).set<int64_t>(c_versionColumnName, version);
 }
 
 template<typename Group>
@@ -105,41 +83,42 @@ DataType to_core_type(PropertyType type)
     }
 }
 
-void insert_column(Group& group, Table& table, Property const& property, size_t col_ndx)
+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);
-        table.insert_column_link(col_ndx, is_array(property.type) ? type_LinkList : type_Link,
-                                 property.name, *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)) {
-        DescriptorRef desc;
-        table.insert_column(col_ndx, type_Table, property.name, &desc);
-        desc->add_column(to_core_type(property.type & ~PropertyType::Flags), ObjectStore::ArrayColumnName,
-                         nullptr, is_nullable(property.type));
+        return table.add_column_list(to_core_type(property.type & ~PropertyType::Flags),
+                                     property.name, is_nullable(property.type));
     }
     else {
-        table.insert_column(col_ndx, to_core_type(property.type), property.name, is_nullable(property.type));
+        auto key = table.add_column(to_core_type(property.type), property.name, is_nullable(property.type));
         if (property.requires_index())
-            table.add_search_index(col_ndx);
+            table.add_search_index(key);
+        return key;
     }
 }
 
-void add_column(Group& group, Table& table, Property const& property)
+void replace_column(Group& group, Table& table, Property const& old_property,
+                    Property const& new_property)
 {
-    insert_column(group, table, property, table.get_column_count());
-}
-
-void replace_column(Group& group, Table& table, Property const& old_property, Property const& new_property)
-{
-    insert_column(group, table, new_property, old_property.table_column);
-    table.remove_column(old_property.table_column + 1);
+    table.remove_column(old_property.column_key);
+    add_column(group, table, new_property);
 }
 
 TableRef create_table(Group& group, ObjectSchema const& object_schema)
@@ -147,18 +126,16 @@ TableRef create_table(Group& group, ObjectSchema const& object_schema)
     auto name = ObjectStore::table_name_for_object_type(object_schema.name);
 
     TableRef table;
-#if REALM_ENABLE_SYNC
     if (auto* pk_property = object_schema.primary_key_property()) {
-        table = sync::create_table_with_primary_key(group, name, to_core_type(pk_property->type),
-                                                    pk_property->name, is_nullable(pk_property->type));
+        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 = sync::create_table(group, name);
+        table = group.get_or_add_table(name);
     }
-#else
-    table = group.get_or_add_table(name);
-    ObjectStore::set_primary_key_for_object(group, object_schema.name, object_schema.primary_key);
-#endif // REALM_ENABLE_SYNC
 
     return table;
 }
@@ -178,75 +155,20 @@ void add_initial_columns(Group& group, ObjectSchema const& object_schema)
     }
 }
 
-void copy_property_values(Property const& prop, Table& table)
-{
-    auto copy_property_values = [&](auto getter, auto setter) {
-        for (size_t i = 0, count = table.size(); i < count; i++) {
-            bool is_default = false;
-            (table.*setter)(prop.table_column, i, (table.*getter)(prop.table_column + 1, i),
-                            is_default);
-        }
-    };
-
-    switch (prop.type & ~PropertyType::Flags) {
-        case PropertyType::Int:
-            copy_property_values(&Table::get_int, &Table::set_int);
-            break;
-        case PropertyType::Bool:
-            copy_property_values(&Table::get_bool, &Table::set_bool);
-            break;
-        case PropertyType::Float:
-            copy_property_values(&Table::get_float, &Table::set_float);
-            break;
-        case PropertyType::Double:
-            copy_property_values(&Table::get_double, &Table::set_double);
-            break;
-        case PropertyType::String:
-            copy_property_values(&Table::get_string, &Table::set_string);
-            break;
-        case PropertyType::Data:
-            copy_property_values(&Table::get_binary, &Table::set_binary);
-            break;
-        case PropertyType::Date:
-            copy_property_values(&Table::get_timestamp, &Table::set_timestamp);
-            break;
-        default:
-            break;
-    }
-}
-
-void make_property_optional(Group& group, Table& table, Property property)
+void make_property_optional(Table& table, Property property)
 {
     property.type |= PropertyType::Nullable;
-    insert_column(group, table, property, property.table_column);
-    copy_property_values(property, table);
-    table.remove_column(property.table_column + 1);
+    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;
-    insert_column(group, table, property, property.table_column);
-    table.remove_column(property.table_column + 1);
-}
-
-void validate_primary_column_uniqueness(Group const& group, StringData object_type, StringData primary_property)
-{
-    auto table = ObjectStore::table_for_object_type(group, object_type);
-    if (table->get_distinct_view(table->get_column_index(primary_property)).size() != table->size()) {
-        throw DuplicatePrimaryKeyValueException(object_type, primary_property);
-    }
+    table.remove_column(property.column_key);
+    property.column_key = add_column(group, table, property).value;
 }
 
-void validate_primary_column_uniqueness(Group const& group)
-{
-    auto pk_table = group.get_table(c_primaryKeyTableName);
-    for (size_t i = 0, count = pk_table->size(); i < count; ++i) {
-        validate_primary_column_uniqueness(group,
-                                           pk_table->get_string(c_primaryKeyObjectClassColumnIndex, i),
-                                           pk_table->get_string(c_primaryKeyPropertyNameColumnIndex, i));
-    }
-}
 } // anonymous namespace
 
 void ObjectStore::set_schema_version(Group& group, uint64_t version) {
@@ -259,54 +181,26 @@ uint64_t ObjectStore::get_schema_version(Group const& group) {
     if (!table || table->get_column_count() == 0) {
         return ObjectStore::NotVersioned;
     }
-    return table->get_int(c_versionColumnIndex, c_zeroRowIndex);
+    return table->get_object(0).get<int64_t>(c_versionColumnName);
 }
 
 StringData ObjectStore::get_primary_key_for_object(Group const& group, StringData object_type) {
-    ConstTableRef table = group.get_table(c_primaryKeyTableName);
-    if (!table) {
-        return "";
-    }
-    size_t row = table->find_first_string(c_primaryKeyObjectClassColumnIndex, object_type);
-    if (row == not_found) {
-        return "";
+    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 table->get_string(c_primaryKeyPropertyNameColumnIndex, row);
+    return "";
 }
 
 void ObjectStore::set_primary_key_for_object(Group& group, StringData object_type, StringData primary_key) {
-    TableRef table = group.get_table(c_primaryKeyTableName);
-
-    size_t row = table->find_first_string(c_primaryKeyObjectClassColumnIndex, object_type);
-
-#if REALM_ENABLE_SYNC
-    // sync::create_table* functions should have already updated the pk table.
-    if (sync::has_object_ids(group)) {
-        if (primary_key.size() == 0)
-            REALM_ASSERT(row == not_found);
-        else {
-             REALM_ASSERT(row != not_found);
-             REALM_ASSERT(table->get_string(c_primaryKeyPropertyNameColumnIndex, row) == primary_key);
-        }
-        return;
-    }
-#endif // REALM_ENABLE_SYNC
-
-    if (row == not_found && primary_key.size()) {
-        row = table->add_empty_row();
-        table->set_string_unique(c_primaryKeyObjectClassColumnIndex, row, object_type);
-        table->set_string(c_primaryKeyPropertyNameColumnIndex, row, primary_key);
-        return;
-    }
-    // set if changing, or remove if setting to nil
-    if (primary_key.size() == 0) {
-        if (row != not_found) {
-            table->move_last_over(row);
-        }
-    }
-    else {
-        table->set_string(c_primaryKeyPropertyNameColumnIndex, row, 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) {
@@ -548,8 +442,8 @@ static void apply_non_migration_changes(Group& group, std::vector<SchemaChange>
 
         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->table_column); }
-        void operator()(RemoveIndex op) { table(op.object).remove_search_index(op.property->table_column); }
+        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);
 }
@@ -572,12 +466,12 @@ static void create_initial_tables(Group& group, std::vector<SchemaChange> const&
         // 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->table_column); }
-        void operator()(MakePropertyNullable op) { make_property_optional(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->table_column); }
-        void operator()(RemoveIndex op) { table(op.object).remove_search_index(op.property->table_column); }
+        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)
         {
@@ -604,8 +498,8 @@ void ObjectStore::apply_additive_changes(Group& group, std::vector<SchemaChange>
         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->table_column); }
-        void operator()(RemoveIndex op) { if (update_indexes) table(op.object).remove_search_index(op.property->table_column); }
+        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
@@ -634,11 +528,11 @@ static void apply_pre_migration_changes(Group& group, std::vector<SchemaChange>
         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(group, table(op.object), *op.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) { ObjectStore::set_primary_key_for_object(group, op.object->name.c_str(), op.property ? op.property->name.c_str() : ""); }
-        void operator()(AddIndex op) { table(op.object).add_search_index(op.property->table_column); }
-        void operator()(RemoveIndex op) { table(op.object).remove_search_index(op.property->table_column); }
+        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) {
@@ -648,7 +542,9 @@ static void apply_pre_migration_changes(Group& group, std::vector<SchemaChange>
 
 enum class DidRereadSchema { Yes, No };
 
-static void apply_post_migration_changes(Group& group, std::vector<SchemaChange> const& changes, Schema const& initial_schema,
+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;
@@ -667,13 +563,19 @@ static void apply_post_migration_changes(Group& group, std::vector<SchemaChange>
             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->table_column);
+            table->remove_column(op.property->column_key);
         }
 
         void operator()(ChangePrimaryKey op)
         {
+            Table& t = table(op.object);
             if (op.property) {
-                validate_primary_column_uniqueness(group, op.object->name, op.property->name);
+                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());
             }
         }
 
@@ -688,8 +590,8 @@ static void apply_post_migration_changes(Group& group, std::vector<SchemaChange>
             }
         }
 
-        void operator()(AddIndex op) { table(op.object).add_search_index(op.property->table_column); }
-        void operator()(RemoveIndex op) { table(op.object).remove_search_index(op.property->table_column); }
+        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) { }
@@ -703,7 +605,7 @@ static void apply_post_migration_changes(Group& group, std::vector<SchemaChange>
     }
 }
 
-static void create_default_permissions(Group& group, std::vector<SchemaChange> const& changes,
+static void create_default_permissions(Transaction& group, std::vector<SchemaChange> const& changes,
                                        std::string const& sync_user_id)
 {
 #if !REALM_ENABLE_SYNC
@@ -725,7 +627,7 @@ static void create_default_permissions(Group& group, std::vector<SchemaChange> c
     // sure that the permissions tables actually exist.
     using namespace schema_change;
     struct Applier {
-        Group& group;
+        Transaction& group;
         void operator()(AddTable op)
         {
             sync::set_class_permissions_for_role(group, op.object->name, "everyone",
@@ -751,13 +653,13 @@ static void create_default_permissions(Group& group, std::vector<SchemaChange> c
 }
 
 #if REALM_ENABLE_SYNC
-void ObjectStore::ensure_private_role_exists_for_user(Group& group, StringData sync_user_id)
+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");
-    size_t private_role_ndx = roles->find_first_string(roles->get_column_index("name"), private_role_name);
-    if (private_role_ndx != npos) {
+    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;
     }
@@ -766,14 +668,14 @@ void ObjectStore::ensure_private_role_exists_for_user(Group& group, StringData s
     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_index("name"), private_role_name);
+    private_role_ndx = roles->find_first_string(roles->get_column_key("name"), private_role_name);
     TableRef users = ObjectStore::table_for_object_type(group, "__User");
-    size_t user_ndx = users->find_first_string(users->get_column_index("id"), sync_user_id);
-    users->set_link(users->get_column_index("role"), user_ndx, private_role_ndx);
+    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(Group& group, uint64_t schema_version,
+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,
@@ -795,121 +697,112 @@ void ObjectStore::apply_schema_changes(Group& group, uint64_t schema_version,
         if (sync_user_id)
             create_default_permissions(group, changes, *sync_user_id);
 
-        set_schema_columns(group, target_schema);
+        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_columns(group, target_schema);
+        set_schema_keys(group, target_schema);
         return;
     }
 
     if (mode == SchemaMode::Manual) {
-        set_schema_columns(group, target_schema);
+        set_schema_keys(group, target_schema);
         if (migration_function) {
             migration_function();
         }
 
         verify_no_changes_required(schema_from_group(group).compare(target_schema));
-        validate_primary_column_uniqueness(group);
-        set_schema_columns(group, 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_columns(group, target_schema);
+        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_columns(group, target_schema);
+        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);
-        validate_primary_column_uniqueness(group);
+        group.validate_primary_columns();
     }
     else {
         apply_post_migration_changes(group, changes, {}, DidRereadSchema::No);
     }
 
     set_schema_version(group, target_schema_version);
-    set_schema_columns(group, target_schema);
+    set_schema_keys(group, target_schema);
 }
 
 Schema ObjectStore::schema_from_group(Group const& group) {
     std::vector<ObjectSchema> schema;
     schema.reserve(group.size());
-    for (size_t i = 0; i < group.size(); i++) {
-        auto object_type = object_type_for_table_name(group.get_table_name(i));
+    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, i);
+            schema.emplace_back(group, object_type, key);
         }
     }
     return schema;
 }
 
-util::Optional<Property> ObjectStore::property_for_column_index(ConstTableRef& table, size_t column_index)
+util::Optional<Property> ObjectStore::property_for_column_index(ConstTableRef& table, ColKey column_key)
 {
-    StringData column_name = table->get_column_name(column_index);
-
-#if REALM_ENABLE_SYNC
-    // The object ID column is an implementation detail, and is omitted from the schema.
-    // FIXME: Consider filtering out all column names starting with `!`.
-    if (column_name == sync::object_id_column_name)
-        return util::none;
-#endif
-
-    if (table->get_column_type(column_index) == type_Table) {
-        auto subdesc = table->get_subdescriptor(column_index);
-        if (subdesc->get_column_count() != 1 || subdesc->get_column_name(0) != ObjectStore::ArrayColumnName)
-            return util::none;
-    }
+    StringData column_name = table->get_column_name(column_key);
 
     Property property;
     property.name = column_name;
-    property.type = ObjectSchema::from_core_type(*table->get_descriptor(), column_index);
-    property.is_indexed = table->has_search_index(column_index);
-    property.table_column = column_index;
+    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_index);
+        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_columns(Group const& group, Schema& schema)
+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.table_column = table->get_column_index(property.name);
+            property.column_key = table->get_column_key(property.name);
         }
     }
 }
 
-void ObjectStore::delete_data_for_object(Group& group, StringData object_type) {
+void ObjectStore::delete_data_for_object(Group& group, StringData object_type)
+{
     if (TableRef table = table_for_object_type(group, object_type)) {
-        group.remove_table(table->get_index_in_group());
         ObjectStore::set_primary_key_for_object(group, object_type, "");
+        group.remove_table(table->get_key());
     }
 }
 
-bool ObjectStore::is_empty(Group const& group) {
-    for (size_t i = 0; i < group.size(); i++) {
-        ConstTableRef table = group.get_table(i);
+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;
@@ -938,7 +831,7 @@ void ObjectStore::rename_property(Group& group, Schema& target_schema, StringDat
                                             object_type, old_name, new_name));
     }
 
-    ObjectSchema table_object_schema(group, object_type);
+    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));
@@ -950,7 +843,7 @@ void ObjectStore::rename_property(Group& group, Schema& target_schema, StringDat
         // 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->table_column, new_name);
+        table->rename_column(old_property->column_key, new_name);
         return;
     }
 
@@ -964,23 +857,18 @@ void ObjectStore::rename_property(Group& group, Schema& target_schema, StringDat
                                             object_type, old_name, new_name));
     }
 
-    size_t column_to_remove = new_property->table_column;
-    table->rename_column(old_property->table_column, new_name);
-    table->remove_column(column_to_remove);
+    table->remove_column(new_property->column_key);
+    table->rename_column(old_property->column_key, new_name);
 
-    // update table_column for each property since it may have shifted
-    for (auto& current_prop : target_object_schema->persisted_properties) {
-        if (current_prop.table_column == column_to_remove)
-            current_prop.table_column = old_property->table_column;
-        else if (current_prop.table_column > column_to_remove)
-            --current_prop.table_column;
+    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.table_column = old_property->table_column;
-        make_property_optional(group, *table, prop);
+        prop.column_key = old_property->column_key;
+        make_property_optional(*table, prop);
     }
 }
 
@@ -990,12 +878,6 @@ InvalidSchemaVersionException::InvalidSchemaVersionException(uint64_t old_versio
 {
 }
 
-DuplicatePrimaryKeyValueException::DuplicatePrimaryKeyValueException(std::string object_type, std::string property)
-: logic_error(util::format("Primary key property '%1.%2' has duplicate values after migration.", object_type, property))
-, m_object_type(object_type), m_property(property)
-{
-}
-
 SchemaValidationException::SchemaValidationException(std::vector<ObjectSchemaValidationException> const& errors)
 : std::logic_error([&] {
     std::string message = "Schema validation failed due to the following errors:";

+ 7 - 15
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/object_store.hpp

@@ -27,8 +27,11 @@
 #include <functional>
 #include <string>
 #include <vector>
+#include <limits>
+
 namespace realm {
 class Group;
+class Transaction;
 class Schema;
 class SchemaChange;
 class StringData;
@@ -82,7 +85,7 @@ public:
     // 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(Group& group, uint64_t schema_version,
+    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,
@@ -99,9 +102,9 @@ public:
 
     // 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, size_t column_index);
+    static util::Optional<Property> property_for_column_index(ConstTableRef& table, ColKey column_key);
 
-    static void set_schema_columns(Group const& group, Schema& schema);
+    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);
@@ -123,7 +126,7 @@ public:
     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(Group& group, StringData sync_user_id);
+    static void ensure_private_role_exists_for_user(Transaction& group, StringData sync_user_id);
 
 private:
     friend class ObjectSchema;
@@ -138,17 +141,6 @@ private:
     uint64_t m_old_version, m_new_version;
 };
 
-class DuplicatePrimaryKeyValueException : public std::logic_error {
-public:
-    DuplicatePrimaryKeyValueException(std::string object_type, std::string property);
-
-    std::string const& object_type() const { return m_object_type; }
-    std::string const& property() const { return m_property; }
-private:
-    std::string m_object_type;
-    std::string m_property;
-};
-
 // Schema validation exceptions
 struct ObjectSchemaValidationException : public std::logic_error {
     ObjectSchemaValidationException(std::string message) : logic_error(std::move(message)) {}

+ 17 - 16
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/property.hpp

@@ -22,6 +22,8 @@
 #include "util/tagged_bool.hpp"
 
 #include <realm/util/features.h>
+// FIXME: keys.hpp is currently pretty heavyweight
+#include <realm/keys.hpp>
 
 #include <string>
 
@@ -29,13 +31,11 @@ namespace realm {
 namespace util {
     template<typename> class Optional;
 }
-class StringData;
 class BinaryData;
-class Timestamp;
+class Obj;
+class StringData;
 class Table;
-
-template<typename> class BasicRowExpr;
-using RowExpr = BasicRowExpr<Table>;
+class Timestamp;
 
 enum class PropertyType : unsigned char {
     Int    = 0,
@@ -86,24 +86,25 @@ struct Property {
     IsPrimary is_primary = false;
     IsIndexed is_indexed = false;
 
-    size_t table_column = -1;
+    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, 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&&) = default;
+    Property(Property&&) noexcept = default;
     Property& operator=(Property const&) = default;
-    Property& operator=(Property&&) = default;
+    Property& operator=(Property&&) noexcept = default;
 
     bool requires_index() const { return is_primary || is_indexed; }
 
-    bool type_is_indexable() const;
-    bool type_is_nullable() const;
+    bool type_is_indexable() const noexcept;
+    bool type_is_nullable() const noexcept;
 
     std::string type_string() const;
 };
@@ -172,7 +173,7 @@ inline constexpr bool is_nullable(PropertyType a)
     return to_underlying(a & PropertyType::Nullable) == to_underlying(PropertyType::Nullable);
 }
 
-template<typename Fn>
+template<typename ObjType=Obj, typename Fn>
 static auto switch_on_type(PropertyType type, Fn&& fn)
 {
     using PT = PropertyType;
@@ -185,7 +186,7 @@ static auto switch_on_type(PropertyType type, Fn&& fn)
         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((RowExpr*)0);
+        case PT::Object: return fn((ObjType*)0);
         default: REALM_COMPILER_HINT_UNREACHABLE();
     }
 }
@@ -235,7 +236,7 @@ inline Property::Property(std::string name, PropertyType type,
 {
 }
 
-inline bool Property::type_is_indexable() const
+inline bool Property::type_is_indexable() const noexcept
 {
     return type == PropertyType::Int
         || type == PropertyType::Bool
@@ -243,7 +244,7 @@ inline bool Property::type_is_indexable() const
         || type == PropertyType::String;
 }
 
-inline bool Property::type_is_nullable() const
+inline bool Property::type_is_nullable() const noexcept
 {
     return !(is_array(type) && type == PropertyType::Object) && type != PropertyType::LinkingObjects;
 }
@@ -269,7 +270,7 @@ inline std::string Property::type_string() const
 
 inline bool operator==(Property const& lft, Property const& rgt)
 {
-    // note: not checking table_column
+    // 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

文件差异内容过多而无法显示
+ 456 - 211
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/results.cpp


+ 120 - 90
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/results.hpp

@@ -26,6 +26,8 @@
 #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>
@@ -35,7 +37,7 @@ class Mixed;
 class ObjectSchema;
 
 namespace _impl {
-    class ResultsNotifier;
+    class ResultsNotifierBase;
 }
 
 class Results {
@@ -44,10 +46,12 @@ public:
     // 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, Table& table);
+    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, LinkViewRef lv, util::Optional<Query> q = {}, SortDescriptor s = {});
+    Results(std::shared_ptr<Realm> r, std::shared_ptr<LnkLst> list, util::Optional<Query> q = {}, SortDescriptor s = {});
     ~Results();
 
     // Results is copyable and moveable
@@ -60,100 +64,112 @@ public:
     std::shared_ptr<Realm> get_realm() const { return m_realm; }
 
     // Object schema describing the vendored object type
-    const ObjectSchema &get_object_schema() const;
+    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;
+    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();
+    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;
-
-    // Get the LinkView this Results is derived from, if any
-    LinkViewRef get_linkview() const { return m_link_view; }
+    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();
+    size_t size() REQUIRES(!m_mutex);
 
     // Get the row accessor for the given index
     // Throws OutOfBoundsIndexException if index >= size()
-    template<typename T = RowExpr>
-    T get(size_t index);
+    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);
+    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 = RowExpr>
-    util::Optional<T> first();
-    template<typename T = RowExpr>
-    util::Optional<T> last();
+    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);
+    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);
+    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();
+    void clear() REQUIRES(!m_mutex);
 
     // Create a new Results by further filtering or sorting this Results
-    Results filter(Query&& q) const;
-    Results sort(SortDescriptor&& sort) const;
-    Results sort(std::vector<std::pair<std::string, bool>> const& keypaths) const;
+    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;
-    Results distinct(std::vector<std::string> const& keypaths) const;
+    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;
+    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);
+    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 &;
-    Results snapshot() &&;
+    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(size_t column=0);
-    util::Optional<Mixed> min(size_t column=0);
-    util::Optional<double> average(size_t column=0);
-    util::Optional<Mixed> sum(size_t column=0);
+    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
-        LinkView,  // Backed directly by a LinkView
+        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 { return m_mode; }
+    Mode get_mode() const noexcept REQUIRES(!m_mutex);
 
     // Is this Results associated with a Realm that has not been invalidated?
     bool is_valid() const;
@@ -186,16 +202,17 @@ public:
 
     // The requested aggregate operation is not supported for the column type
     struct UnsupportedColumnTypeException : public std::logic_error {
-        size_t column_index;
+        ColKey column_key;
         StringData column_name;
         PropertyType property_type;
 
-        UnsupportedColumnTypeException(size_t column, const Table* table, const char* operation);
+        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(const std::string& object_type, const std::string& property_name);
+        InvalidPropertyException(StringData object_type, StringData property_name);
         const std::string object_type;
         const std::string property_name;
 	};
@@ -208,91 +225,104 @@ public:
     // 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
-    template<typename Func>
-    NotificationToken async(Func&& target);
     NotificationToken add_notification_callback(CollectionChangeCallback cb) &;
 
-    bool wants_background_updates() const { return m_wants_background_updates; }
-
     // Returns whether the rows are guaranteed to be in table order.
     bool is_in_table_order() const;
 
-    // Helper type to let ResultsNotifier update the tableview without giving access
-    // to any other privates or letting anyone else do so
-    class Internal {
-        friend class _impl::ResultsNotifier;
-        static void set_table_view(Results& results, TableView&& tv);
-    };
-
-    template<typename Context> auto first(Context&);
-    template<typename Context> auto last(Context&);
+    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);
+    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);
+    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);
+    void evaluate_query_if_needed(bool wants_notifications = true) REQUIRES(!m_mutex);
 
-private:
     enum class UpdatePolicy {
-        Auto,  // Update automatically to reflect changes in the underlying data.
-        Never, // Never update.
+        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 const ObjectSchema *m_object_schema = nullptr;
-    Query m_query;
-    TableView m_table_view;
-    LinkViewRef m_link_view;
-    TableRef m_table;
+    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::ResultsNotifier> m_notifier;
+    _impl::CollectionNotifier::Handle<_impl::ResultsNotifierBase> m_notifier;
 
-    Mode m_mode = Mode::Empty;
+    Mode m_mode GUARDED_BY(m_mutex) = Mode::Empty;
     UpdatePolicy m_update_policy = UpdatePolicy::Auto;
-    bool m_has_used_table_view = false;
-    bool m_wants_background_updates = true;
 
-    bool update_linkview();
+    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);
 
-    template<typename T>
-    util::Optional<T> try_get(size_t);
+    ColKey key(StringData) const;
 
-    template<typename Int, typename Float, typename Double, typename Timestamp>
-    util::Optional<Mixed> aggregate(size_t column,
-                                    const char* name,
-                                    Int agg_int, Float agg_float,
-                                    Double agg_double, Timestamp agg_timestamp);
-    void prepare_for_aggregate(size_t column, const char* name);
+    template<typename T>
+    util::Optional<T> try_get(size_t) REQUIRES(m_mutex);
 
-    void set_table_view(TableView&& tv);
+    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;
-};
+    auto dispatch(Fn&&) const REQUIRES(!m_mutex);
 
-template<typename Func>
-NotificationToken Results::async(Func&& target)
-{
-    return this->add_notification_callback([target = std::forward<Func>(target)](CollectionChangeSet const&, std::exception_ptr e) {
-        target(e);
-    });
-}
+    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
@@ -312,7 +342,7 @@ 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(*value)) : ctx->no_value();
+        return value ? static_cast<decltype(ctx->no_value())>(ctx->box(std::move(*value))) : ctx->no_value();
     });
 }
 
@@ -321,7 +351,7 @@ 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(*value)) : ctx.no_value();
+        return value ? static_cast<decltype(ctx.no_value())>(ctx.box(std::move(*value))) : ctx.no_value();
     });
 }
 
@@ -334,7 +364,7 @@ size_t Results::index_of(Context& ctx, T value)
 }
 
 template <typename ValueType, typename ContextType>
-void Results::set_property_value(ContextType& ctx, StringData prop_name, ValueType value)
+void Results::set_property_value(ContextType& ctx, StringData prop_name, ValueType value) NO_THREAD_SAFETY_ANALYSIS
 {
     // Check invariants for calling this method
     validate_write();
@@ -344,7 +374,7 @@ void Results::set_property_value(ContextType& ctx, StringData prop_name, ValueTy
         throw InvalidPropertyException(object_schema.name, prop_name);
     }
     if (prop->is_primary && !m_realm->is_in_migration()) {
-        throw ModifyPrimaryKeyException(m_object_schema->name, prop->name);
+        throw ModifyPrimaryKeyException(object_schema.name, prop->name);
     }
 
     // Update all objects in this ResultSets. Use snapshot to avoid correctness problems if the

+ 16 - 33
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/schema.cpp

@@ -28,29 +28,30 @@
 using namespace realm;
 
 namespace realm {
-bool operator==(Schema const& a, Schema const& b)
+bool operator==(Schema const& a, Schema const& b) noexcept
 {
     return static_cast<Schema::base const&>(a) == static_cast<Schema::base const&>(b);
 }
 }
 
-Schema::Schema() = default;
+Schema::Schema() noexcept = default;
 Schema::~Schema() = default;
 Schema::Schema(Schema const&) = default;
-Schema::Schema(Schema &&) = default;
+Schema::Schema(Schema &&) noexcept = default;
 Schema& Schema::operator=(Schema const&) = default;
-Schema& Schema::operator=(Schema&&) = default;
+Schema& Schema::operator=(Schema&&) noexcept = default;
 
 Schema::Schema(std::initializer_list<ObjectSchema> types) : Schema(base(types)) { }
 
-Schema::Schema(base types) : base(std::move(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)
+Schema::iterator Schema::find(StringData name) noexcept
 {
     auto it = std::lower_bound(begin(), end(), name, [](ObjectSchema const& lft, StringData rgt) {
         return lft.name < rgt;
@@ -61,7 +62,7 @@ Schema::iterator Schema::find(StringData name)
     return it;
 }
 
-Schema::const_iterator Schema::find(StringData name) const
+Schema::const_iterator Schema::find(StringData name) const noexcept
 {
     return const_cast<Schema *>(this)->find(name);
 }
@@ -101,19 +102,6 @@ void Schema::validate() const
     }
 }
 
-namespace {
-struct IsNotRemoveProperty {
-    bool operator()(SchemaChange sc) const { return sc.visit(*this); }
-    bool operator()(schema_change::RemoveProperty) const { return false; }
-    template<typename T> bool operator()(T) const { return true; }
-};
-struct GetRemovedColumn {
-    size_t operator()(SchemaChange sc) const { return sc.visit(*this); }
-    size_t operator()(schema_change::RemoveProperty p) const { return p.property->table_column; }
-    template<typename T> size_t operator()(T) const { REALM_COMPILER_HINT_UNREACHABLE(); }
-};
-}
-
 static void compare(ObjectSchema const& existing_schema,
                     ObjectSchema const& target_schema,
                     std::vector<SchemaChange>& changes)
@@ -151,25 +139,19 @@ static void compare(ObjectSchema const& existing_schema,
         }
     }
 
-    if (existing_schema.primary_key != target_schema.primary_key) {
-        changes.emplace_back(schema_change::ChangePrimaryKey{&existing_schema, target_schema.primary_key_property()});
-    }
-
     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});
         }
     }
 
-    // Move all RemovePropertys to the end and sort in descending order of
-    // column index, as removing a column will shift all columns after that one
-    auto it = std::partition(begin(changes), end(changes), IsNotRemoveProperty{});
-    std::sort(it, end(changes),
-              [](auto a, auto b) { return GetRemovedColumn()(a) > GetRemovedColumn()(b); });
+    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)
+void Schema::zip_matching(T&& a, U&& b, Func&& func) noexcept
 {
     size_t i = 0, j = 0;
     while (i < a.size() && j < b.size()) {
@@ -224,23 +206,24 @@ std::vector<SchemaChange> Schema::compare(Schema const& target_schema, bool incl
     return changes;
 }
 
-void Schema::copy_table_columns_from(realm::Schema const& other)
+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->table_column = current_prop.table_column;
+                target_prop->column_key = current_prop.column_key;
             }
         }
     });
 }
 
 namespace realm {
-bool operator==(SchemaChange const& lft, SchemaChange const& rgt)
+bool operator==(SchemaChange const& lft, SchemaChange const& rgt) noexcept
 {
     if (lft.m_kind != rgt.m_kind)
         return false;

+ 11 - 11
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/schema.hpp

@@ -34,20 +34,20 @@ class Schema : private std::vector<ObjectSchema> {
 private:
     using base = std::vector<ObjectSchema>;
 public:
-    Schema();
+    Schema() noexcept;
     ~Schema();
     // Create a schema from a vector of ObjectSchema
-    Schema(base types);
+    Schema(base types) noexcept;
     Schema(std::initializer_list<ObjectSchema> types);
 
     Schema(Schema const&);
-    Schema(Schema &&);
+    Schema(Schema&&) noexcept;
     Schema& operator=(Schema const&);
-    Schema& operator=(Schema&&);
+    Schema& operator=(Schema&&) noexcept;
 
     // find an ObjectSchema by name
-    iterator find(StringData name);
-    const_iterator find(StringData name) const;
+    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;
@@ -60,10 +60,10 @@ public:
     // 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_table_columns_from(Schema const&);
+    void copy_keys_from(Schema const&) noexcept;
 
-    friend bool operator==(Schema const&, Schema const&);
-    friend bool operator!=(Schema const& a, Schema const& b) { return !(a == b); }
+    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;
@@ -74,7 +74,7 @@ public:
 
 private:
     template<typename T, typename U, typename Func>
-    static void zip_matching(T&& a, U&& b, Func&& func);
+    static void zip_matching(T&& a, U&& b, Func&& func) noexcept;
 };
 
 namespace schema_change {
@@ -162,7 +162,7 @@ public:
         REALM_COMPILER_HINT_UNREACHABLE();
     }
 
-    friend bool operator==(SchemaChange const& lft, SchemaChange const& rgt);
+    friend bool operator==(SchemaChange const& lft, SchemaChange const& rgt) noexcept;
 private:
     enum class Kind {
 #define REALM_SCHEMA_CHANGE_TYPE(name) name,

文件差异内容过多而无法显示
+ 249 - 364
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/shared_realm.cpp


+ 80 - 84
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/shared_realm.hpp

@@ -19,11 +19,12 @@
 #ifndef REALM_REALM_HPP
 #define REALM_REALM_HPP
 
-#include "execution_context_id.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>
@@ -35,23 +36,23 @@ namespace realm {
 class AsyncOpenTask;
 class AuditInterface;
 class BindingContext;
+class DB;
 class Group;
+class Obj;
 class Realm;
 class Replication;
-class SharedGroup;
 class StringData;
 class Table;
+class ThreadSafeReference;
+class Transaction;
 struct SyncConfig;
-class ThreadSafeReferenceBase;
-template <typename T> class ThreadSafeReference;
-struct VersionID;
-template<typename Table> class BasicRow;
-typedef BasicRow<Table> Row;
-template<typename> class BasicRowExpr;
-using RowExpr = BasicRowExpr<Table>;
 typedef std::shared_ptr<Realm> SharedRealm;
 typedef std::weak_ptr<Realm> WeakRealm;
 
+namespace util {
+class Scheduler;
+}
+
 namespace _impl {
     class AnyHandover;
     class CollectionNotifier;
@@ -227,11 +228,6 @@ public:
         // The following are intended for internal/testing purposes and
         // should not be publicly exposed in binding APIs
 
-        // If false, always return a new Realm instance, and don't return
-        // that Realm instance for other requests for a cached Realm. Useful
-        // for dynamic Realms and for tests that need multiple instances on
-        // one thread
-        bool cache = true;
         // Throw an exception rather than automatically upgrading the file
         // format. Used by the browser to warn the user that it'll modify
         // the file.
@@ -242,9 +238,9 @@ public:
         // speeds up tests that don't need notifications.
         bool automatic_change_notifications = true;
 
-        // The identifier of the abstract execution context in which this Realm will be used.
-        // If unset, the current thread's identifier will be used to identify the execution context.
-        util::Optional<AbstractExecutionContextID> execution_context;
+        // 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;
@@ -255,16 +251,18 @@ public:
 
         // 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();
     };
 
-    // Get a cached Realm or create a new one if no cached copies exists
-    // Caching is done by path - mismatches for in_memory, schema mode or
-    // encryption key will raise an exception.
+    // Returns a thread-confined live Realm for the given configuration
     static SharedRealm get_shared_realm(Config config);
 
-    // Get a Realm for the given execution context (or current thread if `none`)
-    // from the thread safe reference. May return a cached Realm or create a new one.
-    static SharedRealm get_shared_realm(ThreadSafeReference<Realm>, util::Optional<AbstractExecutionContextID> = util::none);
+    // 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
@@ -275,6 +273,8 @@ public:
     // 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,
@@ -304,14 +304,36 @@ public:
     void cancel_transaction();
     bool is_in_transaction() const noexcept;
 
-    bool is_in_read_transaction() const { return !!m_group; }
+    // 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) { m_auto_refresh = auto_refresh; }
+    void set_auto_refresh(bool auto_refresh);
     bool auto_refresh() const { return m_auto_refresh; }
     void notify();
 
@@ -326,13 +348,14 @@ public:
     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 and remove it from the cache. Continuing to use a
-    // Realm after closing it will throw ClosedRealmException
+    // Close this Realm. Continuing to use a Realm after closing it will throw ClosedRealmException
     void close();
-    bool is_closed() const { return !m_read_only_group && !m_shared_group; }
+    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;
@@ -343,27 +366,21 @@ public:
     Realm& operator=(Realm&&) = delete;
     ~Realm();
 
-    // Construct a thread safe reference, pinning the version in the process.
-    template <typename T>
-    ThreadSafeReference<T> obtain_thread_safe_reference(T const& value);
-
-    // Advances the read transaction to the latest version, resolving the thread safe reference and unpinning the
-    // version in the process.
-    template <typename T>
-    T resolve_thread_safe_reference(ThreadSafeReference<T> reference);
-
     ComputedPrivileges get_privileges();
     ComputedPrivileges get_privileges(StringData object_type);
-    ComputedPrivileges get_privileges(RowExpr row);
+    ComputedPrivileges get_privileges(ConstObj const& obj);
 
     AuditInterface* audit_context() const noexcept;
 
-    static SharedRealm make_shared_realm(Config config, std::shared_ptr<_impl::RealmCoordinator> coordinator = nullptr) {
-        struct make_shared_enabler : public Realm {
-            make_shared_enabler(Config config, std::shared_ptr<_impl::RealmCoordinator> coordinator)
-            : Realm(std::move(config), std::move(coordinator)) { }
-        };
-        return std::make_shared<make_shared_enabler>(std::move(config), std::move(coordinator));
+    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
@@ -372,41 +389,35 @@ public:
         friend class _impl::CollectionNotifier;
         friend class _impl::PartialSyncHelper;
         friend class _impl::RealmCoordinator;
-        friend class ThreadSafeReferenceBase;
         friend class GlobalNotifier;
         friend class TestHelper;
+        friend class ThreadSafeReference;
 
-        // ResultsNotifier and ListNotifier need access to the SharedGroup
-        // to be able to call the handover functions, which are not very wrappable
-        static const std::unique_ptr<SharedGroup>& get_shared_group(Realm& realm) { return realm.m_shared_group; }
+        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);
     };
 
-    static void open_with_config(const Config& config,
-                                 std::unique_ptr<Replication>& history,
-                                 std::unique_ptr<SharedGroup>& shared_group,
-                                 std::unique_ptr<Group>& read_only_group,
-                                 Realm* realm);
-
 private:
-    // `enable_shared_from_this` is unsafe with public constructors; use `make_shared_realm` instead
-    Realm(Config config, std::shared_ptr<_impl::RealmCoordinator> coordinator);
+    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;
-    AnyExecutionContextID m_execution_context;
+    util::Optional<VersionID> m_frozen_version;
+    std::shared_ptr<util::Scheduler> m_scheduler;
     bool m_auto_refresh = true;
 
-    std::unique_ptr<Replication> m_history;
-    std::unique_ptr<SharedGroup> m_shared_group;
-    std::unique_ptr<Group> m_read_only_group;
-
-    Group *m_group = nullptr;
+    std::shared_ptr<Group> m_group;
 
     uint64_t m_schema_version;
     Schema m_schema;
@@ -417,13 +428,6 @@ private:
     // that's actually fully working
     bool m_dynamic_schema = true;
 
-    std::shared_ptr<_impl::RealmCoordinator> m_coordinator;
-    std::unique_ptr<sync::TableInfoCache> m_table_info_cache;
-    std::unique_ptr<sync::PermissionsCache> m_permissions_cache;
-
-    // File format versions populated when a file format upgrade takes place during realm opening
-    int upgrade_initial_version = 0, upgrade_final_version = 0;
-
     // True while sending the notifications caused by advancing the read
     // transaction version, to avoid recursive notifications where possible
     bool m_is_sending_notifications = false;
@@ -434,6 +438,7 @@ private:
     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);
@@ -452,13 +457,15 @@ private:
     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;
 
-    // FIXME: This is currently needed by the adapter to get access to its changeset cooker
-    Replication* history() { return m_history.get(); }
-
-    friend class _impl::RealmFriend;
+    // `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 {
@@ -481,9 +488,6 @@ public:
         IncompatibleLockFile,
         /** Thrown if the file needs to be upgraded to a new format, but upgrades have been explicitly disabled. */
         FormatUpgradeRequired,
-        /** Thrown if the local copy of a synced Realm file was created using an incompatible version of Realm.
-         The specified path is where the local file was moved for recovery. */
-        IncompatibleSyncedRealm,
     };
     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)) {}
@@ -531,14 +535,6 @@ class InvalidEncryptionKeyException : public std::logic_error {
 public:
     InvalidEncryptionKeyException() : std::logic_error("Encryption key must be 64 bytes.") {}
 };
-
-// FIXME Those are exposed for Java async queries, mainly because of handover related methods.
-class _impl::RealmFriend {
-public:
-    static SharedGroup& get_shared_group(Realm& realm);
-    static Group& read_group_to(Realm& realm, VersionID version);
-};
-
 } // namespace realm
 
 #endif /* defined(REALM_REALM_HPP) */

+ 208 - 78
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/thread_safe_reference.cpp

@@ -18,106 +18,236 @@
 
 #include "thread_safe_reference.hpp"
 
-#include "impl/realm_coordinator.hpp"
 #include "list.hpp"
 #include "object.hpp"
 #include "object_schema.hpp"
 #include "results.hpp"
+#include "shared_realm.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_created_in_write_transaction(realm.is_in_transaction())
+    {
+    }
+
+    void refresh_target_realm(Realm&);
 
-#include <realm/util/scope_exit.hpp>
+protected:
+    const TransactionRef m_transaction;
 
-using namespace realm;
+private:
+    const VersionID m_target_version;
+    const bool m_created_in_write_transaction;
+};
 
-ThreadSafeReferenceBase::ThreadSafeReferenceBase(SharedRealm source_realm) : m_source_realm(std::move(source_realm))
+void ThreadSafeReference::Payload::refresh_target_realm(Realm& realm)
 {
-    m_source_realm->verify_thread();
-    if (m_source_realm->is_in_transaction()) {
-        throw InvalidTransactionException("Cannot obtain thread safe reference during a write transaction.");
+    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());
     }
-
-    try {
-        m_version_id = get_source_shared_group().pin_version();
-    } catch (...) {
-        invalidate();
-        throw;
+    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();
     }
 }
 
-ThreadSafeReferenceBase::~ThreadSafeReferenceBase()
-{
-    if (!is_invalidated())
-        invalidate();
-}
+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())
+    {
+    }
 
-template <typename V, typename T>
-V ThreadSafeReferenceBase::invalidate_after_import(Realm& destination_realm, T construct_with_shared_group) {
-    destination_realm.verify_thread();
-    REALM_ASSERT_DEBUG(!m_source_realm->is_in_transaction());
-    REALM_ASSERT_DEBUG(!is_invalidated());
+    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);
+    }
 
-    SharedGroup& destination_shared_group = *Realm::Internal::get_shared_group(destination_realm);
-    auto unpin_version = util::make_scope_exit([&]() noexcept { invalidate(); });
+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)
+    {
+    }
 
-    return construct_with_shared_group(destination_shared_group);
-}
+    Object import_into(std::shared_ptr<Realm> const& r)
+    {
+        return Object(r, m_object_schema_name, m_key);
+    }
 
-SharedGroup& ThreadSafeReferenceBase::get_source_shared_group() const {
-    return *Realm::Internal::get_shared_group(*m_source_realm);
-}
+private:
+    ObjKey m_key;
+    std::string m_object_schema_name;
+};
+
+template<typename T>
+struct ListType {
+    using type = Lst<std::remove_reference_t<T>>;
+};
+
+// The code path which would instantiate List<Obj> isn't reachable, but still
+// produces errors about the type not being instantiable so we instead map it
+// to an arbitrary valid type
+template<>
+struct ListType<Obj&> {
+    using type = Lst<int64_t>;
+};
+
+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);
+        }
+    }
 
-bool ThreadSafeReferenceBase::has_same_config(Realm& realm) const {
-    return &Realm::Internal::get_coordinator(*m_source_realm) == &Realm::Internal::get_coordinator(realm);
-}
+    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<typename ListType<decltype(*t)>::type>();
+                });
+            }
+            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)
+    {
+    }
 
-void ThreadSafeReferenceBase::invalidate() {
-    REALM_ASSERT_DEBUG(m_source_realm);
-    SharedRealm thread_local_realm = Realm::Internal::get_coordinator(*m_source_realm).get_realm();
-    Realm::Internal::get_shared_group(*thread_local_realm)->unpin_version(m_version_id);
-    m_source_realm = nullptr;
+    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));
 }
 
-ThreadSafeReference<List>::ThreadSafeReference(List const& list)
-: ThreadSafeReferenceBase(list.get_realm())
-, m_link_view(get_source_shared_group().export_linkview_for_handover(list.m_link_view))
-, m_table(get_source_shared_group().export_table_for_handover(list.m_table))
-{ }
-
-List ThreadSafeReference<List>::import_into_realm(SharedRealm realm) && {
-    return invalidate_after_import<List>(*realm, [&](SharedGroup& shared_group) {
-        if (auto link_view = shared_group.import_linkview_from_handover(std::move(m_link_view)))
-            return List(std::move(realm), std::move(link_view));
-        return List(std::move(realm), shared_group.import_table_from_handover(std::move(m_table)));
-    });
+template<>
+ThreadSafeReference::ThreadSafeReference(std::shared_ptr<Realm> const& value)
+{
+    m_payload.reset(new PayloadImpl<std::shared_ptr<Realm>>(value));
 }
 
-ThreadSafeReference<Object>::ThreadSafeReference(Object const& object)
-: ThreadSafeReferenceBase(object.realm())
-, m_row(get_source_shared_group().export_for_handover(Row(object.row())))
-, m_object_schema_name(object.get_object_schema().name) { }
-
-Object ThreadSafeReference<Object>::import_into_realm(SharedRealm realm) && {
-    return invalidate_after_import<Object>(*realm, [&](SharedGroup& shared_group) {
-        Row row = *shared_group.import_from_handover(std::move(m_row));
-        auto object_schema = realm->schema().find(m_object_schema_name);
-        REALM_ASSERT_DEBUG(object_schema != realm->schema().end());
-        return Object(std::move(realm), *object_schema, row);
-    });
+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 {};
+    }
 }
 
-ThreadSafeReference<Results>::ThreadSafeReference(Results const& results)
-: ThreadSafeReferenceBase(results.get_realm())
-, m_query(get_source_shared_group().export_for_handover(results.get_query(), ConstSourcePayload::Copy))
-, m_ordering_patch([&]() {
-    DescriptorOrdering::HandoverPatch ordering_patch;
-    DescriptorOrdering::generate_patch(results.get_descriptor_ordering(), ordering_patch);
-    return ordering_patch;
-}()){ }
-
-Results ThreadSafeReference<Results>::import_into_realm(SharedRealm realm) && {
-    return invalidate_after_import<Results>(*realm, [&](SharedGroup& shared_group) {
-        Query query = *shared_group.import_from_handover(std::move(m_query));
-        Table& table = *query.get_table();
-        DescriptorOrdering descriptors = DescriptorOrdering::create_from_and_consume_patch(m_ordering_patch, table);
-        return Results(std::move(realm), std::move(query), std::move(descriptors));
-    });
+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

+ 21 - 96
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/thread_safe_reference.hpp

@@ -16,116 +16,41 @@
 //
 ////////////////////////////////////////////////////////////////////////////
 
-#ifndef REALM_THREAD_SAFE_REFERENCE_HPP
-#define REALM_THREAD_SAFE_REFERENCE_HPP
+#ifndef REALM_OS_THREAD_SAFE_REFERENCE_HPP
+#define REALM_OS_THREAD_SAFE_REFERENCE_HPP
 
-#include <realm/group_shared.hpp>
+#include <memory>
 
 namespace realm {
-class LinkView;
 class List;
 class Object;
-class Query;
 class Realm;
 class Results;
-class TableView;
-template<typename T> class BasicRow;
-typedef BasicRow<Table> Row;
-namespace _impl { class RealmCoordinator; }
 
-// Opaque type representing an object for handover
-class ThreadSafeReferenceBase {
+// Opaque type-ereased wrapper for a Realm object which can be imported into another Realm
+class ThreadSafeReference {
 public:
-    ThreadSafeReferenceBase(const ThreadSafeReferenceBase&) = delete;
-    ThreadSafeReferenceBase& operator=(const ThreadSafeReferenceBase&) = delete;
-    ThreadSafeReferenceBase(ThreadSafeReferenceBase&&) = default;
-    ThreadSafeReferenceBase& operator=(ThreadSafeReferenceBase&&) = default;
-    ThreadSafeReferenceBase();
-    virtual ~ThreadSafeReferenceBase();
+    ThreadSafeReference() noexcept;
+    ~ThreadSafeReference();
+    ThreadSafeReference(const ThreadSafeReference&) = delete;
+    ThreadSafeReference& operator=(const ThreadSafeReference&) = delete;
+    ThreadSafeReference(ThreadSafeReference&&) noexcept;
+    ThreadSafeReference& operator=(ThreadSafeReference&&) noexcept;
 
-    bool is_invalidated() const { return m_source_realm == nullptr; };
+    template<typename T>
+    ThreadSafeReference(T const& value);
 
-protected:
-    // Precondition: The associated Realm is for the current thread and is not in a write transaction;.
-    ThreadSafeReferenceBase(std::shared_ptr<Realm> source_realm);
+    // Import the object into the destination Realm
+    template<typename T>
+    T resolve(std::shared_ptr<Realm> const&);
 
-    SharedGroup& get_source_shared_group() const;
-
-    template <typename V, typename T>
-    V invalidate_after_import(Realm& destination_realm, T construct_with_shared_group);
+    explicit operator bool() const noexcept { return !!m_payload; }
 
 private:
-    friend Realm;
-
-    VersionID m_version_id;
-    std::shared_ptr<Realm> m_source_realm; // Strong reference keeps alive so version stays pinned! Don't touch!!
-
-    bool has_same_config(Realm& realm) const;
-    void invalidate();
-};
-
-template <typename T>
-class ThreadSafeReference;
-
-template<>
-class ThreadSafeReference<List>: public ThreadSafeReferenceBase {
-    friend class Realm;
-
-    std::unique_ptr<SharedGroup::Handover<LinkView>> m_link_view;
-    std::unique_ptr<SharedGroup::Handover<Table>> m_table;
-
-    // Precondition: The associated Realm is for the current thread and is not in a write transaction;.
-    ThreadSafeReference(List const& value);
-
-    // Precondition: Realm and handover are on same version.
-    List import_into_realm(std::shared_ptr<Realm> realm) &&;
-};
-
-template<>
-class ThreadSafeReference<Object>: public ThreadSafeReferenceBase {
-    friend class Realm;
-
-    std::unique_ptr<SharedGroup::Handover<Row>> m_row;
-    std::string m_object_schema_name;
-
-    // Precondition: The associated Realm is for the current thread and is not in a write transaction;.
-    ThreadSafeReference(Object const& value);
-
-    // Precondition: Realm and handover are on same version.
-    Object import_into_realm(std::shared_ptr<Realm> realm) &&;
-};
-
-template<>
-class ThreadSafeReference<Results>: public ThreadSafeReferenceBase {
-    friend class Realm;
-
-    std::unique_ptr<SharedGroup::Handover<Query>> m_query;
-    DescriptorOrdering::HandoverPatch m_ordering_patch;
-
-    // Precondition: The associated Realm is for the current thread and is not in a write transaction;.
-    ThreadSafeReference(Results const& value);
-
-    // Precondition: Realm and handover are on same version.
-    Results import_into_realm(std::shared_ptr<Realm> realm) &&;
-};
-
-template<>
-class ThreadSafeReference<Realm> {
-    friend class Realm;
-    friend class _impl::RealmCoordinator;
-
-    std::shared_ptr<Realm> m_realm;
-
-    ThreadSafeReference(std::shared_ptr<Realm>);
-    std::shared_ptr<Realm> resolve() &&;
-public:
-
-    ThreadSafeReference() = default;
-    ThreadSafeReference(ThreadSafeReference const&) = delete;
-    ThreadSafeReference(ThreadSafeReference &&) = default;
-    ThreadSafeReference& operator=(ThreadSafeReference const&) = delete;
-    ThreadSafeReference& operator=(ThreadSafeReference&&) = default;
+    class Payload;
+    template<typename> class PayloadImpl;
+    std::unique_ptr<Payload> m_payload;
 };
 }
 
-#endif /* REALM_THREAD_SAFE_REFERENCE_HPP */
+#endif /* REALM_OS_THREAD_SAFE_REFERENCE_HPP */

+ 7 - 7
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/src/util/tagged_bool.hpp

@@ -36,20 +36,20 @@ namespace util {
 template <typename Tag>
 struct TaggedBool {
     // Allow explicit construction from anything convertible to bool
-    constexpr explicit TaggedBool(bool v) : m_value(v) { }
+    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) : m_value(v) {}
+    constexpr TaggedBool(Bool v) noexcept : m_value(v) {}
 
-    constexpr TaggedBool(TaggedBool const& v) : m_value(v.m_value) {}
+    constexpr TaggedBool(TaggedBool const& v) noexcept : m_value(v.m_value) {}
 
-    constexpr operator bool() const { return m_value; }
-    constexpr TaggedBool operator!() const { return TaggedBool{!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) { return l.m_value == r.m_value; }
-    friend constexpr bool operator!=(TaggedBool l, TaggedBool r) { return l.m_value != r.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;

+ 7 - 0
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/CMakeLists.txt

@@ -9,6 +9,7 @@ set(HEADERS
 
 set(SOURCES
     collection_change_indices.cpp
+    frozen_objects.cpp
     index_set.cpp
     list.cpp
     main.cpp
@@ -49,6 +50,12 @@ if(REALM_ENABLE_SYNC)
     )
 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})
 

+ 26 - 298
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/collection_change_indices.cpp

@@ -59,16 +59,6 @@ TEST_CASE("collection_change: insert()") {
         c.insert(4);
         REQUIRE_MOVES(c, {10, 6}, {10, 2}, {3, 11});
     }
-
-    SECTION("shifts destination of previous row moves after the insertion point") {
-        c.move_over(10, 30);
-        c.move_over(15, 29);
-        c.move_over(16, 28);
-        c.move_over(20, 27);
-        c.insert(11, 3);
-        c.parse_complete();
-        REQUIRE_MOVES(c, {27, 23}, {28, 19}, {29, 18}, {30, 10});
-    }
 }
 
 TEST_CASE("collection_change: modify()") {
@@ -99,22 +89,17 @@ TEST_CASE("collection_change: modify()") {
     SECTION("marks the appropriate column as modified when applicable") {
         c.modify(5, 2);
         REQUIRE_INDICES(c.modifications, 5);
-        REQUIRE(c.columns.size() > 2);
-        REQUIRE(c.columns[0].empty());
-        REQUIRE(c.columns[1].empty());
+        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() > 2);
-        REQUIRE(c.columns[0].empty());
-        REQUIRE(c.columns[1].empty());
+        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(c.columns[0].empty());
+        REQUIRE(c.columns.size() == 2);
         REQUIRE_INDICES(c.columns[1], 3);
         REQUIRE_INDICES(c.columns[2], 4, 5);
     }
@@ -176,111 +161,6 @@ TEST_CASE("collection_change: erase()") {
     }
 }
 
-TEST_CASE("collection_change: move_over()") {
-    _impl::CollectionChangeBuilder c;
-
-    SECTION("is just erase when row == last_row") {
-        c.move_over(10, 10);
-        c.parse_complete();
-
-        REQUIRE_INDICES(c.deletions, 10);
-        REQUIRE(c.insertions.empty());
-        REQUIRE(c.moves.empty());
-    }
-
-    SECTION("is just erase when row + 1 == last_row") {
-        c.move_over(0, 6);
-        c.move_over(4, 5);
-        c.move_over(0, 4);
-        c.move_over(2, 3);
-        c.parse_complete();
-        c.clean_up_stale_moves();
-
-        REQUIRE_INDICES(c.deletions, 0, 2, 4, 5, 6);
-        REQUIRE_INDICES(c.insertions, 0);
-        REQUIRE_MOVES(c, {5, 0});
-    }
-
-    SECTION("marks the old last row as moved") {
-        c.move_over(5, 8);
-        c.parse_complete();
-        REQUIRE_MOVES(c, {8, 5});
-    }
-
-    SECTION("does not mark the old last row as moved if it was newly inserted") {
-        c.insert(8);
-        c.move_over(5, 8);
-        c.parse_complete();
-        REQUIRE(c.moves.empty());
-    }
-
-    SECTION("removes previous modifications for the removed row") {
-        c.modify(5, 0);
-        c.move_over(5, 8);
-        c.parse_complete();
-        REQUIRE(c.modifications.empty());
-        REQUIRE(c.columns[0].empty());
-    }
-
-    SECTION("updates previous insertions for the old last row") {
-        c.insert(5);
-        c.move_over(3, 5);
-        c.parse_complete();
-        REQUIRE_INDICES(c.insertions, 3);
-    }
-
-    SECTION("updates previous modifications for the old last row") {
-        c.modify(5, 0);
-        c.move_over(3, 5);
-        c.parse_complete();
-        REQUIRE_INDICES(c.modifications, 3);
-        REQUIRE_COLUMN_INDICES(c.columns, 0, 3);
-    }
-
-    SECTION("removes moves to the target") {
-        c.move_over(5, 10);
-        c.move_over(5, 8);
-        c.parse_complete();
-        REQUIRE_MOVES(c, {8, 5});
-    }
-
-    SECTION("updates moves to the source") {
-        c.move_over(8, 10);
-        c.move_over(5, 8);
-        c.parse_complete();
-        REQUIRE_MOVES(c, {10, 5});
-    }
-
-    SECTION("removes moves to the row when row == last_row") {
-        c.move_over(0, 1);
-        c.move_over(0, 0);
-        c.parse_complete();
-
-        REQUIRE_INDICES(c.deletions, 0, 1);
-        REQUIRE(c.insertions.empty());
-        REQUIRE(c.moves.empty());
-    }
-
-    SECTION("is not shifted by previous calls to move_over()") {
-        c.move_over(5, 10);
-        c.move_over(6, 9);
-        c.parse_complete();
-        REQUIRE_INDICES(c.deletions, 5, 6, 9, 10);
-        REQUIRE_INDICES(c.insertions, 5, 6);
-        REQUIRE_MOVES(c, {9, 6}, {10, 5});
-    }
-
-    SECTION("marks the moved-over row as deleted when chaining moves") {
-        c.move_over(5, 10);
-        c.move_over(0, 5);
-        c.parse_complete();
-
-        REQUIRE_INDICES(c.deletions, 0, 5, 10);
-        REQUIRE_INDICES(c.insertions, 0);
-        REQUIRE_MOVES(c, {10, 0});
-    }
-}
-
 TEST_CASE("collection_change: clear()") {
     _impl::CollectionChangeBuilder c;
 
@@ -311,15 +191,6 @@ TEST_CASE("collection_change: clear()") {
         c.clear(5);
         REQUIRE_INDICES(c.deletions, 0, 1, 2, 3, 4, 5, 6, 7);
     }
-
-    SECTION("sets deletions SIZE_T_MAX if that if the given previous size") {
-        c.insertions = {1, 2, 3};
-        c.clear(std::numeric_limits<size_t>::max());
-        REQUIRE(!c.deletions.empty());
-        REQUIRE(++c.deletions.begin() == c.deletions.end());
-        REQUIRE(c.deletions.begin()->first == 0);
-        REQUIRE(c.deletions.begin()->second == std::numeric_limits<size_t>::max());
-    }
 }
 
 TEST_CASE("collection_change: move()") {
@@ -393,220 +264,80 @@ TEST_CASE("collection_change: move()") {
     }
 }
 
-TEST_CASE("collection_change: swap()") {
-    _impl::CollectionChangeBuilder c;
-
-    SECTION("marks both as inserted and deleted") {
-        c.swap(6, 10);
-        c.parse_complete();
-        REQUIRE_INDICES(c.insertions, 6, 10);
-        REQUIRE_INDICES(c.deletions, 6, 10);
-    }
-
-    SECTION("does not mark new insertions as deleted") {
-        c.insert(10);
-        c.swap(5, 10);
-        c.parse_complete();
-        REQUIRE_INDICES(c.insertions, 5, 10);
-        REQUIRE_INDICES(c.deletions, 5);
-    }
-
-    SECTION("adds a pair of moves to represent the swap") {
-        c.swap(5, 10);
-        c.parse_complete();
-        REQUIRE_MOVES(c, {5, 10}, {10, 5});
-    }
-
-    SECTION("collapses away when swapped twice") {
-        c.swap(5, 10);
-        c.swap(5, 10);
-        c.parse_complete();
-        REQUIRE(c.empty());
-    }
-
-    SECTION("updates previous moves to one of the swapped rows") {
-        c.move_over(5, 8);
-        c.swap(3, 5);
-        c.parse_complete();
-        REQUIRE_MOVES(c, {3, 5}, {8, 3});
-    }
-
-    SECTION("does not report a move for newly inserted rows") {
-        c.insert(5);
-        c.swap(5, 10);
-        c.parse_complete();
-        REQUIRE_INDICES(c.insertions, 5, 10);
-        REQUIRE_MOVES(c, {9, 5});
-
-        c = {};
-        c.insert(10);
-        c.swap(5, 10);
-        c.parse_complete();
-        REQUIRE_INDICES(c.insertions, 5, 10);
-        REQUIRE_MOVES(c, {5, 10});
-
-        c = {};
-        c.insert(5);
-        c.insert(10);
-        c.swap(5, 10);
-        c.parse_complete();
-        REQUIRE_INDICES(c.insertions, 5, 10);
-        REQUIRE(c.moves.empty());
-    }
-
-    SECTION("updates previous modifications") {
-        c.modify(6, 0);
-
-        c.swap(6, 10);
-        REQUIRE_INDICES(c.modifications, 10);
-        REQUIRE_COLUMN_INDICES(c.columns, 0, 10);
-
-        c.swap(10, 6);
-        REQUIRE_INDICES(c.modifications, 6);
-        REQUIRE_COLUMN_INDICES(c.columns, 0, 6);
-    }
-}
-
-TEST_CASE("collection_change: subsume()") {
-    _impl::CollectionChangeBuilder c;
-
-    auto subsume = [&](size_t ndx1, size_t ndx2) {
-        c.insert(ndx2);
-        c.insert(ndx2 + 1);
-        c.subsume(ndx1, ndx2);
-        c.move_over(ndx1, ndx2 + 1);
-        c.parse_complete();
-    };
-
-    SECTION("adds a move from the old to the new row") {
-        subsume(5, 10);
-        REQUIRE_MOVES(c, {5, 10});
-    }
-
-    SECTION("updates moves to the old row") {
-        c.move_over(5, 10);
-        c.insert(10);
-        c.insert(11);
-        c.insert(12);
-        c.subsume(5, 11);
-        c.move_over(5, 12);
-        c.parse_complete();
-
-        REQUIRE_MOVES(c, {10, 11});
-        REQUIRE_INDICES(c.insertions, 5, 10, 11);
-    }
-
-    SECTION("collapses away to nothing when the subsuming row is the last one") {
-        c.insert(10);
-        c.subsume(5, 10);
-        c.move_over(5, 10);
-        c.parse_complete();
-        REQUIRE(c.empty());
-    }
-
-    SECTION("marks the new as modified if the old was") {
-        subsume(5, 10);
-        REQUIRE(c.modifications.empty());
-
-        c.modify(3, 0);
-        subsume(3, 15);
-        REQUIRE_INDICES(c.modifications, 15);
-        REQUIRE_COLUMN_INDICES(c.columns, 0, 15);
-    }
-
-    SECTION("leaves the new row marked as modified if it already was") {
-        c.insert(6);
-        c.insert(7);
-        c.modify(6, 0);
-
-        c.subsume(5, 6);
-        c.move_over(5, 7);
-        REQUIRE_INDICES(c.modifications, 6);
-        REQUIRE_COLUMN_INDICES(c.columns, 0, 6);
-    }
-}
-
-TEST_CASE("collection_change: calculate() unsorted") {
+TEST_CASE("collection_change: calculate() table order") {
     _impl::CollectionChangeBuilder c;
 
     auto all_modified = [](size_t) { return true; };
     auto none_modified = [](size_t) { return false; };
-    const auto npos = size_t(-1);
-    IndexSet empty;
+    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, empty);
+        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, 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, 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({npos, 1, 2, 3, npos}, {1, 2, 3}, all_modified, empty);
+        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, empty);
+        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, empty);
+        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, empty);
+        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, empty);
+        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, empty);
+        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, empty);
+        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, empty);
+        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, empty);
+        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, empty);
+        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());
     }
 
-    IndexSet all = {0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 10};
-    SECTION("marks rows as modified even if they moved") {
-        c = _impl::CollectionChangeBuilder::calculate({5, 3}, {3, 5}, all_modified, all);
-        REQUIRE_MOVES(c, {1, 0});
-        REQUIRE_INDICES(c.modifications, 0, 1);
-    }
-
     SECTION("does not mark rows as modified if they are new") {
-        c = _impl::CollectionChangeBuilder::calculate({3}, {3, 5}, all_modified, all);
+        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<size_t> values) {
+        auto calc = [&](std::vector<int64_t> values) {
             return _impl::CollectionChangeBuilder::calculate(values, {1, 2, 3}, none_modified, all);
         };
 
@@ -624,6 +355,7 @@ TEST_CASE("collection_change: calculate() unsorted") {
         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") {
@@ -631,7 +363,7 @@ TEST_CASE("collection_change: calculate() sorted") {
 
     auto all_modified = [](size_t) { return true; };
     auto none_modified = [](size_t) { return false; };
-    const auto npos = size_t(-1);
+    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);
@@ -940,7 +672,7 @@ TEST_CASE("collection_change: merge()") {
 
     SECTION("is a no-op if the new set is empty") {
         c = {{1, 2, 3}, {4, 5}, {6, 7}, {{8, 9}}};
-        c.columns = {{6}, {7}};
+        c.columns = {{0, {6}}, {1, {7}}};
         c.merge({});
         REQUIRE_INDICES(c.deletions, 1, 2, 3, 8);
         REQUIRE_INDICES(c.insertions, 4, 5, 9);
@@ -1194,7 +926,7 @@ TEST_CASE("collection_change: merge()") {
     SECTION("column-level modifications") {
         _impl::CollectionChangeBuilder c2;
         c = {{}, {}, {1, 2, 3}, {}};
-        c.columns = {{1}, {2, 3}};
+        c.columns = {{0, {1}}, {1, {2, 3}}};
 
         SECTION("preserved on no-op merges") {
             c.merge({});
@@ -1208,7 +940,7 @@ TEST_CASE("collection_change: merge()") {
 
         SECTION("merged with other column-level modifications") {
             c2.modifications = {0, 4};
-            c2.columns = {{1, 2}, {}, {4}};
+            c2.columns = {{0, {1, 2}}, {2, {4}}};
             c.merge(std::move(c2));
 
             REQUIRE_COLUMN_INDICES(c.columns, 0, 1, 2);
@@ -1229,9 +961,5 @@ TEST_CASE("collection_change: merge()") {
             REQUIRE_COLUMN_INDICES(c.columns, 0, 1);
             REQUIRE_COLUMN_INDICES(c.columns, 1, 2, 4);
         }
-
-        SECTION("updated by moves") {
-
-        }
     }
 }

+ 151 - 183
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/list.cpp

@@ -32,9 +32,8 @@
 #include "impl/realm_coordinator.hpp"
 #include "impl/object_accessor_impl.hpp"
 
-#include <realm/group_shared.hpp>
-#include <realm/link_view.hpp>
 #include <realm/version.hpp>
+#include <realm/db.hpp>
 
 #include <cstdint>
 
@@ -43,7 +42,6 @@ using namespace realm;
 TEST_CASE("list") {
     InMemoryTestFile config;
     config.automatic_change_notifications = false;
-    config.cache = false;
     auto r = Realm::get_shared_realm(config);
     r->update_schema({
         {"origin", {
@@ -61,45 +59,50 @@ TEST_CASE("list") {
         }},
     });
 
-    auto& coordinator = *_impl::RealmCoordinator::get_existing_coordinator(config.path);
+    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();
 
-    target->add_empty_row(10);
+    std::vector<ObjKey> target_keys;
+    target->create_objects(10, target_keys);
     for (int i = 0; i < 10; ++i)
-        target->set_int(0, i, i);
+        target->get_object(target_keys[i]).set_all(i);
 
-    origin->add_empty_row(2);
-    origin->set_int_unique(0, 0, 1);
-    origin->set_int_unique(0, 1, 2);
-    LinkViewRef lv = origin->get_linklist(1, 0);
+    Obj obj = origin->create_object();
+    auto lv = obj.get_linklist_ptr(col_link);
     for (int i = 0; i < 10; ++i)
-        lv->add(i);
-    LinkViewRef lv2 = origin->get_linklist(1, 1);
+        lv->add(target_keys[i]);
+    auto lv2 = origin->create_object().get_linklist_ptr(col_link);
     for (int i = 0; i < 10; ++i)
-        lv2->add(i);
+        lv2->add(target_keys[i]);
 
-    other_origin->add_empty_row();
-    other_target->add_empty_row(10);
+    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->set_int(0, i, i);
-    LinkViewRef other_lv = other_origin->get_linklist(0, 0);
+        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(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_linklist(1, 0);
+    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, lv);
+        List lst(r, obj, col_link);
 
         auto write = [&](auto&& f) {
             r->begin_transaction();
@@ -129,23 +132,23 @@ TEST_CASE("list") {
 
         SECTION("modifying the list sends a change notifications") {
             auto token = require_change();
-            write([&] { lst.remove(5); });
+            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([&] { lv2->remove(5); });
+            write([&] { if (lv2->size() > 5) lv2->remove(5); });
         }
 
         SECTION("deleting the list sends a change notification") {
             auto token = require_change();
-            write([&] { origin->move_last_over(0); });
+            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->add_empty_row(); });
+            write([&] { target->create_object(); });
             REQUIRE(change.empty());
         }
 
@@ -154,27 +157,28 @@ TEST_CASE("list") {
                 change = c;
             });
             advance_and_notify(*r);
-            write([&] { origin->move_last_over(0); });
+            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_int(0, 6); });
+            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->move_last_over(5); });
+            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([&] {
-                lst.add(5);
-                target->set_int(0, 5, 10);
+                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);
@@ -183,7 +187,7 @@ TEST_CASE("list") {
         SECTION("modifying and then moving a row reports move/insert but not modification") {
             auto token = require_change();
             write([&] {
-                target->set_int(0, 5, 10);
+                target->get_object(target_keys[5]).set(col_value, 10);
                 lst.move(5, 8);
             });
             REQUIRE_INDICES(change.insertions, 8);
@@ -194,21 +198,21 @@ TEST_CASE("list") {
 
         SECTION("modifying a row which appears multiple times in a list marks them all as modified") {
             r->begin_transaction();
-            lst.add(5);
+            lst.add(target_keys[5]);
             r->commit_transaction();
 
             auto token = require_change();
-            write([&] { target->set_int(0, 5, 10); });
+            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(5);
+            lst.add(target_keys[5]);
             r->commit_transaction();
 
             auto token = require_change();
-            write([&] { target->move_last_over(5); });
+            write([&] { target->remove_object(target_keys[5]); });
             REQUIRE_INDICES(change.deletions, 5, 10);
         }
 
@@ -221,30 +225,32 @@ TEST_CASE("list") {
         SECTION("moving a target row does not send a change notification") {
             // Remove a row from the LV so that we have one to delete that's not in the list
             r->begin_transaction();
-            lv->remove(2);
+            if (lv->size() > 2)
+                lv->remove(2);
             r->commit_transaction();
 
             auto token = require_no_change();
-            write([&] { target->move_last_over(2); });
+            write([&] { target->remove_object(target_keys[2]); });
         }
 
-        SECTION("multiple LinkViws for the same LinkList can get notifications") {
+        SECTION("multiple LinkViews for the same LinkList can get notifications") {
             r->begin_transaction();
             target->clear();
-            target->add_empty_row(5);
+            std::vector<ObjKey> keys;
+            target->create_objects(5, keys);
             r->commit_transaction();
 
             auto get_list = [&] {
                 auto r = Realm::get_shared_realm(config);
-                auto lv = r->read_group().get_table("class_origin")->get_linklist(1, 0);
-                return List(r, lv);
+                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->set_int(0, lv->size() - 1, lv->size());
+                    target->get_object(lv->size() - 1).set(col_value, int64_t(lv->size()));
                 }
-                lv->add(lv->size());
+                lv->add(keys[lv->size()]);
                 r->commit_transaction();
             };
 
@@ -291,7 +297,7 @@ TEST_CASE("list") {
             auto token2 = require_change();
 
             r->begin_transaction();
-            lv->add(0);
+            lv->add(target_keys[0]);
             token.suppress_next();
             r->commit_transaction();
 
@@ -302,14 +308,14 @@ TEST_CASE("list") {
         SECTION("multiple Lists for the same LinkView can be skipped individually") {
             auto token = require_no_change();
 
-            List list2(r, lv);
+            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(0);
+            lv->add(target_keys[0]);
             token.suppress_next();
             r->commit_transaction();
 
@@ -329,7 +335,7 @@ TEST_CASE("list") {
 
             // should now produce a notification
             r->begin_transaction();
-            lv->add(0);
+            lv->add(target_keys[0]);
             r->commit_transaction();
             advance_and_notify(*r);
             REQUIRE_INDICES(change.insertions, 10);
@@ -337,11 +343,11 @@ TEST_CASE("list") {
 
         SECTION("modifying a different table does not send a change notification") {
             auto token = require_no_change();
-            write([&] { other_lv->add(0); });
+            write([&] { other_lv->add(other_target_keys[0]); });
         }
 
         SECTION("changes are reported correctly for multiple tables") {
-            List list2(r, other_lv);
+            List list2(r, *other_lv);
             CollectionChangeSet other_changes;
             auto token1 = list2.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) {
                 other_changes = std::move(c);
@@ -349,28 +355,29 @@ TEST_CASE("list") {
             auto token2 = require_change();
 
             write([&] {
-                lv->add(1);
+                lv->add(target_keys[1]);
 
-                other_origin->insert_empty_row(0);
-                other_lv->insert(1, 0);
+                other_origin->create_object();
+                if (other_lv->size() > 0)
+                    other_lv->insert(1, other_target_keys[0]);
 
-                lv->add(2);
+                lv->add(target_keys[2]);
             });
             REQUIRE_INDICES(change.insertions, 10, 11);
             REQUIRE_INDICES(other_changes.insertions, 1);
 
             write([&] {
-                lv->add(3);
-                other_origin->move_last_over(1);
-                lv->add(4);
+                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(5);
+                lv->add(target_keys[5]);
                 other_origin->clear();
-                lv->add(6);
+                lv->add(target_keys[6]);
             });
             REQUIRE_INDICES(change.insertions, 14, 15);
         }
@@ -385,19 +392,19 @@ TEST_CASE("list") {
             });
 
             r2->begin_transaction();
-            r2->read_group().get_table("class_target")->set_int(0, 0, 10);
-            r2->read_group().get_table("class_other_target")->set_int(0, 1, 10);
+            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_linklist(0, 0));
+            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")->set_int(0, 2, 10);
-            r3->read_group().get_table("class_other_target")->set_int(0, 3, 10);
+            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);
@@ -411,8 +418,8 @@ TEST_CASE("list") {
             auto token = require_change();
 
             r2->begin_transaction();
-            r2_lv->get(5).set_int(0, 10);
-            r2_lv->get(1).set_int(0, 10);
+            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();
@@ -420,7 +427,8 @@ TEST_CASE("list") {
             coordinator.on_change();
 
             r2->begin_transaction();
-            r2_lv->move(8, 5);
+            if (r2_lv->size() > 8)
+                r2_lv->move(8, 5);
             r2->commit_transaction();
             advance_and_notify(*r);
 
@@ -430,46 +438,6 @@ TEST_CASE("list") {
             REQUIRE_MOVES(change, {1, 2});
         }
 
-        SECTION("moving the list's containing row does not break notifications") {
-            auto token = require_change();
-
-            // insert rows before it
-            write([&] {
-                origin->insert_empty_row(0, 2);
-                lv->add(1);
-            });
-            REQUIRE_INDICES(change.insertions, 10);
-            REQUIRE(lst.size() == 11);
-            REQUIRE(lst.get(10).get_index() == 1);
-
-            // swap the row containing it with another row
-            write([&] {
-                origin->swap_rows(2, 3);
-                lv->add(2);
-            });
-            REQUIRE_INDICES(change.insertions, 11);
-            REQUIRE(lst.size() == 12);
-            REQUIRE(lst.get(11).get_index() == 2);
-
-            // swap it back to verify both of the rows in the swap are handled
-            write([&] {
-                origin->swap_rows(2, 3);
-                lv->add(3);
-            });
-            REQUIRE_INDICES(change.insertions, 12);
-            REQUIRE(lst.size() == 13);
-            REQUIRE(lst.get(12).get_index() == 3);
-
-            // delete a row so that it's moved (as it's at the end)
-            write([&] {
-                origin->move_last_over(0);
-                lv->add(4);
-            });
-            REQUIRE_INDICES(change.insertions, 13);
-            REQUIRE(lst.size() == 14);
-            REQUIRE(lst.get(13).get_index() == 4);
-        }
-
         SECTION("changes are sent in initial notification") {
             auto token = lst.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) {
                 change = c;
@@ -530,8 +498,8 @@ TEST_CASE("list") {
     }
 
     SECTION("sorted add_notification_block()") {
-        List lst(r, lv);
-        Results results = lst.sort({*target, {{0}}, {false}});
+        List lst(r, *lv);
+        Results results = lst.sort({{{col_value}}, {false}});
 
         int notification_calls = 0;
         CollectionChangeSet change;
@@ -550,12 +518,11 @@ TEST_CASE("list") {
 
             advance_and_notify(*r);
         };
-
         SECTION("add duplicates") {
             write([&] {
-                lst.add(5);
-                lst.add(5);
-                lst.add(5);
+                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);
@@ -563,7 +530,7 @@ TEST_CASE("list") {
 
         SECTION("change order by modifying target") {
             write([&] {
-                lst.get(5).set_int(0, 15);
+                lst.get(5).set(col_value, 15);
             });
             REQUIRE(notification_calls == 2);
             REQUIRE_INDICES(change.deletions, 4);
@@ -586,8 +553,8 @@ TEST_CASE("list") {
     }
 
     SECTION("filtered add_notification_block()") {
-        List lst(r, lv);
-        Results results = lst.filter(target->where().less(0, 9));
+        List lst(r, *lv);
+        Results results = lst.filter(target->where().less(col_value, 9));
 
         int notification_calls = 0;
         CollectionChangeSet change;
@@ -606,12 +573,11 @@ TEST_CASE("list") {
 
             advance_and_notify(*r);
         };
-
         SECTION("add duplicates") {
             write([&] {
-                lst.add(5);
-                lst.add(5);
-                lst.add(5);
+                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);
@@ -652,50 +618,50 @@ TEST_CASE("list") {
 
     SECTION("sort()") {
         auto objectschema = &*r->schema().find("target");
-        List list(r, lv);
-        auto results = list.sort({*target, {{0}}, {false}});
+        List list(r, *lv);
+        auto results = list.sort({{{col_value}}, {false}});
 
         REQUIRE(&results.get_object_schema() == objectschema);
-        REQUIRE(results.get_mode() == Results::Mode::LinkView);
+        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(0) == 45);
+        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({*target, {{0}}, {false}});
+        results = list.sort({{{col_value}}, {false}});
         for (size_t i = 0; i < 10; ++i)
-            REQUIRE(results.get(i).get_index() == 9 - 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({*target, {}, {}});
+        results = list.sort(SortDescriptor());
         for (size_t i = 0; i < 10; ++i)
-            REQUIRE(results.get(i).get_index() == 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::LinkView);
+        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(0, 5));
+        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_index() == i + 6);
+            REQUIRE(results.get(i).get_key().value == i + 6);
         }
     }
 
     SECTION("snapshot()") {
         auto objectschema = &*r->schema().find("target");
-        List list(r, lv);
+        List list(r, *lv);
 
         auto snapshot = list.snapshot();
         REQUIRE(&snapshot.get_object_schema() == objectschema);
@@ -708,30 +674,30 @@ TEST_CASE("list") {
         }
         REQUIRE(snapshot.size() == 10);
         for (size_t i = 0; i < snapshot.size(); ++i) {
-            REQUIRE(snapshot.get(i).is_attached());
+            REQUIRE(snapshot.get(i).is_valid());
         }
         for (size_t i = 0; i < 5; ++i) {
-            target->move_last_over(i);
+            target->remove_object(target_keys[i]);
         }
         REQUIRE(snapshot.size() == 10);
         for (size_t i = 0; i < 5; ++i) {
-            REQUIRE(!snapshot.get(i).is_attached());
+            REQUIRE(!snapshot.get(i).is_valid());
         }
         for (size_t i = 5; i < 10; ++i) {
-            REQUIRE(snapshot.get(i).is_attached());
+            REQUIRE(snapshot.get(i).is_valid());
         }
-        list.add(0);
+        list.add(target_keys[5]);
         REQUIRE(snapshot.size() == 10);
     }
 
     SECTION("get_object_schema()") {
-        List list(r, lv);
+        List list(r, *lv);
         auto objectschema = &*r->schema().find("target");
         REQUIRE(&list.get_object_schema() == objectschema);
     }
 
     SECTION("delete_at()") {
-        List list(r, lv);
+        List list(r, *lv);
         r->begin_transaction();
         auto initial_view_size = lv->size();
         auto initial_target_size = target->size();
@@ -742,7 +708,7 @@ TEST_CASE("list") {
     }
 
     SECTION("delete_all()") {
-        List list(r, lv);
+        List list(r, *lv);
         r->begin_transaction();
         list.delete_all();
         REQUIRE(lv->size() == 0);
@@ -751,7 +717,7 @@ TEST_CASE("list") {
     }
 
     SECTION("as_results().clear()") {
-        List list(r, lv);
+        List list(r, *lv);
         r->begin_transaction();
         list.as_results().clear();
         REQUIRE(lv->size() == 0);
@@ -760,7 +726,7 @@ TEST_CASE("list") {
     }
 
     SECTION("snapshot().clear()") {
-        List list(r, lv);
+        List list(r, *lv);
         r->begin_transaction();
         auto snapshot = list.snapshot();
         snapshot.clear();
@@ -772,153 +738,155 @@ TEST_CASE("list") {
     }
 
     SECTION("add(RowExpr)") {
-        List list(r, lv);
+        List list(r, *lv);
         r->begin_transaction();
         SECTION("adds rows from the correct table") {
-            list.add(target->get(5));
+            list.add(target_keys[5]);
             REQUIRE(list.size() == 11);
-            REQUIRE(list.get(10).get_index() == 5);
+            REQUIRE(list.get(10).get_key() == target_keys[5]);
         }
 
         SECTION("throws for rows from the wrong table") {
-            REQUIRE_THROWS(list.add(origin->get(0)));
+            REQUIRE_THROWS(list.add(obj));
         }
         r->cancel_transaction();
     }
 
     SECTION("insert(RowExpr)") {
-        List list(r, lv);
+        List list(r, *lv);
         r->begin_transaction();
 
         SECTION("insert rows from the correct table") {
-            list.insert(0, target->get(5));
+            list.insert(0, target_keys[5]);
             REQUIRE(list.size() == 11);
-            REQUIRE(list.get(0).get_index() == 5);
+            REQUIRE(list.get(0).get_key() == target_keys[5]);
         }
 
         SECTION("throws for rows from the wrong table") {
-            REQUIRE_THROWS(list.insert(0, origin->get(0)));
+            REQUIRE_THROWS(list.insert(0, obj));
         }
 
         SECTION("throws for out of bounds insertions") {
-            REQUIRE_THROWS(list.insert(11, target->get(5)));
-            REQUIRE_NOTHROW(list.insert(10, target->get(5)));
+            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);
+        List list(r, *lv);
         r->begin_transaction();
 
         SECTION("assigns for rows from the correct table") {
-            list.set(0, target->get(5));
+            list.set(0, target_keys[5]);
             REQUIRE(list.size() == 10);
-            REQUIRE(list.get(0).get_index() == 5);
+            REQUIRE(list.get(0).get_key() == target_keys[5]);
         }
 
         SECTION("throws for rows from the wrong table") {
-            REQUIRE_THROWS(list.set(0, origin->get(0)));
+            REQUIRE_THROWS(list.set(0, obj));
         }
 
         SECTION("throws for out of bounds sets") {
-            REQUIRE_THROWS(list.set(10, target->get(5)));
+            REQUIRE_THROWS(list.set(10, target_keys[5]));
         }
         r->cancel_transaction();
     }
 
     SECTION("find(RowExpr)") {
-        List list(r, lv);
+        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(target->get(5)) == 5);
+            REQUIRE(list.find(obj5) == 5);
         }
 
         SECTION("returns index in list and not index in table") {
             r->begin_transaction();
             list.remove(1);
-            REQUIRE(list.find(target->get(5)) == 4);
-            REQUIRE(list.as_results().index_of(target->get(5)) == 4);
+            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(target->get(1)) == npos);
-            REQUIRE(list.as_results().index_of(target->get(1)) == npos);
+            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(origin->get(0)));
-            REQUIRE_THROWS(list.as_results().index_of(origin->get(0)));
+            REQUIRE_THROWS(list.find(obj));
+            REQUIRE_THROWS(list.as_results().index_of(obj));
         }
     }
 
     SECTION("find(Query)") {
-        List list(r, lv);
+        List list(r, *lv);
 
         SECTION("returns index in list for values in the list") {
-            REQUIRE(list.find(std::move(target->where().equal(0, 5))) == 5);
+            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(0, 5))) == 4);
+            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(0, 11))) == npos);
+            REQUIRE(list.find(std::move(target->where().equal(col_value, 11))) == npos);
         }
     }
 
     SECTION("add(Context)") {
-        List list(r, lv);
+        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(5)));
+            list.add(ctx, util::Any(target->get_object(target_keys[5])));
             REQUIRE(list.size() == 11);
-            REQUIRE(list.get(10).get_index() == 5);
+            REQUIRE(list.get(10).get_key().value == 5);
         }
 
         SECTION("adds boxed realm::Object") {
-            realm::Object obj(r, list.get_object_schema(), target->get(5));
+            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_index() == 5);
+            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(0) == 20);
+            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(0))));
-            realm::Object obj(r, *r->schema().find("origin"), origin->get(0));
-            REQUIRE_THROWS(list.add(ctx, util::Any(obj)));
+            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);
+        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(5))) == 5);
+            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(5));
+            realm::Object obj(r, *r->schema().find("origin"), target->get_object(target_keys[5]));
             REQUIRE(list.find(ctx, util::Any(obj)) == 5);
         }
 
@@ -928,17 +896,17 @@ TEST_CASE("list") {
         }
 
         SECTION("throws for object in wrong table") {
-            REQUIRE_THROWS(list.find(ctx, util::Any(origin->get(0))));
+            REQUIRE_THROWS(list.find(ctx, util::Any(obj)));
         }
     }
 
     SECTION("get(Context)") {
-        List list(r, lv);
+        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.row().get_index() == 1);
+        REQUIRE(obj.obj().get_key() == target_keys[1]);
     }
 }

+ 4 - 4
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/main.cpp

@@ -16,9 +16,6 @@
 //
 ////////////////////////////////////////////////////////////////////////////
 
-// FIXME: the nextafter define below can be removed once we upgrade to an
-// Android ndk version which has support for std::nextafter (absent in ndk r10e)
-#define CATCH_CONFIG_GLOBAL_NEXTAFTER
 #define CATCH_CONFIG_RUNNER
 #include "catch2/catch.hpp"
 
@@ -42,7 +39,10 @@ int main(int argc, char** argv) {
     SetCurrentDirectoryA(path);
 #else
     char executable[PATH_MAX];
-    realpath(argv[0], executable);
+    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

+ 120 - 141
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/migrations.cpp

@@ -27,7 +27,6 @@
 
 #include "impl/object_accessor_impl.hpp"
 
-#include <realm/descriptor.hpp>
 #include <realm/group.hpp>
 #include <realm/table.hpp>
 #include <realm/util/scope_exit.hpp>
@@ -38,11 +37,11 @@
 
 using namespace realm;
 
-#define VERIFY_SCHEMA(r) verify_schema((r), __LINE__)
+#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); \
+    VERIFY_SCHEMA(r, false); \
     REQUIRE((r).schema() == s); \
 } while (0)
 
@@ -59,21 +58,28 @@ using namespace realm;
 } while (0)
 
 namespace {
-void verify_schema(Realm& r, int line)
+void verify_schema(Realm& r, int line, bool in_migration)
 {
     CAPTURE(line);
     for (auto&& object_schema : r.schema()) {
-        auto table = ObjectStore::table_for_object_type(r.read_group(), object_schema.name);
+        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 = ObjectStore::get_primary_key_for_object(r.read_group(), object_schema.name);
-        REQUIRE(primary_key == object_schema.primary_key);
+        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) {
-            size_t col = table->get_column_index(prop.name);
+            auto col = table->get_column_key(prop.name);
             CAPTURE(prop.name);
-            REQUIRE(col != npos);
-            REQUIRE(col == prop.table_column);
-            REQUIRE(to_underlying(ObjectSchema::from_core_type(*table->get_descriptor(), col)) ==
+            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));
@@ -158,6 +164,11 @@ Schema set_primary_key(Schema schema, StringData object_name, StringData new_pri
     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") {
@@ -283,7 +294,7 @@ TEST_CASE("migration: Automatic") {
             REQUIRE((*realm).schema() == schema1);
             REQUIRE_NOTHROW((*realm).update_schema(schema2, 1,
                             [](SharedRealm, SharedRealm, Schema&) { /* empty but present migration handler */ }));
-            VERIFY_SCHEMA(*realm);
+            VERIFY_SCHEMA(*realm, false);
             REQUIRE((*realm).schema() == schema2);
         }
 
@@ -433,7 +444,7 @@ TEST_CASE("migration: Automatic") {
             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");
-                table->add_empty_row(2);
+                create_objects(*table, 2);
             }));
         }
 
@@ -446,8 +457,10 @@ TEST_CASE("migration: Automatic") {
             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");
-            table->add_empty_row(2);
+            create_objects(*table, 2);
+            realm->commit_transaction();
 
             schema = set_primary_key(schema, "object", "value");
             REQUIRE_THROWS(realm->update_schema(schema, 2, nullptr));
@@ -466,7 +479,7 @@ TEST_CASE("migration: Automatic") {
 
             REQUIRE_THROWS(realm->update_schema(schema2, 2, [](SharedRealm, SharedRealm realm, Schema&) {
                 auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
-                table->add_empty_row();
+                table->create_object();
                 throw 5;
             }));
 
@@ -489,7 +502,7 @@ TEST_CASE("migration: Automatic") {
 
             realm->begin_transaction();
             auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
-            table->add_empty_row(10);
+            create_objects(*table, 10);
             realm->commit_transaction();
 
             schema = set_type(schema, "object", "value", PropertyType::Float);
@@ -508,14 +521,16 @@ TEST_CASE("migration: Automatic") {
 
             realm->begin_transaction();
             auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
-            table->add_empty_row(10);
-            for (size_t i = 0; i < 10; ++i)
-                table->set_int(0, i, i);
+            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_int(0, i) == i);
+                REQUIRE(table->get_object(i).get<util::Optional<int64_t>>(key) == i);
         }
 
         SECTION("values for nullable properties are discarded when converitng to required") {
@@ -529,14 +544,16 @@ TEST_CASE("migration: Automatic") {
 
             realm->begin_transaction();
             auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
-            table->add_empty_row(10);
-            for (size_t i = 0; i < 10; ++i)
-                table->set_int(0, i, i);
+            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_int(0, i) == 0);
+                REQUIRE(table->get_object(i).get<int64_t>(key) == 0);
         }
 
         SECTION("deleting table removed from the schema deletes it") {
@@ -564,7 +581,7 @@ TEST_CASE("migration: Automatic") {
             realm->update_schema(schema, 1);
 
             realm->begin_transaction();
-            ObjectStore::table_for_object_type(realm->read_group(), "object")->add_empty_row();
+            ObjectStore::table_for_object_type(realm->read_group(), "object")->create_object();
             realm->commit_transaction();
 
             realm->update_schema(schema, 2, [](SharedRealm, SharedRealm realm, Schema&) {
@@ -588,32 +605,6 @@ TEST_CASE("migration: Automatic") {
                 ObjectStore::delete_data_for_object(realm->read_group(), "foo");
             }));
         }
-
-        SECTION("subtables columns are not modified by unrelated changes") {
-            config.in_memory = false;
-            Schema schema = {
-                {"object", {
-                    {"value", PropertyType::Int},
-                }},
-            };
-
-            {
-                auto realm = Realm::get_shared_realm(config);
-                realm->update_schema(schema, 1);
-                realm->begin_transaction();
-                get_table(realm, "object")->add_column(type_Table, "subtable");
-                realm->commit_transaction();
-            }
-            // close and reopen the Realm to ensure it rereads the schema from
-            // the group
-
-            auto realm = Realm::get_shared_realm(config);
-            realm->update_schema(add_property(schema, "object", {"value 2", PropertyType::Int}), 2);
-
-            auto& table = *get_table(realm, "object");
-            REQUIRE(table.get_column_type(1) == type_Table);
-            REQUIRE(table.get_column_count() == 3);
-        }
     }
 
     SECTION("schema correctness during migration") {
@@ -642,11 +633,11 @@ TEST_CASE("migration: Automatic") {
         REQUIRE(old_realm->schema() == schema); \
         REQUIRE(new_realm->schema_version() == 1); \
         REQUIRE(new_realm->schema() == new_schema); \
-        VERIFY_SCHEMA(*old_realm); \
-        VERIFY_SCHEMA(*new_realm); \
+        VERIFY_SCHEMA(*old_realm, true); \
+        VERIFY_SCHEMA(*new_realm, true); \
     }); \
     REQUIRE(realm->schema() == new_schema); \
-    VERIFY_SCHEMA(*realm); \
+    VERIFY_SCHEMA(*realm, false); \
 } while (false)
 
         SECTION("add new table") {
@@ -875,16 +866,16 @@ TEST_CASE("migration: Automatic") {
                 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, "link target", get_table(new_realm, "link target")->add_empty_row());
+                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")).row().get_index()
-                        == linked_obj.row().get_index());
+                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")).row().get_index()
-                        == new_obj.row().get_index());
+                REQUIRE(any_cast<Object>(obj.get_property_value<util::Any>(ctx, "object")).obj().get_key()
+                        == new_obj.obj().get_key());
 
                 REQUIRE(linking.size() == 0);
             });
@@ -920,6 +911,8 @@ TEST_CASE("migration: Automatic") {
 
         SECTION("change primary key property type") {
             schema = set_type(schema, "all types", "pk", PropertyType::String);
+            // FIXME: changing the primary key of a type with binary columns currently crashes in core
+            schema = remove_property(schema, "all types", "data");
             realm->update_schema(schema, 2, [](auto, auto new_realm, auto&) {
                 Object obj(new_realm, "all types", 0);
 
@@ -1075,8 +1068,7 @@ TEST_CASE("migration: Automatic") {
             realm->update_schema(old_schema, 1);
             realm->begin_transaction();
             auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
-            table->add_empty_row();
-            table->set_int(0, 0, 10);
+            table->create_object().set_all(10);
             realm->commit_transaction();
         };
 
@@ -1084,8 +1076,13 @@ TEST_CASE("migration: Automatic") {
     init(old_schema); \
     REQUIRE_NOTHROW(realm->update_schema(new_schema, 2, apply_renames({__VA_ARGS__}))); \
     REQUIRE(realm->schema() == new_schema); \
-    VERIFY_SCHEMA(*realm); \
-    REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->get_int(0, 0) == 10); \
+    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") {
@@ -1166,12 +1163,6 @@ TEST_CASE("migration: Immutable") {
             };
             REQUIRE_NOTHROW(realm->update_schema(schema));
             REQUIRE(realm->schema() == schema);
-
-            for (auto& object_schema : realm->schema()) {
-                for (size_t i = 0; i < object_schema.persisted_properties.size(); ++i) {
-                    REQUIRE(i == object_schema.persisted_properties[i].table_column);
-                }
-            }
         }
 
         SECTION("extra tables") {
@@ -1210,11 +1201,11 @@ TEST_CASE("migration: Immutable") {
 
             auto object_schema = realm->schema().find("object");
             REQUIRE(object_schema->persisted_properties.size() == 1);
-            REQUIRE(object_schema->persisted_properties[0].table_column == 0);
+            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].table_column == size_t(-1));
+            REQUIRE(!object_schema->persisted_properties[0].column_key);
         }
 
         SECTION("extra columns in table") {
@@ -1289,12 +1280,6 @@ TEST_CASE("migration: ReadOnly") {
             };
             REQUIRE_NOTHROW(realm->update_schema(schema));
             REQUIRE(realm->schema() == schema);
-
-            for (auto& object_schema : realm->schema()) {
-                for (size_t i = 0; i < object_schema.persisted_properties.size(); ++i) {
-                    REQUIRE(i == object_schema.persisted_properties[i].table_column);
-                }
-            }
         }
 
         SECTION("extra tables") {
@@ -1421,7 +1406,7 @@ TEST_CASE("migration: ResetFile") {
         realm->update_schema(schema);
         REQUIRE(ino == get_fileid());
         realm->begin_transaction();
-        ObjectStore::table_for_object_type(realm->read_group(), "object")->add_empty_row();
+        ObjectStore::table_for_object_type(realm->read_group(), "object")->create_object();
         realm->commit_transaction();
     }
     auto realm = Realm::get_shared_realm(config);
@@ -1481,7 +1466,6 @@ TEST_CASE("migration: Additive") {
 
     TestFile config;
     config.schema_mode = SchemaMode::Additive;
-    config.cache = false;
     config.schema = schema;
     auto realm = Realm::get_shared_realm(config);
     realm->update_schema(schema);
@@ -1502,26 +1486,28 @@ TEST_CASE("migration: Additive") {
 
     SECTION("indexes are updated when schema version is bumped") {
         auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
-        REQUIRE(table->has_search_index(0));
-        REQUIRE(!table->has_search_index(1));
+        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(0));
+        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(1));
+        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");
-        REQUIRE(table->has_search_index(0));
-        REQUIRE(!table->has_search_index(1));
+        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(0));
+        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(1));
+        REQUIRE(!table->has_search_index(col_keys[1]));
     }
 
     SECTION("can remove properties from existing tables, but column is not removed") {
@@ -1530,7 +1516,9 @@ TEST_CASE("migration: Additive") {
         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);
-        REQUIRE(properties[0].table_column == 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") {
@@ -1571,32 +1559,19 @@ TEST_CASE("migration: Additive") {
                                              [&](SharedRealm, SharedRealm, Schema&) { REQUIRE(false); }));
     }
 
-    SECTION("add new columns at end from different SG") {
+    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].table_column == 0);
-        REQUIRE(realm->schema().find("object")->persisted_properties[1].table_column == 1);
-    }
-
-    SECTION("add new columns at beginning 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");
-        table->insert_column(0, type_Int, "new column");
-        realm2->commit_transaction();
-
-        REQUIRE_NOTHROW(realm->refresh());
-        REQUIRE(realm->schema() == schema);
-        REQUIRE(realm->schema().find("object")->persisted_properties[0].table_column == 1);
-        REQUIRE(realm->schema().find("object")->persisted_properties[1].table_column == 2);
+        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") {
@@ -1604,18 +1579,19 @@ TEST_CASE("migration: Additive") {
         auto& group = realm2->read_group();
         realm2->begin_transaction();
         auto table = ObjectStore::table_for_object_type(group, "object");
-        table->insert_column(0, type_Double, "newcol");
+        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].table_column == 1);
-        REQUIRE(realm->schema().find("object")->persisted_properties[1].table_column == 2);
+        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(realm3->schema().find("object")->persisted_properties[0].table_column == 1);
-        REQUIRE(realm3->schema().find("object")->persisted_properties[1].table_column == 2);
+        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();
@@ -1624,8 +1600,8 @@ TEST_CASE("migration: Additive") {
 
         realm = Realm::get_shared_realm(config);
         REQUIRE(realm->schema() == schema);
-        REQUIRE(realm->schema().find("object")->persisted_properties[0].table_column == 1);
-        REQUIRE(realm->schema().find("object")->persisted_properties[1].table_column == 2);
+        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") {
@@ -1659,13 +1635,14 @@ TEST_CASE("migration: Additive") {
         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].table_column == 0);
-        REQUIRE(properties[1].table_column == 1);
-        REQUIRE(properties[2].table_column == 2);
+        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") {
@@ -1739,6 +1716,7 @@ TEST_CASE("migration: Manual") {
         }}
     };
     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); \
@@ -1759,91 +1737,92 @@ TEST_CASE("migration: Manual") {
     }
     SECTION("add property to table") {
         REQUIRE_MIGRATION(add_property(schema, "object", {"new", PropertyType::Int}),
-                          [](SharedRealm, SharedRealm realm, Schema&) {
+                          [&](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(1);
+                          [&](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&) {
+                          [&](SharedRealm, SharedRealm realm, Schema&) {
                               ObjectStore::set_primary_key_for_object(realm->read_group(), "link origin", "not a pk");
-                              get_table(realm, "link origin")->add_search_index(0);
+                              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&) {
+                          [&](SharedRealm, SharedRealm realm, Schema&) {
                               ObjectStore::set_primary_key_for_object(realm->read_group(), "object", "");
-                              get_table(realm, "object")->remove_search_index(0);
+                              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&) {
+                          [&](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(0);
-                              table->add_search_index(1);
+                              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&) {
+                          [&](SharedRealm, SharedRealm realm, Schema&) {
                               auto table = get_table(realm, "object");
-                              table->remove_column(1);
-                              size_t col = table->add_column(type_Timestamp, "value");
+                              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&) {
+                          [&](SharedRealm, SharedRealm realm, Schema&) {
                               auto table = get_table(realm, "link origin");
-                              table->remove_column(1);
+                              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&) {
+                          [&](SharedRealm, SharedRealm realm, Schema&) {
                               auto table = get_table(realm, "link origin");
-                              table->remove_column(2);
+                              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&) {
+                          [&](SharedRealm, SharedRealm realm, Schema&) {
                               auto table = get_table(realm, "object");
-                              table->remove_column(1);
-                              size_t col = table->add_column(type_Int, "value", true);
+                              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&) {
+                          [&](SharedRealm, SharedRealm realm, Schema&) {
                               auto table = get_table(realm, "object");
-                              table->remove_column(2);
+                              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(2);
+                          [&](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(1);
+                          [&](SharedRealm, SharedRealm realm, Schema&) {
+                              get_table(realm, "object")->remove_search_index(col_keys[1]);
                           });
     }
     SECTION("reorder properties") {

+ 0 - 1
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/notifications-fuzzer/fuzzer.cpp

@@ -243,7 +243,6 @@ int main(int argc, char** argv) {
 
     Realm::Config config;
     config.path = "fuzzer.realm";
-    config.cache = false;
     config.in_memory = true;
     config.automatic_change_notifications = false;
 

+ 133 - 123
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/object.cpp

@@ -31,7 +31,7 @@
 #include "impl/realm_coordinator.hpp"
 #include "impl/object_accessor_impl.hpp"
 
-#include <realm/group_shared.hpp>
+#include <realm/group.hpp>
 #include <realm/util/any.hpp>
 
 #include <cstdint>
@@ -41,6 +41,11 @@ 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 {
@@ -76,7 +81,6 @@ TEST_CASE("object") {
 
     InMemoryTestFile config;
     config.automatic_change_notifications = false;
-    config.cache = false;
     config.schema = Schema{
         {"table", {
             {"value 1", PropertyType::Int},
@@ -151,22 +155,22 @@ TEST_CASE("object") {
     };
     config.schema_version = 0;
     auto r = Realm::get_shared_realm(config);
-    auto& coordinator = *_impl::RealmCoordinator::get_existing_coordinator(config.path);
+    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();
-
-        table->add_empty_row(10);
         for (int i = 0; i < 10; ++i)
-            table->set_int(0, i, i);
+            table->create_object(object_keys[i]).set_all(i);
         r->commit_transaction();
 
         auto r2 = coordinator.get_realm();
 
         CollectionChangeSet change;
-        Row row = table->get(0);
-        Object object(r, *r->schema().find("table"), row);
+        auto obj = *table->begin();
+        Object object(r, obj);
 
         auto write = [&](auto&& f) {
             r->begin_transaction();
@@ -196,67 +200,65 @@ TEST_CASE("object") {
 
         SECTION("deleting the object sends a change notification") {
             auto token = require_change();
-            write([&] { row.move_last_over(); });
+            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([&] { row.set_int(0, 10); });
+            write([&] { obj.set(col_keys[0], 10); });
             REQUIRE_INDICES(change.modifications, 0);
             REQUIRE(change.columns.size() == 1);
-            REQUIRE_INDICES(change.columns[0], 0);
+            REQUIRE_INDICES(change.columns[col_keys[0].value], 0);
 
-            write([&] { row.set_int(1, 10); });
+            write([&] { obj.set(col_keys[1], 10); });
             REQUIRE_INDICES(change.modifications, 0);
-            REQUIRE(change.columns.size() == 2);
-            REQUIRE(change.columns[0].empty());
-            REQUIRE_INDICES(change.columns[1], 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(1).set_int(0, 10); });
-        }
-
-        SECTION("moving the object") {
-            auto token = require_no_change();
-            write([&] { table->swap_rows(0, 5); });
-        }
-
-        SECTION("subsuming the object") {
-            auto token = require_change();
-            write([&] {
-                table->insert_empty_row(0);
-                table->merge_rows(row.get_index(), 0);
-                row.set_int(0, 10);
-            });
-            REQUIRE(change.columns.size() == 1);
-            REQUIRE_INDICES(change.columns[0], 0);
+            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(0);
+            auto r2row = r2->read_group().get_table("class_table")->get_object(0);
             r2->begin_transaction();
-            r2row.set_int(0, 1);
+            r2row.set(col_keys[0], 1);
             r2->commit_transaction();
             r2->begin_transaction();
-            r2row.set_int(1, 2);
+            r2row.set(col_keys[1], 2);
             r2->commit_transaction();
 
             advance_and_notify(*r);
             REQUIRE(change.columns.size() == 2);
-            REQUIRE_INDICES(change.columns[0], 0);
-            REQUIRE_INDICES(change.columns[1], 0);
+            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([&] {
-                row.set_int(0, 1);
+                obj.set(col_keys[0], 1);
                 token.suppress_next();
             });
         }
@@ -271,9 +273,7 @@ TEST_CASE("object") {
             REQUIRE(change.empty());
 
             // should now produce a notification
-            write([&] {
-                row.set_int(0, 1);
-            });
+            write([&] { obj.set(col_keys[0], 1); });
             REQUIRE_INDICES(change.modifications, 0);
         }
 
@@ -284,13 +284,13 @@ TEST_CASE("object") {
                 });
             }
             auto token = require_change();
-            write([&] { row.move_last_over(); });
+            write([&] { obj.remove(); });
             REQUIRE_INDICES(change.deletions, 0);
         }
 
         SECTION("observing deleted object throws") {
             write([&] {
-                row.move_last_over();
+                obj.remove();
             });
             REQUIRE_THROWS(require_change());
         }
@@ -334,44 +334,50 @@ TEST_CASE("object") {
             {"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{}},
+            {"date array", AnyVec{Timestamp(10, 20), Timestamp(30, 40)}},
             {"object array", AnyVec{AnyDict{{"value", INT64_C(20)}}}},
         });
 
-        auto row = obj.row();
-        REQUIRE(row.get_int(0) == 1);
-        REQUIRE(row.get_bool(1) == true);
-        REQUIRE(row.get_int(2) == 5);
-        REQUIRE(row.get_float(3) == 2.2f);
-        REQUIRE(row.get_double(4) == 3.3);
-        REQUIRE(row.get_string(5) == "hello");
-        REQUIRE(row.get_binary(6) == BinaryData("olleh", 5));
-        REQUIRE(row.get_timestamp(7) == Timestamp(10, 20));
-        REQUIRE(row.get_link(8) == 0);
-
-        auto link_target = r->read_group().get_table("class_link target")->get(0);
-        REQUIRE(link_target.get_int(0) == 10);
-
-        auto check_array = [&](size_t col, auto... values) {
-            auto table = row.get_subtable(col);
+        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 (auto& value : {values...}) {
+            for (const auto& value : vec) {
                 CAPTURE(i);
-                REQUIRE(i < row.get_subtable_size(col));
-                REQUIRE(value == table->get<typename std::decay<decltype(value)>::type>(0, i));
+                REQUIRE(i < list.size());
+                REQUIRE(value == list.get(i));
                 ++i;
             }
         };
-        check_array(9, true, false);
-        check_array(10, INT64_C(5), INT64_C(6));
-        check_array(11, 1.1f, 2.2f);
-        check_array(12, 3.3, 4.4);
-        check_array(13, StringData("a"), StringData("b"), StringData("c"));
-        check_array(14, BinaryData("d", 1), BinaryData("e", 1), BinaryData("f", 1));
-
-        auto list = row.get_linklist(16);
+        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(0).get_int(0) == 20);
+        REQUIRE(list->get_object(0).get<Int>(array_target_table->get_column_key("value")) == 20);
     }
 
     SECTION("create uses defaults for missing values") {
@@ -400,24 +406,25 @@ TEST_CASE("object") {
             {"float", 6.6f},
         });
 
-        auto row = obj.row();
-        REQUIRE(row.get_int(0) == 1);
-        REQUIRE(row.get_bool(1) == true);
-        REQUIRE(row.get_int(2) == 5);
-        REQUIRE(row.get_float(3) == 6.6f);
-        REQUIRE(row.get_double(4) == 3.3);
-        REQUIRE(row.get_string(5) == "hello");
-        REQUIRE(row.get_binary(6) == BinaryData("olleh", 5));
-        REQUIRE(row.get_timestamp(7) == Timestamp(10, 20));
-
-        REQUIRE(row.get_subtable(9)->size() == 2);
-        REQUIRE(row.get_subtable(10)->size() == 2);
-        REQUIRE(row.get_subtable(11)->size() == 2);
-        REQUIRE(row.get_subtable(12)->size() == 2);
-        REQUIRE(row.get_subtable(13)->size() == 3);
-        REQUIRE(row.get_subtable(14)->size() == 3);
-        REQUIRE(row.get_subtable(15)->size() == 0);
-        REQUIRE(row.get_linklist(16)->size() == 1);
+        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") {
@@ -436,8 +443,8 @@ TEST_CASE("object") {
             {"array", AnyVector{AnyDict{{"value", INT64_C(20)}}}},
         });
 
-        auto row = obj.row();
-        REQUIRE(row.get_int(0) == 10);
+        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") {
@@ -525,15 +532,16 @@ TEST_CASE("object") {
         REQUIRE(callback_called);
         REQUIRE_INDICES(change.modifications, 0);
 
-        auto row = obj.row();
-        REQUIRE(row.get_int(0) == 1);
-        REQUIRE(row.get_bool(1) == true);
-        REQUIRE(row.get_int(2) == 6);
-        REQUIRE(row.get_float(3) == 2.2f);
-        REQUIRE(row.get_double(4) == 3.3);
-        REQUIRE(row.get_string(5) == "a");
-        REQUIRE(row.get_binary(6) == BinaryData("olleh", 5));
-        REQUIRE(row.get_timestamp(7) == Timestamp(10, 20));
+        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") {
@@ -567,7 +575,7 @@ TEST_CASE("object") {
 
         auto table = r->read_group().get_table("class_person");
         REQUIRE(table->size() == 5);
-        Results result(r, *table);
+        Results result(r, table);
         auto token = result.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) {
             change = c;
             callback_called = true;
@@ -629,7 +637,7 @@ TEST_CASE("object") {
         });
 
         auto obj_table = r->read_group().get_table("class_all types");
-        Results result(r, *obj_table);
+        Results result(r, obj_table);
         bool callback_called;
         bool results_callback_called;
         bool sub_callback_called;
@@ -706,7 +714,7 @@ TEST_CASE("object") {
         Object obj = create(dict);
 
         auto obj_table = r->read_group().get_table("class_all types");
-        Results result(r, *obj_table);
+        Results result(r, obj_table);
         auto token1 = result.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
             callback_called = true;
         });
@@ -866,26 +874,28 @@ TEST_CASE("object") {
         };
 
         auto obj = create(AnyDict{{"pk", d.null_value()}}, "nullable int pk");
-        REQUIRE(obj.row().is_null(0));
+        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.row().is_null(0));
+        REQUIRE(obj.obj().is_null(col_pk_str));
 
         obj = create(AnyDict{{}}, "nullable int pk");
-        REQUIRE(obj.row().get_int(0) == 10);
+        REQUIRE(obj.obj().get<util::Optional<Int>>(col_pk_int) == 10);
         obj = create(AnyDict{{}}, "nullable string pk");
-        REQUIRE(obj.row().get_string(0) == "value");
+        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.add_empty_row();
-        Object obj(r, *r->schema().find("all types"), table[0]);
+        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.add_empty_row();
-        Object linkobj(r, *r->schema().find("link target"), link_table[0]);
+        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);
@@ -910,7 +920,7 @@ TEST_CASE("object") {
 
         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")).row().get_index() == linkobj.row().get_index());
+        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);
@@ -985,8 +995,8 @@ TEST_CASE("object") {
 
         r1->begin_transaction();
         r2->begin_transaction();
-        auto obj = Object::create(c1, r1, *r1->schema().find("pk after list"), util::Any(v1));
-        Object::create(c2, r2, *r2->schema().find("pk after list"), util::Any(v2));
+        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();
 
@@ -995,12 +1005,12 @@ TEST_CASE("object") {
             return r1->read_group().get_table("class_array target")->size() == 4;
         });
 
-        // With stable IDs, sync creates the primary key column at index 0.
-        REQUIRE(obj.row().get_int(0) == 7); // pk
-        REQUIRE(obj.row().get_linklist(1)->size() == 2);
-        REQUIRE(obj.row().get_int(2) == 1); // non-default from r1
-        REQUIRE(obj.row().get_int(3) == 2); // non-default from r2
-        REQUIRE(obj.row().get_linklist(4)->size() == 2);
+        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

+ 3 - 14
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/object_store.cpp

@@ -26,11 +26,11 @@
 #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);
@@ -63,25 +63,14 @@ TEST_CASE("ObjectStore:: property_for_column_index()") {
         REQUIRE_FALSE(it == realm->schema().end());
         ObjectSchema object_schema = *it;
 
-        size_t count = table->get_column_count();
-        for (size_t col = 0; col < count; col++) {
+        auto all_columns = table->get_column_keys();
+        for (auto col : all_columns) {
             auto property = ObjectStore::property_for_column_index(table, col);
             if (!property) {
-#if REALM_ENABLE_SYNC
-                REQUIRE(table->get_column_name(col) == sync::object_id_column_name);
-#else
                 FAIL();
-#endif
                 continue;
             }
             auto actual_property = *object_schema.property_for_name(property->name);
-
-            // property_for_column_index won't read the pk info, but it will set the is_index to true for pk.
-            // Property could be created with is_indexed = false , is_primary = true.
-            if (actual_property.is_primary) {
-                actual_property.is_primary = false;
-                actual_property.is_indexed = true;
-            }
             REQUIRE(property.value() == actual_property);
         }
    }

+ 102 - 82
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/primitive_list.cpp

@@ -34,8 +34,7 @@
 #include "impl/realm_coordinator.hpp"
 #include "impl/object_accessor_impl.hpp"
 
-#include <realm/group_shared.hpp>
-#include <realm/link_view.hpp>
+#include <realm/db.hpp>
 #include <realm/query_expression.hpp>
 #include <realm/version.hpp>
 
@@ -43,11 +42,13 @@
 
 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; }
@@ -104,21 +105,29 @@ struct String : Base<PropertyType::String, StringData> {
 
 struct Binary : Base<PropertyType::Data, BinaryData> {
     using Boxed = std::string;
-    static std::vector<BinaryData> values() { return {BinaryData("a", 1)}; }
     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(1, 1)}; }
+    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(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()
     {
@@ -137,6 +146,7 @@ struct BoxedOptional : BaseT {
 
 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())
     {
@@ -190,7 +200,7 @@ struct StringifyingContext {
         return ss.str();
     }
 
-    std::string box(RowExpr row) { return util::to_string(row.get_index()); }
+    std::string box(Obj obj) { return util::to_string(obj.get_key().value); }
 };
 
 namespace Catch {
@@ -275,7 +285,8 @@ auto greater::operator()<Timestamp&, Timestamp&>(Timestamp& a, Timestamp& b) con
 
 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>) {
+                   UnboxedOptional<::String>, UnboxedOptional<::Binary>, UnboxedOptional<::Date>)
+{
     auto values = TestType::values();
     using T = typename TestType::Type;
     using W = typename TestType::Wrapped;
@@ -283,7 +294,6 @@ TEMPLATE_TEST_CASE("primitive list", "[primitives]", ::Int, ::Bool, ::Float, ::D
 
     InMemoryTestFile config;
     config.automatic_change_notifications = false;
-    config.cache = false;
     config.schema = Schema{
         {"object", {
             {"value", PropertyType::Array|TestType::property_type()}
@@ -295,9 +305,10 @@ TEMPLATE_TEST_CASE("primitive list", "[primitives]", ::Int, ::Bool, ::Float, ::D
     auto table = r->read_group().get_table("class_object");
     auto table2 = r2->read_group().get_table("class_object");
     r->begin_transaction();
-    table->add_empty_row();
+    Obj obj = table->create_object();
+    ColKey col = table->get_column_key("value");
 
-    List list(r, *table, 0, 0);
+    List list(r, obj, col);
     auto results = list.as_results();
     CppContext ctx(r);
 
@@ -305,7 +316,7 @@ TEMPLATE_TEST_CASE("primitive list", "[primitives]", ::Int, ::Bool, ::Float, ::D
         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);
@@ -313,11 +324,11 @@ TEMPLATE_TEST_CASE("primitive list", "[primitives]", ::Int, ::Bool, ::Float, ::D
         REQUIRE(list.get_query().count() == 1);
         REQUIRE(results.get_query().count() == 1);
     }
-
+#endif
     SECTION("get_origin_row_index()") {
-        REQUIRE(list.get_origin_row_index() == 0);
-        table->insert_empty_row(0);
-        REQUIRE(list.get_origin_row_index() == 1);
+        REQUIRE(list.get_parent_object_key() == obj.get_key());
+        table->create_object();
+        REQUIRE(list.get_parent_object_key() == obj.get_key());
     }
 
     SECTION("get_type()") {
@@ -346,7 +357,7 @@ TEMPLATE_TEST_CASE("primitive list", "[primitives]", ::Int, ::Bool, ::Float, ::D
         }
 
         SECTION("delete row") {
-            table->move_last_over(0);
+            obj.remove();
             REQUIRE_FALSE(list.is_valid());
             REQUIRE_FALSE(results.is_valid());
         }
@@ -372,7 +383,7 @@ TEMPLATE_TEST_CASE("primitive list", "[primitives]", ::Int, ::Bool, ::Float, ::D
         }
 
         SECTION("delete row") {
-            table->move_last_over(0);
+            obj.remove();
             REQUIRE_THROWS(list.verify_attached());
         }
 
@@ -396,7 +407,7 @@ TEMPLATE_TEST_CASE("primitive list", "[primitives]", ::Int, ::Bool, ::Float, ::D
         }
 
         SECTION("delete row") {
-            table->move_last_over(0);
+            obj.remove();
             REQUIRE_THROWS(list.verify_in_transaction());
         }
 
@@ -521,18 +532,18 @@ TEMPLATE_TEST_CASE("primitive list", "[primitives]", ::Int, ::Bool, ::Float, ::D
     }
 
     SECTION("find()") {
-        // cast to T needed for vector<bool>'s wonky proxy
         for (size_t i = 0; i < values.size(); ++i) {
             CAPTURE(i);
-            REQUIRE(list.find(static_cast<T>(values[i])) == i);
-            REQUIRE(results.index_of(static_cast<T>(values[i])) == 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);
@@ -542,27 +553,23 @@ TEMPLATE_TEST_CASE("primitive list", "[primitives]", ::Int, ::Bool, ::Float, ::D
         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 subtable = table->get_subtable(0, 0);
-
         auto sorted = list.sort({{"self", true}});
         std::sort(begin(values), end(values), less());
         for (size_t i = 0; i < values.size(); ++i) {
             CAPTURE(i);
-            auto q = TestType::unwrap(values[i], [&] (auto v) { return table->get_subtable(0, 0)->column<W>(0) == v; });
-            REQUIRE(sorted.index_of(std::move(q)) == 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);
-            auto q = TestType::unwrap(values[i], [&] (auto v) { return table->get_subtable(0, 0)->column<W>(0) == v; });
-            REQUIRE(sorted.index_of(std::move(q)) == 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; });
@@ -572,28 +579,27 @@ TEMPLATE_TEST_CASE("primitive list", "[primitives]", ::Int, ::Bool, ::Float, ::D
             REQUIRE(filtered.index_of(static_cast<T>(values[i])) == i - 1);
         }
     }
-
+#endif
     SECTION("sort()") {
-        auto subtable = table->get_subtable(0, 0);
-
         auto unsorted = list.sort(std::vector<std::pair<std::string, bool>>{});
         REQUIRE(unsorted == values);
 
-        auto sorted = list.sort(SortDescriptor(*subtable, {{0}}, {true}));
+        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(*subtable, {{0}}, {false}));
+        sorted = list.sort(SortDescriptor({{col}}, {false}));
         sorted2 = list.sort({{"self", false}});
         std::sort(begin(values), end(values), greater());
         REQUIRE(sorted == values);
         REQUIRE(sorted2 == values);
 
-        REQUIRE_THROWS_WITH(list.sort({{"not self", true}}),
-                            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)));
+        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)));
@@ -605,12 +611,10 @@ TEMPLATE_TEST_CASE("primitive list", "[primitives]", ::Int, ::Bool, ::Float, ::D
         auto values2 = values;
         values2.insert(values2.end(), values.begin(), values.end());
 
-        auto subtable = table->get_subtable(0, 0);
-
         auto undistinct = list.as_results().distinct(std::vector<std::string>{});
         REQUIRE(undistinct == values2);
 
-        auto distinct = results.distinct(SortDescriptor(*subtable, {{0}}, {true}));
+        auto distinct = results.distinct(DistinctDescriptor({{col}}));
         auto distinct2 = results.distinct({"self"});
         REQUIRE(distinct == values);
         REQUIRE(distinct2 == values);
@@ -623,6 +627,7 @@ TEMPLATE_TEST_CASE("primitive list", "[primitives]", ::Int, ::Bool, ::Float, ::D
                                          string_for_property_type(TestType::property_type() & ~PropertyType::Flags)));
     }
 
+#if 0
     SECTION("filter()") {
         T v = values.front();
         values.erase(values.begin());
@@ -636,6 +641,7 @@ TEMPLATE_TEST_CASE("primitive list", "[primitives]", ::Int, ::Bool, ::Float, ::D
         REQUIRE(filtered.size() == 1);
         REQUIRE(*filtered.first<T>() == v);
     }
+#endif
 
     SECTION("min()") {
         if (!TestType::can_minmax()) {
@@ -692,58 +698,82 @@ TEMPLATE_TEST_CASE("primitive list", "[primitives]", ::Int, ::Bool, ::Float, ::D
     }
 
     SECTION("operator==()") {
-        table->add_empty_row();
-        REQUIRE(list == List(r, *table, 0, 0));
-        REQUIRE_FALSE(list == List(r, *table, 0, 1));
+        Obj obj1 = table->create_object();
+        REQUIRE(list == List(r, obj, col));
+        REQUIRE_FALSE(list == List(r, obj1, col));
     }
 
     SECTION("hash") {
-        table->add_empty_row();
+        Obj obj1 = table->create_object();
         std::hash<List> h;
-        REQUIRE(h(list) == h(List(r, *table, 0, 0)));
-        REQUIRE_FALSE(h(list) == h(List(r, *table, 0, 1)));
+        REQUIRE(h(list) == h(List(r, obj, col)));
+        REQUIRE_FALSE(h(list) == h(List(r, obj1, col)));
     }
 
     SECTION("handover") {
         r->commit_transaction();
 
-        auto handover = r->obtain_thread_safe_reference(list);
-        auto list2 = r->resolve_thread_safe_reference(std::move(handover));
+        auto list2 = ThreadSafeReference(list).resolve<List>(r);
         REQUIRE(list == list2);
-
-        auto results_handover = r->obtain_thread_safe_reference(results);
-        auto results2 = r->resolve_thread_safe_reference(std::move(results_handover));
+        auto results2 = ThreadSafeReference(results).resolve<Results>(r);
         REQUIRE(results2 == values);
     }
 
     SECTION("notifications") {
         r->commit_transaction();
 
-        CollectionChangeSet change, rchange;
+        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") {
-            auto token = list.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) {
-                change = c;
-            });
-            auto rtoken = results.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) {
-                rchange = c;
-            });
+            // 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") {
-            auto token = list.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) {
-                change = c;
-            });
-            auto rtoken = results.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) {
-                rchange = c;
-            });
             advance_and_notify(*r);
 
             r->begin_transaction();
@@ -752,42 +782,32 @@ TEMPLATE_TEST_CASE("primitive list", "[primitives]", ::Int, ::Bool, ::Float, ::D
             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") {
-            size_t calls = 0;
-            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;
-            });
             advance_and_notify(*r);
-            REQUIRE(calls == 2);
+            REQUIRE(calls == 3);
 
             r->begin_transaction();
-            table->move_last_over(0);
+            obj.remove();
             r->commit_transaction();
             advance_and_notify(*r);
-            REQUIRE(calls == 4);
+            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->add_empty_row();
+            table->create_object();
             r->commit_transaction();
             advance_and_notify(*r);
-            REQUIRE(calls == 4);
+            REQUIRE(calls == 6);
         }
 
         SECTION("deleting containing row before first run of notifier") {
-            auto token = list.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) {
-                change = c;
-            });
             r2->begin_transaction();
-            table2->move_last_over(0);
+            table2->begin()->remove();
             r2->commit_transaction();
             advance_and_notify(*r);
             REQUIRE(change.deletions.count() == values.size());

+ 129 - 187
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/realm.cpp

@@ -23,28 +23,29 @@
 #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 "impl/realm_coordinator.hpp"
+#include <realm/db.hpp>
 
 #if REALM_ENABLE_SYNC
 #include "sync/async_open_task.hpp"
 #endif
 
-#include <realm/group.hpp>
 #include <realm/util/scope_exit.hpp>
 
 namespace realm {
 class TestHelper {
 public:
-    static SharedGroup& get_shared_group(SharedRealm const& shared_realm)
+    static DBRef& get_db(SharedRealm const& shared_realm)
     {
-        return *Realm::Internal::get_shared_group(*shared_realm);
+        return Realm::Internal::get_db(*shared_realm);
     }
 
     static void begin_read(SharedRealm const& shared_realm, VersionID version)
@@ -65,19 +66,6 @@ TEST_CASE("SharedRealm: get_shared_realm()") {
         }},
     };
 
-    SECTION("should return the same instance when caching is enabled") {
-        auto realm1 = Realm::get_shared_realm(config);
-        auto realm2 = Realm::get_shared_realm(config);
-        REQUIRE(realm1.get() == realm2.get());
-    }
-
-    SECTION("should return different instances when caching is disabled") {
-        config.cache = false;
-        auto realm1 = Realm::get_shared_realm(config);
-        auto realm2 = Realm::get_shared_realm(config);
-        REQUIRE(realm1.get() != realm2.get());
-    }
-
     SECTION("should validate that the config is sensible") {
         SECTION("bad encryption key") {
             config.encryption_key = std::vector<char>(2, 0);
@@ -121,9 +109,6 @@ TEST_CASE("SharedRealm: get_shared_realm()") {
     }
 
     SECTION("should reject mismatched config") {
-        SECTION("cached") { }
-        SECTION("uncached") { config.cache = false; }
-
         SECTION("schema version") {
             auto realm = Realm::get_shared_realm(config);
             config.schema_version = 2;
@@ -221,7 +206,7 @@ TEST_CASE("SharedRealm: get_shared_realm()") {
             auto table = ObjectStore::table_for_object_type(g, "object");
             REQUIRE(table);
             REQUIRE(table->get_column_count() == 1);
-            REQUIRE(table->get_column_name(0) == "value");
+            REQUIRE(table->get_column_name(*table->get_column_keys().begin()) == "value");
         }
 
         config.schema_version = 2;
@@ -272,27 +257,28 @@ TEST_CASE("SharedRealm: get_shared_realm()") {
         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].table_column == 0);
+        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.cache = false;
         config.schema_mode = SchemaMode::Additive;
         config.schema_version = 0;
 
         auto realm = Realm::get_shared_realm(config);
         REQUIRE(realm->schema().size() == 1);
 
-        auto& shared_group = TestHelper::get_shared_group(realm);
-        shared_group.begin_read();
-        shared_group.pin_version();
-        VersionID old_version = shared_group.get_version_of_current_transaction();
+        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{
@@ -314,9 +300,6 @@ TEST_CASE("SharedRealm: get_shared_realm()") {
     }
 
     SECTION("should sensibly handle opening an uninitialized file without a schema specified") {
-        SECTION("cached") { }
-        SECTION("uncached") { config.cache = false; }
-
         // create an empty file
         File(config.path, File::mode_Write);
 
@@ -342,14 +325,15 @@ TEST_CASE("SharedRealm: get_shared_realm()") {
         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].table_column == 0);
+        REQUIRE(it->persisted_properties[0].column_key == table->get_column_key("value"));
     }
 
     SECTION("should support using different table subsets on different threads") {
-        config.cache = false;
         auto realm1 = Realm::get_shared_realm(config);
 
         config.schema = Schema{
@@ -387,7 +371,7 @@ TEST_CASE("SharedRealm: get_shared_realm()") {
 #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", SharedGroupOptions::get_sys_tmp_dir(), std::hash<std::string>()(config.path)); // Mirror internal implementation
+        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");
@@ -395,14 +379,6 @@ TEST_CASE("SharedRealm: get_shared_realm()") {
     }
 #endif
 
-    SECTION("should get different instances on different threads") {
-        auto realm1 = Realm::get_shared_realm(config);
-        std::thread([&]{
-            auto realm2 = Realm::get_shared_realm(config);
-            REQUIRE(realm1 != realm2);
-        }).join();
-    }
-
     SECTION("should detect use of Realm on incorrect thread") {
         auto realm = Realm::get_shared_realm(config);
         std::thread([&]{
@@ -410,36 +386,6 @@ TEST_CASE("SharedRealm: get_shared_realm()") {
         }).join();
     }
 
-    SECTION("should get different instances for different explicit execuction contexts") {
-        config.execution_context = 0;
-        auto realm1 = Realm::get_shared_realm(config);
-        config.execution_context = 1;
-        auto realm2 = Realm::get_shared_realm(config);
-        REQUIRE(realm1 != realm2);
-
-        config.execution_context = util::none;
-        auto realm3 = Realm::get_shared_realm(config);
-        REQUIRE(realm1 != realm3);
-        REQUIRE(realm2 != realm3);
-    }
-
-    SECTION("can use Realm with explicit execution context on different thread") {
-        config.execution_context = 1;
-        auto realm = Realm::get_shared_realm(config);
-        std::thread([&]{
-            REQUIRE_NOTHROW(realm->verify_thread());
-        }).join();
-    }
-
-    SECTION("should get same instance for same explicit execution context on different thread") {
-        config.execution_context = 1;
-        auto realm1 = Realm::get_shared_realm(config);
-        std::thread([&]{
-            auto realm2 = Realm::get_shared_realm(config);
-            REQUIRE(realm1 == realm2);
-        }).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");
@@ -465,14 +411,12 @@ TEST_CASE("Get Realm using Async Open", "[asyncOpen]") {
 
     SyncServer server;
     SyncTestFile config(server, "default");
-    config.cache = false;
     config.schema = Schema{
         {"object", {
             {"value", PropertyType::Int},
         }},
     };
     SyncTestFile config2(server, "default");
-    config2.cache = false;
     config2.schema = config.schema;
 
     std::mutex mutex;
@@ -495,7 +439,7 @@ TEST_CASE("Get Realm using Async Open", "[asyncOpen]") {
         {
             auto realm = Realm::get_shared_realm(config2);
             realm->begin_transaction();
-            sync::create_object(realm->read_group(), *realm->read_group().get_table("class_object"));
+            realm->read_group().get_table("class_object")->create_object();
             realm->commit_transaction();
             wait_for_upload(*realm);
         }
@@ -520,7 +464,7 @@ TEST_CASE("Get Realm using Async Open", "[asyncOpen]") {
         {
             auto realm = Realm::get_shared_realm(config2);
             realm->begin_transaction();
-            sync::create_object(realm->read_group(), *realm->read_group().get_table("class_object"));
+            realm->read_group().get_table("class_object")->create_object();
             realm->commit_transaction();
             wait_for_upload(*realm);
         }
@@ -545,7 +489,7 @@ TEST_CASE("Get Realm using Async Open", "[asyncOpen]") {
         {
             auto realm = Realm::get_shared_realm(config2);
             realm->begin_transaction();
-            sync::create_object(realm->read_group(), *realm->read_group().get_table("class_object"));
+            realm->read_group().get_table("class_object")->create_object();
             realm->commit_transaction();
             wait_for_upload(*realm);
         }
@@ -594,7 +538,6 @@ TEST_CASE("SharedRealm: notifications") {
         return;
 
     TestFile config;
-    config.cache = false;
     config.schema_version = 0;
     config.schema = Schema{
         {"object", {
@@ -614,6 +557,7 @@ TEST_CASE("SharedRealm: notifications") {
 
     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;
 
@@ -677,9 +621,12 @@ TEST_CASE("SharedRealm: notifications") {
         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());
     }
@@ -722,7 +669,6 @@ TEST_CASE("SharedRealm: notifications") {
 
 TEST_CASE("SharedRealm: schema updating from external changes") {
     TestFile config;
-    config.cache = false;
     config.schema_version = 0;
     config.schema_mode = SchemaMode::Additive;
     config.schema = Schema{
@@ -733,18 +679,19 @@ TEST_CASE("SharedRealm: schema updating from external changes") {
     };
 
     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")->insert_column(0, type_String, "new col");
+            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);
-            REQUIRE(object_schema.persisted_properties[0].table_column == 0);
+            ColKey col = object_schema.persisted_properties[0].column_key;
             r1->refresh();
-            REQUIRE(object_schema.persisted_properties[0].table_column == 1);
+            REQUIRE(object_schema.persisted_properties[0].column_key == col);
         };
         SECTION("with an active read transaction") {
             r1->read_group();
@@ -760,19 +707,19 @@ TEST_CASE("SharedRealm: schema updating from external changes") {
         auto r = Realm::get_shared_realm(config);
         r->invalidate();
 
-        auto& sg = TestHelper::get_shared_group(r);
-        WriteTransaction wt(sg);
+        auto& db = TestHelper::get_db(r);
+        WriteTransaction wt(db);
         auto& table = *wt.get_table("class_object");
 
         SECTION("removing a property") {
-            table.remove_column(0);
+            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(1);
+            table.remove_column(table.get_column_key("value 2"));
             table.add_column(type_Float, "value 2");
             wt.commit();
             REQUIRE_THROWS_WITH(r->refresh(),
@@ -780,7 +727,7 @@ TEST_CASE("SharedRealm: schema updating from external changes") {
         }
 
         SECTION("make property optional") {
-            table.remove_column(1);
+            table.remove_column(table.get_column_key("value 2"));
             table.add_column(type_Int, "value 2", true);
             wt.commit();
             REQUIRE_THROWS_WITH(r->refresh(),
@@ -788,43 +735,74 @@ TEST_CASE("SharedRealm: schema updating from external changes") {
         }
 
         SECTION("recreate column with no changes") {
-            table.remove_column(1);
+            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(1);
+            table.remove_search_index(table.get_column_key("value 2"));
             wt.commit();
             REQUIRE_NOTHROW(r->refresh());
         }
     }
 }
 
-TEST_CASE("SharedRealm: closed realm") {
+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);
-    realm->close();
 
-    REQUIRE(realm->is_closed());
+    SECTION("all functions throw ClosedRealmException after close") {
+        realm->close();
 
-    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(realm->is_closed());
 
-    REQUIRE_THROWS_AS(realm->refresh(), ClosedRealmException);
-    REQUIRE_THROWS_AS(realm->invalidate(), ClosedRealmException);
-    REQUIRE_THROWS_AS(realm->compact(), ClosedRealmException);
+        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") {
@@ -852,10 +830,12 @@ TEST_CASE("ShareRealm: in-memory mode from buffer") {
         // 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].table_column == 0);
+        REQUIRE(it->persisted_properties[0].column_key == table->get_column_key("value"));
 
         // Test invalid configs
         realm::Realm::Config config3;
@@ -881,13 +861,12 @@ TEST_CASE("ShareRealm: realm closed in did_change callback") {
             {"value", PropertyType::Int}
         }},
     };
-    config.cache = false;
     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->add_empty_row();
+    table->create_object();
     r1->commit_transaction();
 
     // Cannot be a member var of Context since Realm.close will free the context.
@@ -907,7 +886,7 @@ TEST_CASE("ShareRealm: realm closed in did_change callback") {
 
         auto r2 = Realm::get_shared_realm(config);
         r2->begin_transaction();
-        r2->read_group().get_table("class_object")->add_empty_row(1);
+        r2->read_group().get_table("class_object")->create_object();
         r2->commit_transaction();
         r2.reset();
 
@@ -924,11 +903,11 @@ TEST_CASE("ShareRealm: realm closed in did_change callback") {
 
         auto r2 = Realm::get_shared_realm(config);
         r2->begin_transaction();
-        r2->read_group().get_table("class_object")->add_empty_row(1);
+        r2->read_group().get_table("class_object")->create_object();
         r2->commit_transaction();
         r2.reset();
 
-        auto coordinator = _impl::RealmCoordinator::get_existing_coordinator(config.path);
+        auto coordinator = _impl::RealmCoordinator::get_coordinator(config.path);
         coordinator->on_change();
 
         r1->notify();
@@ -939,7 +918,7 @@ TEST_CASE("ShareRealm: realm closed in did_change callback") {
 
         auto r2 = Realm::get_shared_realm(config);
         r2->begin_transaction();
-        r2->read_group().get_table("class_object")->add_empty_row(1);
+        r2->read_group().get_table("class_object")->create_object();
         r2->commit_transaction();
         r2.reset();
 
@@ -1049,9 +1028,8 @@ TEST_CASE("RealmCoordinator: schema cache") {
 
 TEST_CASE("SharedRealm: coordinator schema cache") {
     TestFile config;
-    config.cache = false;
     auto r = Realm::get_shared_realm(config);
-    auto coordinator = _impl::RealmCoordinator::get_existing_coordinator(config.path);
+    auto coordinator = _impl::RealmCoordinator::get_coordinator(config.path);
 
     Schema cache_schema;
     uint64_t cache_sv = -1, cache_tv = -1;
@@ -1072,17 +1050,16 @@ TEST_CASE("SharedRealm: coordinator schema cache") {
 
     class ExternalWriter {
     private:
-        std::unique_ptr<Replication> history;
-        std::unique_ptr<SharedGroup> shared_group;
-        std::unique_ptr<Group> read_only_group;
-
+        std::shared_ptr<Realm> m_realm;
     public:
         WriteTransaction wt;
         ExternalWriter(Realm::Config const& config)
-        : wt([&]() -> SharedGroup& {
-            Realm::open_with_config(config, history, shared_group, read_only_group, nullptr);
-            return *shared_group;
+        : 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))
         {
         }
     };
@@ -1102,20 +1079,20 @@ TEST_CASE("SharedRealm: coordinator schema cache") {
         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].table_column == 0);
+        REQUIRE(cache_schema.begin()->persisted_properties[0].column_key != ColKey{});
     }
 
     coordinator = nullptr;
     r = nullptr;
     r = Realm::get_shared_realm(config);
-    coordinator = _impl::RealmCoordinator::get_existing_coordinator(config.path);
+    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].table_column == 0);
+        REQUIRE(cache_schema.begin()->persisted_properties[0].column_key != ColKey{});
     }
 
     SECTION("transaction version is bumped after a local write") {
@@ -1131,7 +1108,7 @@ TEST_CASE("SharedRealm: coordinator schema cache") {
 
         SECTION("non-schema change") {
             external_write(config, [](auto& wt) {
-                wt.get_table("class_object")->add_empty_row();
+                wt.get_table("class_object")->create_object();
             });
         }
         SECTION("schema change") {
@@ -1149,7 +1126,7 @@ TEST_CASE("SharedRealm: coordinator schema cache") {
     SECTION("notify() with a read transaction bumps transaction version") {
         r->read_group();
         external_write(config, [](auto& wt) {
-            wt.get_table("class_object")->add_empty_row();
+            wt.get_table("class_object")->create_object();
         });
 
         r->notify();
@@ -1174,7 +1151,7 @@ TEST_CASE("SharedRealm: coordinator schema cache") {
 
     SECTION("transaction version is bumped after refresh() following external non-schema write") {
         external_write(config, [](auto& wt) {
-            wt.get_table("class_object")->add_empty_row();
+            wt.get_table("class_object")->create_object();
         });
 
         r->refresh();
@@ -1273,7 +1250,6 @@ TEST_CASE("SharedRealm: coordinator schema cache") {
 
 TEST_CASE("SharedRealm: dynamic schema mode doesn't invalidate object schema pointers when schema hasn't changed") {
     TestFile config;
-    config.cache = false;
 
     // Prepopulate the Realm with the schema.
     Realm::Config config_with_schema = config;
@@ -1316,7 +1292,6 @@ TEST_CASE("SharedRealm: SchemaChangedFunction") {
     size_t schema_changed_called = 0;
     Schema changed_fixed_schema;
     TestFile config;
-    config.cache = false;
     auto dynamic_config = config;
 
     config.schema = Schema{
@@ -1329,6 +1304,7 @@ TEST_CASE("SharedRealm: SchemaChangedFunction") {
     };
     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") {
@@ -1340,7 +1316,7 @@ TEST_CASE("SharedRealm: SchemaChangedFunction") {
             };
             r1->update_schema(new_schema, 2);
             REQUIRE(schema_changed_called == 1);
-            REQUIRE(changed_fixed_schema.find("object3")->property_for_name("value")->table_column == 0);
+            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") {
@@ -1359,17 +1335,17 @@ TEST_CASE("SharedRealm: SchemaChangedFunction") {
         SECTION("Schema is changed by another Realm") {
             auto r2 = Realm::get_shared_realm(config);
             r2->begin_transaction();
-            r2->read_group().get_table("class_object1")->insert_column(0, type_String, "new col");
+            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")->table_column == 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")->insert_column(0, type_String, "new col");
+            r1->read_group().get_table("class_object1")->add_column(type_String, "new col");
             r1->commit_transaction();
             REQUIRE(schema_changed_called == 0);
         }
@@ -1390,26 +1366,26 @@ TEST_CASE("SharedRealm: SchemaChangedFunction") {
             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")->table_column == 0);
+            REQUIRE(changed_dynamic_schema.find("object1")->property_for_name("value")->column_key != ColKey{});
         }
 
-        SECTION("Non schema related transaction will alway trigger in dynamic mode") {
+        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")->table_column == 0);
+            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")->insert_column(0, type_String, "new col");
+            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")->table_column == 1);
+            REQUIRE(changed_dynamic_schema.find("object1")->property_for_name("value")->column_key != ColKey{});
         }
     }
 }
@@ -1418,7 +1394,6 @@ TEST_CASE("SharedRealm: SchemaChangedFunction") {
 TEST_CASE("SharedRealm: compact on launch") {
     // Make compactable Realm
     TestFile config;
-    config.cache = false;
     config.automatic_change_notifications = false;
     int num_opens = 0;
     config.should_compact_on_launch_function = [&](size_t total_bytes, size_t used_bytes) {
@@ -1437,9 +1412,8 @@ TEST_CASE("SharedRealm: compact on launch") {
     r->begin_transaction();
     auto table = r->read_group().get_table("class_object");
     size_t count = 1000;
-    table->add_empty_row(count);
     for (size_t i = 0; i < count; ++i)
-        table->set_string(0, i, util::format("Foo_%1", i % 10).c_str());
+        table->create_object().set_all(util::format("Foo_%1", i % 10).c_str());
     r->commit_transaction();
     REQUIRE(table->size() == count);
     r->close();
@@ -1459,12 +1433,13 @@ TEST_CASE("SharedRealm: compact on launch") {
         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.async([](std::exception_ptr) { });
+        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([&]{
@@ -1559,7 +1534,6 @@ TEST_CASE("BindingContext is notified about delivery of change notifications") {
     _impl::RealmCoordinator::assert_no_open_realms();
 
     InMemoryTestFile config;
-    config.cache = false;
     config.automatic_change_notifications = false;
 
     auto r = Realm::get_shared_realm(config);
@@ -1569,7 +1543,7 @@ TEST_CASE("BindingContext is notified about delivery of change notifications") {
         }},
     });
 
-    auto coordinator = _impl::RealmCoordinator::get_existing_coordinator(config.path);
+    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") {
@@ -1602,10 +1576,10 @@ TEST_CASE("BindingContext is notified about delivery of change notifications") {
             binding_context_start_notify_calls = 0;
             binding_context_end_notify_calls = 0;
             JoiningThread([&] {
-                auto r2 = coordinator->get_realm();
+                auto r2 = coordinator->get_realm(util::Scheduler::get_frozen());
                 r2->begin_transaction();
                 auto table2 = r2->read_group().get_table("class_object");
-                table2->add_empty_row();
+                table2->create_object();
                 r2->commit_transaction();
             });
             advance_and_notify(*r);
@@ -1619,8 +1593,9 @@ TEST_CASE("BindingContext is notified about delivery of change notifications") {
         static int binding_context_end_notify_calls = 0;
         static int notification_calls = 0;
 
-        Results results1(r, table->where().greater_equal(0, 0));
-        Results results2(r, table->where().less(0, 10));
+        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);
@@ -1655,7 +1630,7 @@ TEST_CASE("BindingContext is notified about delivery of change notifications") {
             notification_calls = 0;
             coordinator->on_change();
             r->begin_transaction();
-            table->add_empty_row();
+            table->create_object();
             r->commit_transaction();
             REQUIRE(binding_context_start_notify_calls == 1);
             REQUIRE(binding_context_end_notify_calls == 1);
@@ -1666,10 +1641,10 @@ TEST_CASE("BindingContext is notified about delivery of change notifications") {
             binding_context_end_notify_calls = 0;
             notification_calls = 0;
             JoiningThread([&] {
-                auto r2 = coordinator->get_realm();
+                auto r2 = coordinator->get_realm(util::Scheduler::get_frozen());
                 r2->begin_transaction();
                 auto table2 = r2->read_group().get_table("class_object");
-                table2->add_empty_row();
+                table2->create_object();
                 r2->commit_transaction();
             });
             advance_and_notify(*r);
@@ -1715,9 +1690,9 @@ TEST_CASE("BindingContext is notified about delivery of change notifications") {
             do_close = true;
 
             JoiningThread([&] {
-                auto r = coordinator->get_realm();
+                auto r = coordinator->get_realm(util::Scheduler::get_frozen());
                 r->begin_transaction();
-                r->read_group().get_table("class_object")->add_empty_row();
+                r->read_group().get_table("class_object")->create_object();
                 r->commit_transaction();
             });
 
@@ -1739,9 +1714,9 @@ TEST_CASE("BindingContext is notified about delivery of change notifications") {
             do_close = true;
 
             JoiningThread([&] {
-                auto r = coordinator->get_realm();
+                auto r = coordinator->get_realm(util::Scheduler::get_frozen());
                 r->begin_transaction();
-                r->read_group().get_table("class_object")->add_empty_row();
+                r->read_group().get_table("class_object")->create_object();
                 r->commit_transaction();
             });
 
@@ -1756,7 +1731,7 @@ TEST_CASE("Statistics on Realms") {
     _impl::RealmCoordinator::assert_no_open_realms();
 
     InMemoryTestFile config;
-    config.cache = false;
+    // config.cache = false;
     config.automatic_change_notifications = false;
 
     auto r = Realm::get_shared_realm(config);
@@ -1772,7 +1747,7 @@ TEST_CASE("Statistics on Realms") {
     }
 }
 
-#if REALM_PLATFORM_APPLE
+#if REALM_PLATFORM_APPLE && NOTIFIER_BACKGROUND_ERRORS
 TEST_CASE("BindingContext is notified in case of notifier errors") {
     _impl::RealmCoordinator::assert_no_open_realms();
 
@@ -1797,7 +1772,6 @@ TEST_CASE("BindingContext is notified in case of notifier errors") {
     };
 
     InMemoryTestFile config;
-    config.cache = false;
     config.automatic_change_notifications = false;
 
     auto r = Realm::get_shared_realm(config);
@@ -1807,7 +1781,7 @@ TEST_CASE("BindingContext is notified in case of notifier errors") {
       }},
     });
 
-    auto coordinator = _impl::RealmCoordinator::get_existing_coordinator(config.path);
+    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;
@@ -1852,7 +1826,7 @@ TEST_CASE("RealmCoordinator: get_unbound_realm()") {
         }},
     };
 
-    ThreadSafeReference<Realm> ref;
+    ThreadSafeReference ref;
     std::thread([&] { ref = _impl::RealmCoordinator::get_coordinator(config)->get_unbound_realm(); }).join();
 
     SECTION("checks thread after being resolved") {
@@ -1875,34 +1849,7 @@ TEST_CASE("RealmCoordinator: get_unbound_realm()") {
         util::EventLoop::main().run_until([&] { return called; });
     }
 
-    SECTION("does not check thread if resolved using an execution context") {
-        auto realm = Realm::get_shared_realm(std::move(ref), AbstractExecutionContextID(1));
-        REQUIRE_NOTHROW(realm->verify_thread());
-        std::thread([&] {
-            REQUIRE_NOTHROW(realm->verify_thread());
-        }).join();
-    }
-
-    SECTION("resolves to existing cached Realm for the thread if caching is enabled") {
-        auto r1 = Realm::get_shared_realm(config);
-        auto r2 = Realm::get_shared_realm(std::move(ref));
-        REQUIRE(r1 == r2);
-    }
-
-    SECTION("resolves to existing cached Realm for the execution context if caching is enabled") {
-        config.execution_context = AbstractExecutionContextID(1);
-        auto r1 = Realm::get_shared_realm(config);
-        config.execution_context = AbstractExecutionContextID(2);
-        auto r2 = Realm::get_shared_realm(config);
-        auto r3 = Realm::get_shared_realm(std::move(ref), AbstractExecutionContextID(1));
-        REQUIRE(r1 == r3);
-        REQUIRE(r1 != r2);
-        REQUIRE(r2 != r3);
-    }
-
     SECTION("resolves to a new Realm if caching is disabled") {
-        // Cache disabled for local realm, enabled for unbound
-        config.cache = false;
         auto r1 = Realm::get_shared_realm(config);
         auto r2 = Realm::get_shared_realm(std::move(ref));
         REQUIRE(r1 != r2);
@@ -1912,10 +1859,5 @@ TEST_CASE("RealmCoordinator: get_unbound_realm()") {
         auto r3 = Realm::get_shared_realm(std::move(ref));
         REQUIRE(r1 != r3);
         REQUIRE(r2 != r3);
-
-        // New local with cache enabled should grab the resolved unbound
-        config.cache = true;
-        auto r4 = Realm::get_shared_realm(config);
-        REQUIRE(r4 == r2);
     }
 }

文件差异内容过多而无法显示
+ 287 - 202
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/results.cpp


+ 19 - 39
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/schema.cpp

@@ -24,7 +24,6 @@
 #include "property.hpp"
 #include "schema.hpp"
 
-#include <realm/descriptor.hpp>
 #include <realm/group.hpp>
 #include <realm/table.hpp>
 
@@ -114,17 +113,9 @@ TEST_CASE("ObjectSchema") {
 
     SECTION("from a Group") {
         Group g;
-        TableRef pk = g.add_table("pk");
-        pk->add_column(type_String, "pk_table");
-        pk->add_column(type_String, "pk_property");
-        pk->add_empty_row();
-        pk->set_string(0, 0, "table");
-        pk->set_string(1, 0, "pk");
-
-        TableRef table = g.add_table("class_table");
-        TableRef target = g.add_table("class_target");
 
-        table->add_column(type_Int, "pk");
+        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");
@@ -145,13 +136,8 @@ TEST_CASE("ObjectSchema") {
         table->add_column(type_Binary, "data?", true);
         table->add_column(type_Timestamp, "date?", true);
 
-        table->add_column(type_Table, "subtable 1");
-        size_t col = table->add_column(type_Table, "subtable 2");
-        table->get_subdescriptor(col)->add_column(type_Int, "value");
-
         auto add_list = [](TableRef table, DataType type, StringData name, bool nullable) {
-            size_t col = table->add_column(type_Table, name);
-            table->get_subdescriptor(col)->add_column(type, ObjectStore::ArrayColumnName, nullptr, nullable);
+            table->add_column_list(type, name, nullable);
         };
 
         add_list(table, type_Int, "int array", false);
@@ -169,30 +155,32 @@ TEST_CASE("ObjectSchema") {
         add_list(table, type_Binary, "data? array", true);
         add_list(table, type_Timestamp, "date? array", true);
 
-        size_t indexed_start = table->get_column_count();
-        table->add_column(type_Int, "indexed int");
-        table->add_column(type_Bool, "indexed bool");
-        table->add_column(type_String, "indexed string");
-        table->add_column(type_Timestamp, "indexed date");
+        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"));
 
-        table->add_column(type_Int, "indexed int?", true);
-        table->add_column(type_Bool, "indexed bool?", true);
-        table->add_column(type_String, "indexed string?", true);
-        table->add_column(type_Timestamp, "indexed date?", true);
+        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 (size_t i = indexed_start; i < table->get_column_count(); ++i)
-            table->add_search_index(i);
+        for (auto col : indexed_cols)
+            table->add_search_index(col);
 
-        ObjectSchema os(g, "table");
+        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->table_column == expected_col++); \
+    REQUIRE(prop->column_key == *expected_col++); \
 } while (0)
 
-        size_t expected_col = 0;
+        auto all_column_keys = table->get_column_keys();
+        auto expected_col = all_column_keys.begin();
 
         REQUIRE(os.property_for_name("nonexistent property") == nullptr);
 
@@ -217,11 +205,6 @@ TEST_CASE("ObjectSchema") {
         REQUIRE_PROPERTY("data?", Data|PropertyType::Nullable);
         REQUIRE_PROPERTY("date?", Date|PropertyType::Nullable);
 
-        // Unsupported column type should be skipped entirely
-        REQUIRE(os.property_for_name("subtable 1") == nullptr);
-        REQUIRE(os.property_for_name("subtable 2") == nullptr);
-        expected_col += 2;
-
         REQUIRE_PROPERTY("int array", Int|PropertyType::Array);
         REQUIRE_PROPERTY("bool array", Bool|PropertyType::Array);
         REQUIRE_PROPERTY("float array", Float|PropertyType::Array);
@@ -246,9 +229,6 @@ TEST_CASE("ObjectSchema") {
         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});
-
-        pk->set_string(1, 0, "nonexistent property");
-        REQUIRE(ObjectSchema(g, "table").primary_key_property() == nullptr);
     }
 }
 

+ 429 - 252
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/thread_safe_reference.cpp

@@ -28,15 +28,15 @@
 #include "results.hpp"
 #include "schema.hpp"
 #include "thread_safe_reference.hpp"
+#include "util/scheduler.hpp"
 
 #include "impl/object_accessor_impl.hpp"
 
+#include <realm/db.hpp>
 #include <realm/history.hpp>
+#include <realm/string_data.hpp>
 #include <realm/util/optional.hpp>
 
-#include <future>
-#include <thread>
-
 using namespace realm;
 
 static TableRef get_table(Realm& realm, StringData object_name) {
@@ -70,7 +70,6 @@ TEST_CASE("thread safe reference") {
     };
 
     InMemoryTestFile config;
-    config.cache = false;
     config.automatic_change_notifications = false;
     SharedRealm r = Realm::get_shared_realm(config);
     r->update_schema(schema);
@@ -80,123 +79,111 @@ TEST_CASE("thread safe reference") {
     auto foo = create_object(r, "foo object", {{"ignore me", INT64_C(0)}});
     r->commit_transaction();
 
-    SECTION("disallowed during write transactions") {
+    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_THROWS(r->obtain_thread_safe_reference(foo));
+            REQUIRE_NOTHROW(ThreadSafeReference(foo));
         }
         SECTION("resolve") {
-            auto ref = r->obtain_thread_safe_reference(foo);
+            auto ref = ThreadSafeReference(foo);
             r->begin_transaction();
-            REQUIRE_THROWS(r->resolve_thread_safe_reference(std::move(ref)));
+            REQUIRE_NOTHROW(ref.resolve<Object>(r));
         }
     }
 
     SECTION("cleanup properly unpins version") {
         auto history = make_in_realm_history(config.path);
-        SharedGroup shared_group(*history, config.options());
+        auto shared_group = DB::create(*history, config.options());
 
         auto get_current_version = [&]() -> VersionID {
-            shared_group.begin_read();
-            auto version = shared_group.get_version_of_current_transaction();
-            shared_group.end_read();
+            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(r->obtain_thread_safe_reference(foo));
+        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.begin_read(reference_version)); shared_group.end_read(); // Ensure pinned
-        bool did_run_section = false;
+        REQUIRE_NOTHROW(shared_group->start_read(reference_version)); // Ensure pinned
 
-        SECTION("destroyed without being resolved") {
-            did_run_section = true;
-            ref = {}; // Destroy thread safe reference, unpinning version
-        }
-        SECTION("exception thrown on resolve") {
-            did_run_section = true;
-            r->begin_transaction(); // Get into state that'll throw exception on resolve
-            REQUIRE_THROWS(r->resolve_thread_safe_reference(std::move(*ref)));
-            r->commit_transaction();
-        }
-        {
-            // Clean up old versions by creating a write with dirty data
-            r->begin_transaction();
-            foo.row().set_int(0, 1);
-            r->commit_transaction();
-        }
-
-        catch2_ensure_section_run_workaround(did_run_section, "cleanup properly unpins version", [&](){
-            REQUIRE_THROWS(shared_group.begin_read(reference_version)); // Ensure unpinned
-        });
+        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") {
-#ifndef _MSC_VER // Visual C++'s buggy <future> needs its template argument to be default constructible so skip this test
         SECTION("resolves at older version") {
             r->begin_transaction();
-            auto num = create_object(r, "int object", {{"value", INT64_C(7)}});
+            Object num = create_object(r, "int object", {{"value", INT64_C(7)}});
             r->commit_transaction();
 
-            REQUIRE(num.row().get_int(0) == 7);
-            auto ref = std::async([config]() -> auto {
-                SharedRealm r = Realm::get_shared_realm(config);
-                Object num = Object(r, "int object", 0);
-                REQUIRE(num.row().get_int(0) == 7);
+            ColKey col = num.get_object_schema().property_for_name("value")->column_key;
+            ObjKey k = num.obj().get_key();
 
-                r->begin_transaction();
-                num.row().set_int(0, 9);
-                r->commit_transaction();
+            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();
 
-                return r->obtain_thread_safe_reference(num);
-            }).get();
+                ref = num;
+            };
 
-            REQUIRE(num.row().get_int(0) == 7);
-            Object num_prime = r->resolve_thread_safe_reference(std::move(ref));
-            REQUIRE(num_prime.row().get_int(0) == 9);
-            REQUIRE(num.row().get_int(0) == 9);
+            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.row().set_int(0, 11);
+            num.obj().set(col, 11);
             r->commit_transaction();
 
-            REQUIRE(num_prime.row().get_int(0) == 11);
-            REQUIRE(num.row().get_int(0) == 11);
+            REQUIRE(num_prime.obj().get<Int>(col) == 11);
+            REQUIRE(num.obj().get<Int>(col) == 11);
         }
-#endif
 
         SECTION("resolve at newer version") {
             r->begin_transaction();
-            auto num = create_object(r, "int object", {{"value", INT64_C(7)}});
+            Object num = create_object(r, "int object", {{"value", INT64_C(7)}});
             r->commit_transaction();
 
-            REQUIRE(num.row().get_int(0) == 7);
-            auto ref = r->obtain_thread_safe_reference(num);
-            std::thread([ref = std::move(ref), config]() mutable {
-                SharedRealm r = Realm::get_shared_realm(config);
-                Object num = Object(r, "int object", 0);
+            ColKey col = num.get_object_schema().property_for_name("value")->column_key;
+            ObjKey k = num.obj().get_key();
 
-                r->begin_transaction();
-                num.row().set_int(0, 9);
-                r->commit_transaction();
-                REQUIRE(num.row().get_int(0) == 9);
+            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);
 
-                Object num_prime = r->resolve_thread_safe_reference(std::move(ref));
-                REQUIRE(num_prime.row().get_int(0) == 9);
+                r2->begin_transaction();
+                num.obj().set(col, 9);
+                r2->commit_transaction();
+                REQUIRE(num.obj().get<Int>(col) == 9);
 
-                r->begin_transaction();
-                num_prime.row().set_int(0, 11);
-                r->commit_transaction();
+                Object num_prime = ref.resolve<Object>(r2);
+                REQUIRE(num_prime.obj().get<Int>(col) == 9);
 
-                REQUIRE(num.row().get_int(0) == 11);
-                REQUIRE(num_prime.row().get_int(0) == 11);
-            }).join();
+                r2->begin_transaction();
+                num_prime.obj().set(col, 11);
+                r2->commit_transaction();
 
-            REQUIRE(num.row().get_int(0) == 7);
+                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.row().get_int(0) == 11);
+            REQUIRE(num.obj().get<Int>(col) == 11);
         }
 
         SECTION("resolve at newer version when schema is specified") {
@@ -204,76 +191,79 @@ TEST_CASE("thread safe reference") {
             config.schema = schema;
             SharedRealm r = Realm::get_shared_realm(config);
             r->begin_transaction();
-            auto num = create_object(r, "int object", {{"value", INT64_C(7)}});
+            Object num = create_object(r, "int object", {{"value", INT64_C(7)}});
             r->commit_transaction();
 
-            auto ref = r->obtain_thread_safe_reference(num);
+            ColKey col = num.get_object_schema().property_for_name("value")->column_key;
+            auto ref = ThreadSafeReference(num);
 
             r->begin_transaction();
-            num.row().set_int(0, 9);
+            num.obj().set(col, 9);
             r->commit_transaction();
 
-            REQUIRE_NOTHROW(r->resolve_thread_safe_reference(std::move(ref)));
+            REQUIRE_NOTHROW(ref.resolve<Object>(r));
         }
 
         SECTION("resolve references at multiple versions") {
             auto commit_new_num = [&](int64_t value) -> Object {
                 r->begin_transaction();
-                auto num = create_object(r, "int object", {{"value", value}});
+                Object num = create_object(r, "int object", {{"value", value}});
                 r->commit_transaction();
                 return num;
             };
 
-            auto ref1 = r->obtain_thread_safe_reference(commit_new_num(1));
-            auto ref2 = r->obtain_thread_safe_reference(commit_new_num(2));
-            std::thread([ref1 = std::move(ref1), ref2 = std::move(ref2), config]() mutable {
-                SharedRealm r = Realm::get_shared_realm(config);
-                Object num1 = r->resolve_thread_safe_reference(std::move(ref1));
-                Object num2 = r->resolve_thread_safe_reference(std::move(ref2));
+            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);
 
-                REQUIRE(num1.row().get_int(0) == 1);
-                REQUIRE(num2.row().get_int(0) == 2);
-            }).join();
+                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();
-        auto num = create_object(r, "int object", {{"value", INT64_C(7)}});
+        Object num = create_object(r, "int object", {{"value", INT64_C(7)}});
         r->commit_transaction();
 
-        REQUIRE(num.row().get_int(0) == 7);
-        auto ref = r->obtain_thread_safe_reference(num);
+        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 = r->resolve_thread_safe_reference(std::move(ref));
-                REQUIRE(num.row().get_int(0) == 7);
+                Object num = ref.resolve<Object>(r);
+                REQUIRE(num.obj().get<Int>(col) == 7);
                 r->begin_transaction();
-                num.row().set_int(0, 9);
+                num.obj().set(col, 9);
                 r->commit_transaction();
-                REQUIRE(num.row().get_int(0) == 9);
+                REQUIRE(num.obj().get<Int>(col) == 9);
             }
-            REQUIRE(num.row().get_int(0) == 9);
+            REQUIRE(num.obj().get<Int>(col) == 9);
         }
         SECTION("different realm") {
             did_run_section = true;
             {
                 SharedRealm r = Realm::get_shared_realm(config);
-                Object num = r->resolve_thread_safe_reference(std::move(ref));
-                REQUIRE(num.row().get_int(0) == 7);
+                Object num = ref.resolve<Object>(r);
+                REQUIRE(num.obj().get<Int>(col) == 7);
                 r->begin_transaction();
-                num.row().set_int(0, 9);
+                num.obj().set(col, 9);
                 r->commit_transaction();
-                REQUIRE(num.row().get_int(0) == 9);
+                REQUIRE(num.obj().get<Int>(col) == 9);
             }
-            REQUIRE(num.row().get_int(0) == 7);
+            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.row().get_int(0) == 9);
+            REQUIRE(num.obj().get<Int>(col) == 9);
             r->cancel_transaction();
         });
     }
@@ -285,73 +275,77 @@ TEST_CASE("thread safe reference") {
             auto num = create_object(r, "int object", {{"value", INT64_C(0)}});
             r->commit_transaction();
 
-            auto ref_str = r->obtain_thread_safe_reference(str);
-            auto ref_num = r->obtain_thread_safe_reference(num);
-            std::thread([ref_str = std::move(ref_str), ref_num = std::move(ref_num), config]() mutable {
-                SharedRealm r = Realm::get_shared_realm(config);
-                Object str = r->resolve_thread_safe_reference(std::move(ref_str));
-                Object num = r->resolve_thread_safe_reference(std::move(ref_num));
+            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.row().get_string(0).is_null());
-                REQUIRE(num.row().get_int(0) == 0);
+                REQUIRE(str.obj().get<String>(col_str).is_null());
+                REQUIRE(num.obj().get<Int>(col_num) == 0);
 
-                r->begin_transaction();
-                str.row().set_string(0, "the meaning of life");
-                num.row().set_int(0, 42);
-                r->commit_transaction();
-            }).join();
+                r2->begin_transaction();
+                str.obj().set(col_str, "the meaning of life");
+                num.obj().set(col_num, 42);
+                r2->commit_transaction();
+            }
 
-            REQUIRE(str.row().get_string(0).is_null());
-            REQUIRE(num.row().get_int(0) == 0);
+            REQUIRE(str.obj().get<String>(col_str).is_null());
+            REQUIRE(num.obj().get<Int>(col_num) == 0);
 
             r->refresh();
 
-            REQUIRE(str.row().get_string(0) == "the meaning of life");
-            REQUIRE(num.row().get_int(0) == 42);
+            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)}});
-            create_object(r, "int array object", {{"value", AnyVector{zero}}});
-            List list(r, *get_table(*r, "int array object"), 0, 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_int(0) == 0);
-            auto ref = r->obtain_thread_safe_reference(list);
-            std::thread([ref = std::move(ref), config]() mutable {
-                SharedRealm r = Realm::get_shared_realm(config);
-                List list = r->resolve_thread_safe_reference(std::move(ref));
+            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_int(0) == 0);
+                REQUIRE(list.get(0).get<int64_t>(int_obj_col) == 0);
 
-                r->begin_transaction();
+                r2->begin_transaction();
                 list.remove_all();
-                auto one = create_object(r, "int object", {{"value", INT64_C(1)}});
-                auto two = create_object(r, "int object", {{"value", INT64_C(2)}});
-                list.add(one.row());
-                list.add(two.row());
-                r->commit_transaction();
+                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_int(0) == 1);
-                REQUIRE(list.get(1).get_int(0) == 2);
-            }).join();
+                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_int(0) == 0);
+            REQUIRE(list.get(0).get<int64_t>(int_obj_col) == 0);
 
             r->refresh();
 
             REQUIRE(list.size() == 2);
-            REQUIRE(list.get(0).get_int(0) == 1);
-            REQUIRE(list.get(1).get_int(0) == 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 results = Results(r, table.where().not_equal(0, "C")).sort({table, {{0}}, {false}});
+            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}});
@@ -361,45 +355,46 @@ TEST_CASE("thread safe reference") {
             r->commit_transaction();
 
             REQUIRE(results.size() == 3);
-            REQUIRE(results.get(0).get_string(0) == "D");
-            REQUIRE(results.get(1).get_string(0) == "B");
-            REQUIRE(results.get(2).get_string(0) == "A");
-            auto ref = r->obtain_thread_safe_reference(results);
-            std::thread([ref = std::move(ref), config]() mutable {
-                SharedRealm r = Realm::get_shared_realm(config);
-                Results results = r->resolve_thread_safe_reference(std::move(ref));
+            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_string(0) == "D");
-                REQUIRE(results.get(1).get_string(0) == "B");
-                REQUIRE(results.get(2).get_string(0) == "A");
+                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->begin_transaction();
-                results.get(2).move_last_over();
-                results.get(0).move_last_over();
-                create_object(r, "string object", {{"value", "E"s}});
-                r->commit_transaction();
+                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_string(0) == "E");
-                REQUIRE(results.get(1).get_string(0) == "B");
-            }).join();
+                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_string(0) == "D");
-            REQUIRE(results.get(1).get_string(0) == "B");
-            REQUIRE(results.get(2).get_string(0) == "A");
+            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_string(0) == "E");
-            REQUIRE(results.get(1).get_string(0) == "B");
+            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 results = Results(r, table.where()).distinct({table, {{0}}}).sort({{"value", true}});
+            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}});
@@ -408,63 +403,64 @@ TEST_CASE("thread safe reference") {
             r->commit_transaction();
 
             REQUIRE(results.size() == 2);
-            REQUIRE(results.get(0).get_string(0) == "A");
-            REQUIRE(results.get(1).get_string(0) == "B");
-            auto ref = r->obtain_thread_safe_reference(results);
-            std::thread([ref = std::move(ref), config]() mutable {
-                SharedRealm r = Realm::get_shared_realm(config);
-                Results results = r->resolve_thread_safe_reference(std::move(ref));
+            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_string(0) == "A");
-                REQUIRE(results.get(1).get_string(0) == "B");
+                REQUIRE(results.get(0).get<StringData>(col) == "A");
+                REQUIRE(results.get(1).get<StringData>(col) == "B");
 
-                r->begin_transaction();
-                results.get(0).move_last_over();
-                create_object(r, "string object", {{"value", "C"s}});
-                r->commit_transaction();
+                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_string(0) == "A");
-                REQUIRE(results.get(1).get_string(0) == "B");
-                REQUIRE(results.get(2).get_string(0) == "C");
-            }).join();
+                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_string(0) == "A");
-            REQUIRE(results.get(1).get_string(0) == "B");
+            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_string(0) == "A");
-            REQUIRE(results.get(1).get_string(0) == "B");
-            REQUIRE(results.get(2).get_string(0) == "C");
+            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();
-            create_object(r, "int array", {{"value", AnyVector{INT64_C(0)}}});
-            List list(r, *get_table(*r, "int array"), 0, 0);
+            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 = r->obtain_thread_safe_reference(list);
-            std::thread([ref = std::move(ref), config]() mutable {
-                SharedRealm r = Realm::get_shared_realm(config);
-                List list = r->resolve_thread_safe_reference(std::move(ref));
+            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);
 
-                r->begin_transaction();
+                r2->begin_transaction();
                 list.remove_all();
-                list.add(1);
-                list.add(2);
-                r->commit_transaction();
+                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);
-            }).join();
+            };
 
             REQUIRE(list.size() == 1);
             REQUIRE(list.get<int64_t>(0) == 0);
@@ -478,8 +474,9 @@ TEST_CASE("thread safe reference") {
 
         SECTION("sorted int results") {
             r->begin_transaction();
-            create_object(r, "int array", {{"value", AnyVector{INT64_C(0), INT64_C(2), INT64_C(1)}}});
-            List list(r, *get_table(*r, "int array"), 0, 0);
+            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}});
@@ -488,10 +485,11 @@ TEST_CASE("thread safe reference") {
             REQUIRE(results.get<int64_t>(0) == 0);
             REQUIRE(results.get<int64_t>(1) == 1);
             REQUIRE(results.get<int64_t>(2) == 2);
-            auto ref = r->obtain_thread_safe_reference(results);
+            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 = r->resolve_thread_safe_reference(std::move(ref));
+                Results results = ref.resolve<Results>(r);
 
                 REQUIRE(results.size() == 3);
                 REQUIRE(results.get<int64_t>(0) == 0);
@@ -499,9 +497,10 @@ TEST_CASE("thread safe reference") {
                 REQUIRE(results.get<int64_t>(2) == 2);
 
                 r->begin_transaction();
-                List list(r, *get_table(*r, "int array"), 0, 0);
+                auto table = get_table(*r, "int array");
+                List list(r, *table->begin(), table->get_column_key("value"));
                 list.remove(1);
-                list.add(-1);
+                list.add(int64_t(-1));
                 r->commit_transaction();
 
                 REQUIRE(results.size() == 3);
@@ -525,8 +524,10 @@ TEST_CASE("thread safe reference") {
 
         SECTION("distinct int results") {
             r->begin_transaction();
-            create_object(r, "int array", {{"value", AnyVector{INT64_C(3), INT64_C(2), INT64_C(1), INT64_C(1), INT64_C(2)}}});
-            List list(r, *get_table(*r, "int array"), 0, 0);
+            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}});
@@ -536,10 +537,11 @@ TEST_CASE("thread safe reference") {
             REQUIRE(results.get<int64_t>(1) == 2);
             REQUIRE(results.get<int64_t>(2) == 3);
 
-            auto ref = r->obtain_thread_safe_reference(results);
+            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 = r->resolve_thread_safe_reference(std::move(ref));
+                Results results = ref.resolve<Results>(r);
 
                 REQUIRE(results.size() == 3);
                 REQUIRE(results.get<int64_t>(0) == 1);
@@ -547,7 +549,8 @@ TEST_CASE("thread safe reference") {
                 REQUIRE(results.get<int64_t>(2) == 3);
 
                 r->begin_transaction();
-                List list(r, *get_table(*r, "int array"), 0, 0);
+                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();
@@ -570,49 +573,49 @@ TEST_CASE("thread safe reference") {
         }
 
         SECTION("multiple types") {
-            auto results = Results(r, get_table(*r, "int object")->where().equal(0, 5));
+            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)}});
-            create_object(r, "int array object", {{"value", AnyVector{}}});
-            List list(r, *get_table(*r, "int array object"), 0, 0);
+            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_int(0) == 5);
-            auto ref_num = r->obtain_thread_safe_reference(num);
-            auto ref_list = r->obtain_thread_safe_reference(list);
-            auto ref_results = r->obtain_thread_safe_reference(results);
-            std::thread([ref_num = std::move(ref_num), ref_list = std::move(ref_list),
-                         ref_results = std::move(ref_results), config]() mutable {
-                SharedRealm r = Realm::get_shared_realm(config);
-                Object num = r->resolve_thread_safe_reference(std::move(ref_num));
-                List list = r->resolve_thread_safe_reference(std::move(ref_list));
-                Results results = r->resolve_thread_safe_reference(std::move(ref_results));
+            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_int(0) == 5);
+                REQUIRE(results.get(0).get<int64_t>(int_obj_col) == 5);
 
-                r->begin_transaction();
-                num.row().set_int(0, 6);
-                list.add(num.row().get_index());
-                r->commit_transaction();
+                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_int(0) == 6);
+                REQUIRE(list.get(0).get<int64_t>(int_obj_col) == 6);
                 REQUIRE(results.size() == 0);
-            }).join();
+            }
 
             REQUIRE(list.size() == 0);
             REQUIRE(results.size() == 1);
-            REQUIRE(results.get(0).get_int(0) == 5);
+            REQUIRE(results.get(0).get<int64_t>(int_obj_col) == 5);
 
             r->refresh();
 
             REQUIRE(list.size() == 1);
-            REQUIRE(list.get(0).get_int(0) == 6);
+            REQUIRE(list.get(0).get<int64_t>(int_obj_col) == 6);
             REQUIRE(results.size() == 0);
         }
     }
@@ -620,13 +623,13 @@ TEST_CASE("thread safe reference") {
     SECTION("resolve at version where handed over thing has been deleted") {
         Object obj;
         auto delete_and_resolve = [&](auto&& list) {
-            auto ref = r->obtain_thread_safe_reference(list);
+            auto ref = ThreadSafeReference(list);
 
             r->begin_transaction();
-            obj.row().move_last_over();
+            obj.obj().remove();
             r->commit_transaction();
 
-            return r->resolve_thread_safe_reference(std::move(ref));
+            return ref.resolve<typename std::remove_reference<decltype(list)>::type>(r);
         };
 
         SECTION("object") {
@@ -640,7 +643,8 @@ TEST_CASE("thread safe reference") {
         SECTION("object list") {
             r->begin_transaction();
             obj = create_object(r, "int array object", {{"value", AnyVector{AnyDict{{"value", INT64_C(0)}}}}});
-            List list(r, *get_table(*r, "int array object"), 0, 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());
@@ -648,8 +652,9 @@ TEST_CASE("thread safe reference") {
 
         SECTION("int list") {
             r->begin_transaction();
-            obj = create_object(r, "int array", {{"value", AnyVector{INT64_C(1)}}});
-            List list(r, *get_table(*r, "int array"), 0, 0);
+            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());
@@ -658,7 +663,8 @@ TEST_CASE("thread safe reference") {
         SECTION("object results") {
             r->begin_transaction();
             obj = create_object(r, "int array object", {{"value", AnyVector{AnyDict{{"value", INT64_C(0)}}}}});
-            List list(r, *get_table(*r, "int array object"), 0, 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}}));
@@ -668,22 +674,193 @@ TEST_CASE("thread safe reference") {
 
         SECTION("int results") {
             r->begin_transaction();
-            obj = create_object(r, "int array", {{"value", AnyVector{INT64_C(1)}}});
-            List list(r, *get_table(*r, "int array"), 0, 0);
+            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();
 
-            auto results = delete_and_resolve(list.sort({{"self", true}}));
+            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() == 0);
+            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 = r->obtain_thread_safe_reference(foo);
+            auto ref = ThreadSafeReference(foo);
             r = nullptr;
             r = Realm::get_shared_realm(config);
-            REQUIRE_NOTHROW(r->resolve_thread_safe_reference(std::move(ref)));
+            REQUIRE_NOTHROW(ref.resolve<Object>(r));
         }
     }
 
@@ -693,17 +870,17 @@ TEST_CASE("thread safe reference") {
         r->commit_transaction();
         REQUIRE(num.get_object_schema().name == "int object");
 
-        auto ref = r->obtain_thread_safe_reference(num);
-        std::thread([ref = std::move(ref), config]() mutable {
-            SharedRealm r = Realm::get_shared_realm(config);
-            Object num = r->resolve_thread_safe_reference(std::move(ref));
+        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");
-        }).join();
+        }
     }
 
-    SECTION("disallow multiple resolves") {
-        auto ref = r->obtain_thread_safe_reference(foo);
-        r->resolve_thread_safe_reference(std::move(ref));
-        REQUIRE_THROWS(r->resolve_thread_safe_reference(std::move(ref)));
+    SECTION("allow multiple resolves") {
+        auto ref = ThreadSafeReference(foo);
+        ref.resolve<Object>(r);
+        REQUIRE_NOTHROW(ref.resolve<Object>(r));
     }
 }

文件差异内容过多而无法显示
+ 195 - 429
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/transaction_log_parsing.cpp


+ 3 - 2
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/util/index_helpers.hpp

@@ -29,8 +29,9 @@
 } while (0)
 
 #define REQUIRE_COLUMN_INDICES(columns, col, ...) do { \
-    REQUIRE((columns).size() > col); \
-    REQUIRE_INDICES((columns)[col], __VA_ARGS__); \
+    auto it = (columns).find(col); \
+    REQUIRE(it != (columns).end()); \
+    REQUIRE_INDICES(it->second, __VA_ARGS__); \
 } while (0)
 
 #define REQUIRE_MOVES(c, ...) do { \

+ 32 - 24
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/util/test_file.cpp

@@ -29,6 +29,7 @@
 #include "schema.hpp"
 #endif
 
+#include <realm/db.hpp>
 #include <realm/disable_sync_to_disk.hpp>
 #include <realm/history.hpp>
 #include <realm/string_data.hpp>
@@ -82,7 +83,17 @@ TestFile::TestFile()
 
 TestFile::~TestFile()
 {
-    unlink(path.c_str());
+    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()
@@ -123,8 +134,9 @@ SyncTestFile::SyncTestFile(SyncServer& server, std::string name, bool is_partial
     schema_mode = SchemaMode::Additive;
 }
 
-SyncServer::SyncServer(StartImmediately start_immediately)
-: m_server(util::make_temp_dir(), util::none, ([&] {
+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;
@@ -135,15 +147,13 @@ SyncServer::SyncServer(StartImmediately start_immediately)
 #else
     config.logger = new TestLogger;
 #endif
-    config.log_compaction_clock = this;
-#if REALM_SYNC_VER_MAJOR > 4 || (REALM_SYNC_VER_MAJOR == 4 && REALM_SYNC_VER_MINOR >= 7)
+    m_logger.reset(config.logger);
+    config.history_compaction_clock = this;
     config.disable_history_compaction = false;
-#else
-    config.enable_log_compaction = true;
-#endif
     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;
 })())
@@ -154,20 +164,8 @@ SyncServer::SyncServer(StartImmediately start_immediately)
     SyncManager::shared().set_log_level(util::Logger::Level::off);
 #endif
 
-    uint64_t port;
-    while (true) {
-        // Try to pick a random available port, or loop forever if other
-        // problems occur because there's no specific error for "port in use"
-        try {
-            port = fastrand(65536 - 1000) + 1000;
-            m_server.start("127.0.0.1", util::to_string(port));
-            break;
-        }
-        catch (std::runtime_error const&) {
-            continue;
-        }
-    }
-    m_url = util::format("realm://127.0.0.1:%1", port);
+    m_server.start();
+    m_url = util::format("realm://127.0.0.1:%1", m_server.listen_endpoint().port());
     if (start_immediately)
         start();
 }
@@ -305,17 +303,27 @@ private:
     std::map<_impl::RealmCoordinator*, std::weak_ptr<_impl::RealmCoordinator>> m_published_coordinators;
 } s_worker;
 
-void advance_and_notify(Realm& realm)
+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)
 {
-    _impl::RealmCoordinator::get_existing_coordinator(realm.config().path)->on_change();
+    on_change_but_no_notify(realm);
     realm.notify();
 }
 #endif

+ 21 - 13
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/tests/util/test_file.hpp

@@ -22,27 +22,28 @@
 #include "shared_realm.hpp"
 #include "util/tagged_bool.hpp"
 
-#include <realm/group_shared.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 {
-struct SyncConfig;
 class Schema;
 enum class SyncSessionStopPolicy;
+struct DBOptions;
+struct SyncConfig;
 }
 
-// {"identity":"test", "access": ["download", "upload"]}
-static const std::string s_test_token = "eyJpZGVudGl0eSI6InRlc3QiLCAiYWNjZXNzIjogWyJkb3dubG9hZCIsICJ1cGxvYWQiXX0=";
-
-#endif
-
 class JoiningThread {
 public:
     template<typename... Args>
@@ -59,13 +60,16 @@ struct TestFile : realm::Realm::Config {
     TestFile();
     ~TestFile();
 
-    auto options() const
+    // The file should outlive the object, ie. should not be deleted in destructor
+    void persist()
     {
-        realm::SharedGroupOptions options;
-        options.durability = in_memory ? realm::SharedGroupOptions::Durability::MemOnly
-                                       : realm::SharedGroupOptions::Durability::Full;
-        return options;
+        m_persist = true;
     }
+
+    realm::DBOptions options() const;
+
+private:
+    bool m_persist = false;
 };
 
 struct InMemoryTestFile : TestFile {
@@ -73,6 +77,7 @@ struct InMemoryTestFile : TestFile {
 };
 
 void advance_and_notify(realm::Realm& realm);
+void on_change_but_no_notify(realm::Realm& realm);
 
 #if REALM_ENABLE_SYNC
 
@@ -88,7 +93,7 @@ using StartImmediately = realm::util::TaggedBool<class StartImmediatelyTag>;
 
 class SyncServer : private realm::sync::Clock {
 public:
-    SyncServer(StartImmediately start_immediately=true);
+    SyncServer(StartImmediately start_immediately=true, std::string local_dir = "");
     ~SyncServer();
 
     void start();
@@ -96,6 +101,7 @@ public:
 
     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
@@ -104,6 +110,8 @@ public:
     }
 
 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;

+ 1 - 1
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/workflow/build.sh

@@ -23,7 +23,7 @@ rm -rf ci.build
 mkdir -p ci.build
 cd ci.build
 
-cmake_flags=""
+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}"

+ 4 - 2
Carthage/Checkouts/realm-cocoa/Realm/ObjectStore/workflow/test_coverage.sh

@@ -20,15 +20,17 @@ if [ "${TMPDIR}" = "" ]; then
 fi
 
 echo "TMPDIR: ${TMPDIR}"
-ls "${TMPDIR}/realm*" | while read filename; do rm -rf "$filename"; done
+find $TMPDIR -name 'realm*' -exec rm -rf "{}" \+ || true
 rm -rf coverage.build
 mkdir -p coverage.build
 cd coverage.build
 
-cmake_flags=""
+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} -DCMAKE_BUILD_TYPE=Coverage -DDEPENDENCIES_FILE="dependencies${deps_suffix}.list" ..
 make VERBOSE=1 -j${nprocs} generate-coverage-cobertura
+
+find $TMPDIR -name 'realm*' -exec rm -rf "{}" \+ || true

+ 4 - 4
Carthage/Checkouts/realm-cocoa/Realm/RLMAccessor.hpp

@@ -44,7 +44,7 @@ public:
     id box(realm::List&&);
     id box(realm::Results&&);
     id box(realm::Object&&);
-    id box(realm::RowExpr);
+    id box(realm::Obj&&);
 
     id box(bool v) { return @(v); }
     id box(double v) { return @(v); }
@@ -60,8 +60,8 @@ public:
     id box(realm::util::Optional<float> v) { return v ? @(*v) : NSNull.null; }
     id box(realm::util::Optional<int64_t> v) { return v ? @(*v) : NSNull.null; }
 
-    void will_change(realm::Row const&, realm::Property const&);
-    void will_change(realm::Object& obj, realm::Property const& prop) { will_change(obj.row(), prop); }
+    void will_change(realm::Obj const&, realm::Property const&);
+    void will_change(realm::Object& obj, realm::Property const& prop) { will_change(obj.obj(), prop); }
     void did_change();
 
     RLMOptionalId value_for_property(id dict, realm::Property const&, size_t prop_index);
@@ -79,7 +79,7 @@ public:
     }
 
     template<typename T>
-    T unbox(id v, realm::CreatePolicy = realm::CreatePolicy::Skip, size_t = 0);
+    T unbox(id v, realm::CreatePolicy = realm::CreatePolicy::Skip, realm::ObjKey = {});
 
     bool is_null(id v) { return v == NSNull.null; }
     id null_value() { return NSNull.null; }

+ 83 - 68
Carthage/Checkouts/realm-cocoa/Realm/RLMAccessor.mm

@@ -34,34 +34,48 @@
 
 #import <objc/runtime.h>
 #import <objc/message.h>
-#import <realm/descriptor.hpp>
 
 #pragma mark - Helper functions
 
+namespace realm {
+template<>
+Obj ConstObj::get<Obj>(ColKey col) const {
+    ObjKey key = get<ObjKey>(col);
+    return key ? get_target_table(col)->get_object(key) : Obj();
+}
+}
+
 namespace {
+using realm::ColKey;
+
+template<typename T>
+bool is_null(T const& v) {
+    return !v;
+}
+template<>
+bool is_null(realm::Timestamp const& v) {
+    return v.is_null();
+}
+
 template<typename T>
 T get(__unsafe_unretained RLMObjectBase *const obj, NSUInteger index) {
     RLMVerifyAttached(obj);
-    return obj->_row.get<T>(obj->_info->objectSchema->persisted_properties[index].table_column);
+    return obj->_row.get<T>(obj->_info->objectSchema->persisted_properties[index].column_key);
 }
 
 template<typename T>
 id getBoxed(__unsafe_unretained RLMObjectBase *const obj, NSUInteger index) {
     RLMVerifyAttached(obj);
     auto& prop = obj->_info->objectSchema->persisted_properties[index];
-    auto col = prop.table_column;
-    if (obj->_row.is_null(col)) {
-        return nil;
-    }
-
     RLMAccessorContext ctx(obj, &prop);
-    return ctx.box(obj->_row.get<T>(col));
+    auto value = obj->_row.get<T>(prop.column_key);
+    return is_null(value) ? nil : ctx.box(std::move(value));
 }
 
 template<typename T>
-void setValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex, T val) {
+void setValue(__unsafe_unretained RLMObjectBase *const obj, ColKey key, T val) {
     RLMVerifyInWriteTransaction(obj);
-    obj->_row.set(colIndex, val);
+    obj->_row.set(key, val);
 }
 
 template<typename Fn>
@@ -74,55 +88,55 @@ auto translateError(Fn&& fn) {
     }
 }
 
-void setValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex,
+void setValue(__unsafe_unretained RLMObjectBase *const obj, ColKey key,
               __unsafe_unretained NSString *const val) {
     RLMVerifyInWriteTransaction(obj);
     translateError([&] {
-        obj->_row.set(colIndex, RLMStringDataWithNSString(val));
+        obj->_row.set(key, RLMStringDataWithNSString(val));
     });
 }
 
 [[gnu::noinline]]
-void setNull(realm::Row& row, size_t col) {
-    translateError([&] { row.set_null(col); });
+void setNull(realm::Obj& row, ColKey key) {
+    translateError([&] { row.set_null(key); });
 }
 
 void setValue(__unsafe_unretained RLMObjectBase *const obj,
-              NSUInteger colIndex, __unsafe_unretained NSDate *const date) {
+              ColKey key, __unsafe_unretained NSDate *const date) {
     RLMVerifyInWriteTransaction(obj);
     if (date) {
-        obj->_row.set(colIndex, RLMTimestampForNSDate(date));
+        obj->_row.set(key, RLMTimestampForNSDate(date));
     }
     else {
-        setNull(obj->_row, colIndex);
+        setNull(obj->_row, key);
     }
 }
 
-void setValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex,
+void setValue(__unsafe_unretained RLMObjectBase *const obj, ColKey key,
               __unsafe_unretained NSData *const data) {
     RLMVerifyInWriteTransaction(obj);
     translateError([&] {
-        obj->_row.set(colIndex, RLMBinaryDataForNSData(data));
+        obj->_row.set(key, RLMBinaryDataForNSData(data));
     });
 }
 
-void setValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex,
+void setValue(__unsafe_unretained RLMObjectBase *const obj, ColKey key,
               __unsafe_unretained RLMObjectBase *const val) {
     RLMVerifyInWriteTransaction(obj);
     if (!val) {
-        obj->_row.nullify_link(colIndex);
+        obj->_row.set(key, realm::null());
         return;
     }
 
     RLMAddObjectToRealm(val, obj->_realm, RLMUpdatePolicyError);
 
     // make sure it is the correct type
-    if (val->_row.get_table() != obj->_row.get_table()->get_link_target(colIndex)) {
+    if (val->_row.get_table() != obj->_row.get_table()->get_link_target(key)) {
         @throw RLMException(@"Can't set object of type '%@' to property of type '%@'",
                             val->_objectSchema.className,
-                            obj->_info->propertyForTableColumn(colIndex).objectClassName);
+                            obj->_info->propertyForTableColumn(key).objectClassName);
     }
-    obj->_row.set_link(colIndex, val->_row.get_index());
+    obj->_row.set(key, val->_row.get_key());
 }
 
 // array getter/setter
@@ -132,13 +146,13 @@ RLMArray *getArray(__unsafe_unretained RLMObjectBase *const obj, NSUInteger prop
     return [[RLMManagedArray alloc] initWithParent:obj property:prop];
 }
 
-void setValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex,
+void setValue(__unsafe_unretained RLMObjectBase *const obj, ColKey key,
               __unsafe_unretained id<NSFastEnumeration> const value) {
     RLMVerifyInWriteTransaction(obj);
-    auto prop = obj->_info->propertyForTableColumn(colIndex);
+    auto prop = obj->_info->propertyForTableColumn(key);
     RLMValidateValueForProperty(value, obj->_info->rlmObjectSchema, prop, true);
 
-    realm::List list(obj->_realm->_realm, *obj->_row.get_table(), colIndex, obj->_row.get_index());
+    realm::List list(obj->_realm->_realm, obj->_row, key);
     RLMClassInfo *info = obj->_info;
     if (list.get_type() == realm::PropertyType::Object) {
         info = &obj->_info->linkTargetType(prop.index);
@@ -149,51 +163,51 @@ void setValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex,
     });
 }
 
-void setValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex,
+void setValue(__unsafe_unretained RLMObjectBase *const obj, ColKey key,
               __unsafe_unretained NSNumber<RLMInt> *const intObject) {
     RLMVerifyInWriteTransaction(obj);
 
     if (intObject) {
-        obj->_row.set(colIndex, intObject.longLongValue);
+        obj->_row.set(key, intObject.longLongValue);
     }
     else {
-        setNull(obj->_row, colIndex);
+        setNull(obj->_row, key);
     }
 }
 
-void setValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex,
+void setValue(__unsafe_unretained RLMObjectBase *const obj, ColKey key,
               __unsafe_unretained NSNumber<RLMFloat> *const floatObject) {
     RLMVerifyInWriteTransaction(obj);
 
     if (floatObject) {
-        obj->_row.set(colIndex, floatObject.floatValue);
+        obj->_row.set(key, floatObject.floatValue);
     }
     else {
-        setNull(obj->_row, colIndex);
+        setNull(obj->_row, key);
     }
 }
 
-void setValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex,
+void setValue(__unsafe_unretained RLMObjectBase *const obj, ColKey key,
               __unsafe_unretained NSNumber<RLMDouble> *const doubleObject) {
     RLMVerifyInWriteTransaction(obj);
 
     if (doubleObject) {
-        obj->_row.set(colIndex, doubleObject.doubleValue);
+        obj->_row.set(key, doubleObject.doubleValue);
     }
     else {
-        setNull(obj->_row, colIndex);
+        setNull(obj->_row, key);
     }
 }
 
-void setValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex,
+void setValue(__unsafe_unretained RLMObjectBase *const obj, ColKey key,
               __unsafe_unretained NSNumber<RLMBool> *const boolObject) {
     RLMVerifyInWriteTransaction(obj);
 
     if (boolObject) {
-        obj->_row.set(colIndex, (bool)boolObject.boolValue);
+        obj->_row.set(key, (bool)boolObject.boolValue);
     }
     else {
-        setNull(obj->_row, colIndex);
+        setNull(obj->_row, key);
     }
 }
 
@@ -203,9 +217,7 @@ RLMLinkingObjects *getLinkingObjects(__unsafe_unretained RLMObjectBase *const ob
     auto& objectInfo = obj->_realm->_info[property.objectClassName];
     auto& linkOrigin = obj->_info->objectSchema->computed_properties[property.index].link_origin_property_name;
     auto linkingProperty = objectInfo.objectSchema->property_for_name(linkOrigin);
-    auto backlinkView = obj->_row.get_table()->get_backlink_view(obj->_row.get_index(),
-                                                                 objectInfo.table(),
-                                                                 linkingProperty->table_column);
+    auto backlinkView = obj->_row.get_backlink_view(objectInfo.table(), linkingProperty->column_key);
     realm::Results results(obj->_realm->_realm, std::move(backlinkView));
     return [RLMLinkingObjects resultsWithObjectInfo:objectInfo results:std::move(results)];
 }
@@ -278,7 +290,7 @@ id managedGetter(RLMProperty *prop, const char *type) {
         case RLMPropertyTypeData:
             return makeBoxedGetter<realm::BinaryData>(index);
         case RLMPropertyTypeObject:
-            return makeBoxedGetter<realm::RowExpr>(index);
+            return makeBoxedGetter<realm::Obj>(index);
         case RLMPropertyTypeAny:
             @throw RLMException(@"Cannot create accessor class for schema with Mixed properties");
         case RLMPropertyTypeLinkingObjects:
@@ -300,11 +312,11 @@ id makeSetter(__unsafe_unretained RLMProperty *const prop) {
 
     return ^(__unsafe_unretained RLMObjectBase *const obj, ArgType val) {
         auto set = [&] {
-            setValue(obj, obj->_info->objectSchema->persisted_properties[index].table_column,
+            setValue(obj, obj->_info->objectSchema->persisted_properties[index].column_key,
                      static_cast<StorageType>(val));
         };
         if (RLMObservationInfo *info = RLMGetObservationInfo(obj->_observationInfo,
-                                                             obj->_row.get_index(), *obj->_info)) {
+                                                             obj->_row.get_key(), *obj->_info)) {
             info->willChange(name);
             set();
             info->didChange(name);
@@ -351,7 +363,7 @@ id managedSetter(RLMProperty *prop, const char *type) {
     }
 }
 
-// call getter for superclass for property at colIndex
+// call getter for superclass for property at key
 id superGet(RLMObjectBase *obj, NSString *propName) {
     typedef id (*getter_type)(RLMObjectBase *, SEL);
     RLMProperty *prop = obj->_objectSchema[propName];
@@ -360,7 +372,7 @@ id superGet(RLMObjectBase *obj, NSString *propName) {
     return superGetter(obj, prop.getterSel);
 }
 
-// call setter for superclass for property at colIndex
+// call setter for superclass for property at key
 void superSet(RLMObjectBase *obj, NSString *propName, id val) {
     typedef void (*setter_type)(RLMObjectBase *, SEL, RLMArray *ar);
     RLMProperty *prop = obj->_objectSchema[propName];
@@ -643,11 +655,11 @@ id RLMAccessorContext::box(realm::List&& l) {
 
 id RLMAccessorContext::box(realm::Object&& o) {
     REALM_ASSERT(currentProperty);
-    return RLMCreateObjectAccessor(_info.linkTargetType(currentProperty.index), o.row());
+    return RLMCreateObjectAccessor(_info.linkTargetType(currentProperty.index), o.obj());
 }
 
-id RLMAccessorContext::box(realm::RowExpr r) {
-    return RLMCreateObjectAccessor(_info, r);
+id RLMAccessorContext::box(realm::Obj&& r) {
+    return RLMCreateObjectAccessor(_info, std::move(r));
 }
 
 id RLMAccessorContext::box(realm::Results&& r) {
@@ -656,35 +668,38 @@ id RLMAccessorContext::box(realm::Results&& r) {
                                      results:std::move(r)];
 }
 
+using realm::ObjKey;
+using realm::CreatePolicy;
+
 template<>
-realm::Timestamp RLMAccessorContext::unbox(__unsafe_unretained id const value, realm::CreatePolicy, size_t) {
+realm::Timestamp RLMAccessorContext::unbox(__unsafe_unretained id const value, CreatePolicy, ObjKey) {
     id v = RLMCoerceToNil(value);
     return RLMTimestampForNSDate(v);
 }
 
 template<>
-bool RLMAccessorContext::unbox(__unsafe_unretained id const v, realm::CreatePolicy, size_t) {
+bool RLMAccessorContext::unbox(__unsafe_unretained id const v, CreatePolicy, ObjKey) {
     return [v boolValue];
 }
 template<>
-double RLMAccessorContext::unbox(__unsafe_unretained id const v, realm::CreatePolicy, size_t) {
+double RLMAccessorContext::unbox(__unsafe_unretained id const v, CreatePolicy, ObjKey) {
     return [v doubleValue];
 }
 template<>
-float RLMAccessorContext::unbox(__unsafe_unretained id const v, realm::CreatePolicy, size_t) {
+float RLMAccessorContext::unbox(__unsafe_unretained id const v, CreatePolicy, ObjKey) {
     return [v floatValue];
 }
 template<>
-long long RLMAccessorContext::unbox(__unsafe_unretained id const v, realm::CreatePolicy, size_t) {
+long long RLMAccessorContext::unbox(__unsafe_unretained id const v, CreatePolicy, ObjKey) {
     return [v longLongValue];
 }
 template<>
-realm::BinaryData RLMAccessorContext::unbox(id v, realm::CreatePolicy, size_t) {
+realm::BinaryData RLMAccessorContext::unbox(id v, CreatePolicy, ObjKey) {
     v = RLMCoerceToNil(v);
     return RLMBinaryDataForNSData(v);
 }
 template<>
-realm::StringData RLMAccessorContext::unbox(id v, realm::CreatePolicy, size_t) {
+realm::StringData RLMAccessorContext::unbox(id v, CreatePolicy, ObjKey) {
     v = RLMCoerceToNil(v);
     return RLMStringDataWithNSString(v);
 }
@@ -696,30 +711,30 @@ static auto to_optional(__unsafe_unretained id const value, Fn&& fn) {
 }
 
 template<>
-realm::util::Optional<bool> RLMAccessorContext::unbox(__unsafe_unretained id const v, realm::CreatePolicy, size_t) {
+realm::util::Optional<bool> RLMAccessorContext::unbox(__unsafe_unretained id const v, CreatePolicy, ObjKey) {
     return to_optional(v, [&](__unsafe_unretained id v) { return (bool)[v boolValue]; });
 }
 template<>
-realm::util::Optional<double> RLMAccessorContext::unbox(__unsafe_unretained id const v, realm::CreatePolicy, size_t) {
+realm::util::Optional<double> RLMAccessorContext::unbox(__unsafe_unretained id const v, CreatePolicy, ObjKey) {
     return to_optional(v, [&](__unsafe_unretained id v) { return [v doubleValue]; });
 }
 template<>
-realm::util::Optional<float> RLMAccessorContext::unbox(__unsafe_unretained id const v, realm::CreatePolicy, size_t) {
+realm::util::Optional<float> RLMAccessorContext::unbox(__unsafe_unretained id const v, CreatePolicy, ObjKey) {
     return to_optional(v, [&](__unsafe_unretained id v) { return [v floatValue]; });
 }
 template<>
-realm::util::Optional<int64_t> RLMAccessorContext::unbox(__unsafe_unretained id const v, realm::CreatePolicy, size_t) {
+realm::util::Optional<int64_t> RLMAccessorContext::unbox(__unsafe_unretained id const v, CreatePolicy, ObjKey) {
     return to_optional(v, [&](__unsafe_unretained id v) { return [v longLongValue]; });
 }
 
 template<>
-realm::RowExpr RLMAccessorContext::unbox(__unsafe_unretained id const v, realm::CreatePolicy createPolicy, size_t) {
-    bool create = createPolicy != realm::CreatePolicy::Skip;
+realm::Obj RLMAccessorContext::unbox(__unsafe_unretained id const v, CreatePolicy createPolicy, ObjKey) {
+    bool create = createPolicy != CreatePolicy::Skip;
     auto policy = static_cast<RLMUpdatePolicy>(createPolicy);
     RLMObjectBase *link = RLMDynamicCast<RLMObjectBase>(v);
     if (!link) {
         if (!create)
-            return realm::RowExpr();
+            return realm::Obj();
         return RLMCreateObjectInRealmWithValue(_realm, _info.rlmObjectSchema.className, v, policy)->_row;
     }
 
@@ -740,7 +755,7 @@ realm::RowExpr RLMAccessorContext::unbox(__unsafe_unretained id const v, realm::
 
     if (!link->_realm) {
         if (!create)
-            return realm::RowExpr();
+            return realm::Obj();
         if (!_promote_existing)
             return RLMCreateObjectInRealmWithValue(_realm, _info.rlmObjectSchema.className, link, policy)->_row;
         RLMAddObjectToRealm(link, _realm, policy);
@@ -753,10 +768,10 @@ realm::RowExpr RLMAccessorContext::unbox(__unsafe_unretained id const v, realm::
     return link->_row;
 }
 
-void RLMAccessorContext::will_change(realm::Row const& row, realm::Property const& prop) {
-    _observationInfo = RLMGetObservationInfo(nullptr, row.get_index(), _info);
+void RLMAccessorContext::will_change(realm::Obj const& row, realm::Property const& prop) {
+    _observationInfo = RLMGetObservationInfo(nullptr, row.get_key(), _info);
     if (_observationInfo) {
-        _kvoPropertyName = _info.propertyForTableColumn(prop.table_column).name;
+        _kvoPropertyName = _info.propertyForTableColumn(prop.column_key).name;
         _observationInfo->willChange(_kvoPropertyName);
     }
 }

+ 81 - 4
Carthage/Checkouts/realm-cocoa/Realm/RLMArray.h

@@ -91,6 +91,15 @@ NS_ASSUME_NONNULL_BEGIN
  */
 @property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated;
 
+/**
+ Indicates if the array is frozen.
+
+ Frozen arrays are immutable and can be accessed from any thread. Frozen arrays
+ are created by calling `-freeze` on a managed live array. Unmanaged arrays are
+ never frozen.
+ */
+@property (nonatomic, readonly, getter = isFrozen) BOOL frozen;
+
 #pragma mark - Accessing Objects from an Array
 
 /**
@@ -290,6 +299,15 @@ NS_ASSUME_NONNULL_BEGIN
  */
 - (RLMResults<RLMObjectType> *)sortedResultsUsingDescriptors:(NSArray<RLMSortDescriptor *> *)properties;
 
+/**
+ Returns a distinct `RLMResults` from the array.
+
+ @param keyPaths     The key paths to distinct on.
+
+ @return    An `RLMResults` with the distinct values of the keypath(s).
+ */
+- (RLMResults<RLMObjectType> *)distinctResultsUsingKeyPaths:(NSArray<NSString *> *)keyPaths;
+
 /// :nodoc:
 - (RLMObjectType)objectAtIndexedSubscript:(NSUInteger)index;
 
@@ -347,14 +365,54 @@ NS_ASSUME_NONNULL_BEGIN
 
  @warning This method cannot be called during a write transaction, or when the
           containing Realm is read-only.
- @warning This method may only be called on a managed array.
+ @warning This method may only be called on a non-frozen managed array.
 
  @param block The block to be called each time the array changes.
  @return A token which must be held for as long as you want updates to be delivered.
  */
-- (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray<RLMObjectType> *__nullable array,
-                                                         RLMCollectionChange *__nullable changes,
-                                                         NSError *__nullable error))block __attribute__((warn_unused_result));
+- (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray<RLMObjectType> *_Nullable array,
+                                                         RLMCollectionChange *_Nullable changes,
+                                                         NSError *_Nullable error))block
+__attribute__((warn_unused_result));
+
+/**
+ Registers a block to be called each time the array changes.
+
+ The block will be asynchronously called with the initial array, and then
+ called again after each write transaction which changes any of the objects in
+ the array, which objects are in the results, or the order of the objects in the
+ array.
+
+ The `changes` parameter will be `nil` the first time the block is called.
+ For each call after that, it will contain information about
+ which rows in the array were added, removed or modified. If a write transaction
+ did not modify any objects in the array, the block is not called at all.
+ See the `RLMCollectionChange` documentation for information on how the changes
+ are reported and an example of updating a `UITableView`.
+
+ If an error occurs the block will be called with `nil` for the results
+ parameter and a non-`nil` error. Currently the only errors that can occur are
+ when opening the Realm on the background worker thread.
+
+ Notifications are delivered on the given queue. If the queue is blocked and
+ notifications can't be delivered instantly, multiple notifications may be
+ coalesced into a single notification.
+
+ You must retain the returned token for as long as you want updates to continue
+ to be sent to the block. To stop receiving updates, call `-invalidate` on the token.
+
+ @warning This method cannot be called when the containing Realm is read-only or frozen.
+ @warning The queue must be a serial queue.
+
+ @param block The block to be called whenever a change occurs.
+ @param queue The serial queue to deliver notifications to.
+ @return A token which must be held for as long as you want updates to be delivered.
+ */
+- (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray<RLMObjectType> *_Nullable array,
+                                                         RLMCollectionChange *_Nullable changes,
+                                                         NSError *_Nullable error))block
+                                         queue:(nullable dispatch_queue_t)queue
+__attribute__((warn_unused_result));
 
 #pragma mark - Aggregating Property Values
 
@@ -414,6 +472,25 @@ NS_ASSUME_NONNULL_BEGIN
  */
 - (nullable NSNumber *)averageOfProperty:(NSString *)property;
 
+#pragma mark - Freeze
+
+/**
+ Returns a frozen (immutable) snapshot of this array.
+
+ The frozen copy is an immutable array which contains the same data as this
+ array currently contains, but will not update when writes are made to the
+ containing Realm. Unlike live arrays, frozen arrays can be accessed from any
+ thread.
+
+ @warning This method cannot be called during a write transaction, or when the
+          containing Realm is read-only.
+ @warning This method may only be called on a managed array.
+ @warning Holding onto a frozen array for an extended period while performing
+          write transaction on the Realm may result in the Realm file growing
+          to large sizes. See `RLMRealmConfiguration.maximumNumberOfActiveVersions`
+          for more information.
+ */
+- (instancetype)freeze;
 
 #pragma mark - Unavailable Methods
 

+ 14 - 2
Carthage/Checkouts/realm-cocoa/Realm/RLMArray.mm

@@ -533,18 +533,30 @@ static bool canAggregate(RLMPropertyType type, bool allowDate) {
     @throw RLMException(@"This method may only be called on RLMArray instances retrieved from an RLMRealm");
 }
 
+- (RLMResults *)distinctResultsUsingKeyPaths:(NSArray<NSString *> *)keyPaths {
+    @throw RLMException(@"This method may only be called on RLMArray instances retrieved from an RLMRealm");
+}
+
 // The compiler complains about the method's argument type not matching due to
 // it not having the generic type attached, but it doesn't seem to be possible
 // to actually include the generic type
 // http://www.openradar.me/radar?id=6135653276319744
 #pragma clang diagnostic ignored "-Wmismatched-parameter-types"
 - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *, RLMCollectionChange *, NSError *))block {
+    return [self addNotificationBlock:block queue:nil];
+}
+- (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *, RLMCollectionChange *, NSError *))block
+                                         queue:(nullable dispatch_queue_t)queue {
+    @throw RLMException(@"This method may only be called on RLMArray instances retrieved from an RLMRealm");
+}
+
+- (instancetype)freeze {
     @throw RLMException(@"This method may only be called on RLMArray instances retrieved from an RLMRealm");
 }
 
 #pragma mark - Thread Confined Protocol Conformance
 
-- (std::unique_ptr<realm::ThreadSafeReferenceBase>)makeThreadSafeReference {
+- (realm::ThreadSafeReference)makeThreadSafeReference {
     REALM_TERMINATE("Unexpected handover of unmanaged `RLMArray`");
 }
 
@@ -552,7 +564,7 @@ static bool canAggregate(RLMPropertyType type, bool allowDate) {
     REALM_TERMINATE("Unexpected handover of unmanaged `RLMArray`");
 }
 
-+ (instancetype)objectWithThreadSafeReference:(std::unique_ptr<realm::ThreadSafeReferenceBase>)reference
++ (instancetype)objectWithThreadSafeReference:(realm::ThreadSafeReference)reference
                                      metadata:(id)metadata
                                         realm:(RLMRealm *)realm {
     REALM_TERMINATE("Unexpected handover of unmanaged `RLMArray`");

+ 0 - 1
Carthage/Checkouts/realm-cocoa/Realm/RLMArray_Private.hpp

@@ -22,7 +22,6 @@
 
 #import "RLMResults_Private.hpp"
 
-#import <realm/link_view_fwd.hpp>
 #import <realm/table_ref.hpp>
 
 namespace realm {

+ 10 - 10
Carthage/Checkouts/realm-cocoa/Realm/RLMClassInfo.hpp

@@ -17,14 +17,17 @@
 ////////////////////////////////////////////////////////////////////////////
 
 #import <Foundation/Foundation.h>
+
+#import <realm/table_ref.hpp>
+
 #import <unordered_map>
 #import <vector>
 
 namespace realm {
     class ObjectSchema;
     class Schema;
-    class Table;
     struct Property;
+    struct ColKey;
 }
 
 class RLMObservationInfo;
@@ -62,11 +65,11 @@ public:
 
     // Get the table for this object type. Will return nullptr only if it's a
     // read-only Realm that is missing the table entirely.
-    realm::Table *_Nullable table() const;
+    realm::TableRef table() const;
 
     // Get the RLMProperty for a given table column, or `nil` if it is a column
     // not used by the current schema
-    RLMProperty *_Nullable propertyForTableColumn(NSUInteger) const noexcept;
+    RLMProperty *_Nullable propertyForTableColumn(realm::ColKey) const noexcept;
 
     // Get the RLMProperty that's used as the primary key, or `nil` if there is
     // no primary key for the current schema
@@ -74,8 +77,8 @@ public:
 
     // Get the table column for the given property. The property must be a valid
     // persisted property.
-    NSUInteger tableColumn(NSString *propertyName) const;
-    NSUInteger tableColumn(RLMProperty *property) const;
+    realm::ColKey tableColumn(NSString *propertyName) const;
+    realm::ColKey tableColumn(RLMProperty *property) const;
 
     // Get the info for the target of the link at the given property index.
     RLMClassInfo &linkTargetType(size_t propertyIndex);
@@ -83,11 +86,8 @@ public:
     // Get the info for the target of the given property
     RLMClassInfo &linkTargetType(realm::Property const& property);
 
-    void releaseTable() { m_table = nullptr; }
-
-private:
-    mutable realm::Table *_Nullable m_table = nullptr;
-    std::vector<RLMClassInfo *> m_linkTargets;
+    // Get the corresponding ClassInfo for the given Realm
+    RLMClassInfo &freeze(RLMRealm *);
 };
 
 // A per-RLMRealm object schema map which stores RLMClassInfo keyed on the name

+ 17 - 18
Carthage/Checkouts/realm-cocoa/Realm/RLMClassInfo.mm

@@ -35,20 +35,20 @@
 using namespace realm;
 
 RLMClassInfo::RLMClassInfo(RLMRealm *realm, RLMObjectSchema *rlmObjectSchema,
-                             const realm::ObjectSchema *objectSchema)
+                           const realm::ObjectSchema *objectSchema)
 : realm(realm), rlmObjectSchema(rlmObjectSchema), objectSchema(objectSchema) { }
 
-realm::Table *RLMClassInfo::table() const {
-    if (!m_table) {
-        m_table = ObjectStore::table_for_object_type(realm.group, objectSchema->name).get();
+realm::TableRef RLMClassInfo::table() const {
+    if (auto key = objectSchema->table_key) {
+        return realm.group.get_table(objectSchema->table_key);
     }
-    return m_table;
+    return nullptr;
 }
 
-RLMProperty *RLMClassInfo::propertyForTableColumn(NSUInteger col) const noexcept {
+RLMProperty *RLMClassInfo::propertyForTableColumn(ColKey col) const noexcept {
     auto const& props = objectSchema->persisted_properties;
     for (size_t i = 0; i < props.size(); ++i) {
-        if (props[i].table_column == col) {
+        if (props[i].column_key == col) {
             return rlmObjectSchema.properties[i];
         }
     }
@@ -59,23 +59,16 @@ RLMProperty *RLMClassInfo::propertyForPrimaryKey() const noexcept {
     return rlmObjectSchema.primaryKeyProperty;
 }
 
-NSUInteger RLMClassInfo::tableColumn(NSString *propertyName) const {
+realm::ColKey RLMClassInfo::tableColumn(NSString *propertyName) const {
     return tableColumn(RLMValidatedProperty(rlmObjectSchema, propertyName));
 }
 
-NSUInteger RLMClassInfo::tableColumn(RLMProperty *property) const {
-    return objectSchema->persisted_properties[property.index].table_column;
+realm::ColKey RLMClassInfo::tableColumn(RLMProperty *property) const {
+    return objectSchema->persisted_properties[property.index].column_key;
 }
 
 RLMClassInfo &RLMClassInfo::linkTargetType(size_t propertyIndex) {
-    if (propertyIndex < m_linkTargets.size() && m_linkTargets[propertyIndex]) {
-        return *m_linkTargets[propertyIndex];
-    }
-    if (m_linkTargets.size() <= propertyIndex) {
-        m_linkTargets.resize(propertyIndex + 1);
-    }
-    m_linkTargets[propertyIndex] = &realm->_info[rlmObjectSchema.properties[propertyIndex].objectClassName];
-    return *m_linkTargets[propertyIndex];
+    return realm->_info[rlmObjectSchema.properties[propertyIndex].objectClassName];
 }
 
 RLMClassInfo &RLMClassInfo::linkTargetType(realm::Property const& property) {
@@ -83,6 +76,12 @@ RLMClassInfo &RLMClassInfo::linkTargetType(realm::Property const& property) {
     return linkTargetType(&property - &objectSchema->persisted_properties[0]);
 }
 
+RLMClassInfo &RLMClassInfo::freeze(__unsafe_unretained RLMRealm *const frozenRealm) {
+    REALM_ASSERT(frozenRealm.frozen);
+    // FIXME
+    return frozenRealm->_info[rlmObjectSchema.className];
+}
+
 RLMSchemaInfo::impl::iterator RLMSchemaInfo::begin() noexcept { return m_objects.begin(); }
 RLMSchemaInfo::impl::iterator RLMSchemaInfo::end() noexcept { return m_objects.end(); }
 RLMSchemaInfo::impl::const_iterator RLMSchemaInfo::begin() const noexcept { return m_objects.begin(); }

+ 27 - 0
Carthage/Checkouts/realm-cocoa/Realm/RLMCollection.h

@@ -309,6 +309,33 @@ typedef RLM_CLOSED_ENUM(int32_t, RLMPropertyType);
  */
 - (nullable NSNumber *)averageOfProperty:(NSString *)property;
 
+#pragma mark - Freeze
+
+/**
+ Indicates if the collection is frozen.
+
+ Frozen collections are immutable and can be accessed from any thread. The
+ objects read from a frozen collection will also be frozen.
+ */
+@property (nonatomic, readonly, getter=isFrozen) BOOL frozen;
+
+/**
+ Returns a frozen (immutable) snapshot of this collection.
+
+ The frozen copy is an immutable collection which contains the same data as
+ this collection currently contains, but will not update when writes are made
+ to the containing Realm. Unlike live collections, frozen collections can be
+ accessed from any thread.
+
+ @warning This method cannot be called during a write transaction, or when the containing Realm is read-only.
+ @warning Holding onto a frozen collection for an extended period while
+          performing write transaction on the Realm may result in the Realm
+          file growing to large sizes. See
+          `RLMRealmConfiguration.maximumNumberOfActiveVersions`
+          for more information.
+ */
+- (instancetype)freeze;
+
 @end
 
 /**

+ 90 - 49
Carthage/Checkouts/realm-cocoa/Realm/RLMCollection.mm

@@ -203,9 +203,9 @@ NSArray *RLMCollectionValueForKey(Collection& collection, NSString *key, RLMClas
             RLMAccessorContext context(info);
             for (size_t i = 0; i < count; ++i) {
                 RLMListBase *list = [[cls alloc] init];
-                list._rlmArray = [[RLMManagedArray alloc] initWithList:realm::List(info.realm->_realm, *info.table(),
-                                                                                   info.tableColumn(prop),
-                                                                                   collection.get(i).get_index())
+                list._rlmArray = [[RLMManagedArray alloc] initWithList:realm::List(info.realm->_realm,
+                                                                                   collection.get(i),
+                                                                                   info.tableColumn(prop))
                                                             parentInfo:&info
                                                               property:prop];
                 [array addObject:list];
@@ -294,33 +294,6 @@ std::vector<std::pair<std::string, bool>> RLMSortDescriptorsToKeypathArray(NSArr
     return keypaths;
 }
 
-@implementation RLMCancellationToken {
-    realm::NotificationToken _token;
-    __unsafe_unretained RLMRealm *_realm;
-}
-- (instancetype)initWithToken:(realm::NotificationToken)token realm:(RLMRealm *)realm {
-    self = [super init];
-    if (self) {
-        _token = std::move(token);
-        _realm = realm;
-    }
-    return self;
-}
-
-- (RLMRealm *)realm {
-    return _realm;
-}
-
-- (void)suppressNextNotification {
-    _token.suppress_next();
-}
-
-- (void)invalidate {
-    _token = {};
-}
-
-@end
-
 @implementation RLMCollectionChange {
     realm::CollectionChangeSet _indices;
 }
@@ -369,23 +342,26 @@ static NSArray *toIndexPathArray(realm::IndexSet const& set, NSUInteger section)
 
 - (NSArray<NSIndexPath *> *)insertionsInSection:(NSUInteger)section {
     return toIndexPathArray(_indices.insertions, section);
-
 }
 
 - (NSArray<NSIndexPath *> *)modificationsInSection:(NSUInteger)section {
     return toIndexPathArray(_indices.modifications, section);
+}
 
+- (NSString *)description {
+    return [NSString stringWithFormat:@"<RLMCollectionChange: %p> insertions: %@, deletions: %@, modifications: %@",
+            (__bridge void *)self, self.insertions, self.deletions, self.modifications];
 }
+
 @end
 
-template<typename Collection>
-RLMNotificationToken *RLMAddNotificationBlock(id objcCollection,
-                                              Collection& collection,
-                                              void (^block)(id, RLMCollectionChange *, NSError *),
-                                              bool suppressInitialChange) {
-    auto skip = suppressInitialChange ? std::make_shared<bool>(true) : nullptr;
-    auto cb = [=, &collection](realm::CollectionChangeSet const& changes,
-                               std::exception_ptr err) {
+namespace {
+struct CollectionCallbackWrapper {
+    void (^block)(id, RLMCollectionChange *, NSError *);
+    id collection;
+    bool ignoreChangesInInitialNotification;
+
+    void operator()(realm::CollectionChangeSet const& changes, std::exception_ptr err) {
         if (err) {
             try {
                 rethrow_exception(err);
@@ -398,22 +374,87 @@ RLMNotificationToken *RLMAddNotificationBlock(id objcCollection,
             }
         }
 
-        if (skip && *skip) {
-            *skip = false;
-            block(objcCollection, nil, nil);
+        if (ignoreChangesInInitialNotification) {
+            ignoreChangesInInitialNotification = false;
+            block(collection, nil, nil);
         }
         else if (changes.empty()) {
-            block(objcCollection, nil, nil);
+            block(collection, nil, nil);
         }
         else {
-            block(objcCollection, [[RLMCollectionChange alloc] initWithChanges:changes], nil);
+            block(collection, [[RLMCollectionChange alloc] initWithChanges:changes], nil);
         }
-    };
+    }
+};
+} // anonymous namespace
+
+@interface RLMCancellationToken : RLMNotificationToken
+@end
+
+@implementation RLMCancellationToken {
+@public
+    __unsafe_unretained RLMRealm *_realm;
+    realm::NotificationToken _token;
+    std::mutex _mutex;
+}
+
+- (RLMRealm *)realm {
+    std::lock_guard<std::mutex> lock(_mutex);
+    return _realm;
+}
+
+- (void)suppressNextNotification {
+    std::lock_guard<std::mutex> lock(_mutex);
+    if (_realm) {
+        _token.suppress_next();
+    }
+}
+
+- (void)invalidate {
+    std::lock_guard<std::mutex> lock(_mutex);
+    _token = {};
+    _realm = nil;
+}
 
-    return [[RLMCancellationToken alloc] initWithToken:collection.add_notification_callback(cb)
-                                                 realm:(RLMRealm *)[objcCollection realm]];
+template<typename RLMCollection>
+RLMNotificationToken *RLMAddNotificationBlock(RLMCollection *collection,
+                                              void (^block)(id, RLMCollectionChange *, NSError *),
+                                              dispatch_queue_t queue) {
+    RLMRealm *realm = collection.realm;
+    if (!realm) {
+        @throw RLMException(@"Linking objects notifications are only supported on managed objects.");
+    }
+    bool skipFirst = std::is_same_v<RLMCollection, RLMResults>;
+    auto token = [[RLMCancellationToken alloc] init];
+
+    if (!queue) {
+        [realm verifyNotificationsAreSupported:true];
+        token->_realm = realm;
+        token->_token = RLMGetBackingCollection(collection).add_notification_callback(CollectionCallbackWrapper{block, collection, skipFirst});
+        return token;
+    }
+
+    RLMThreadSafeReference *tsr = [RLMThreadSafeReference referenceWithThreadConfined:collection];
+    token->_realm = realm;
+    RLMRealmConfiguration *config = realm.configuration;
+    dispatch_async(queue, ^{
+        std::lock_guard<std::mutex> lock(token->_mutex);
+        if (!token->_realm) {
+            return;
+        }
+        NSError *error;
+        RLMRealm *realm = token->_realm = [RLMRealm realmWithConfiguration:config queue:queue error:&error];
+        if (!realm) {
+            block(nil, nil, error);
+            return;
+        }
+        RLMCollection *collection = [realm resolveThreadSafeReference:tsr];
+        token->_token = RLMGetBackingCollection(collection).add_notification_callback(CollectionCallbackWrapper{block, collection, skipFirst});
+    });
+    return token;
 }
+@end
 
 // Explicitly instantiate the templated function for the two types we'll use it on
-template RLMNotificationToken *RLMAddNotificationBlock<realm::List>(id, realm::List&, void (^)(id, RLMCollectionChange *, NSError *), bool);
-template RLMNotificationToken *RLMAddNotificationBlock<realm::Results>(id, realm::Results&, void (^)(id, RLMCollectionChange *, NSError *), bool);
+template RLMNotificationToken *RLMAddNotificationBlock<>(RLMManagedArray *, void (^)(id, RLMCollectionChange *, NSError *), dispatch_queue_t);
+template RLMNotificationToken *RLMAddNotificationBlock<>(RLMResults *, void (^)(id, RLMCollectionChange *, NSError *), dispatch_queue_t);

+ 7 - 9
Carthage/Checkouts/realm-cocoa/Realm/RLMCollection_Private.hpp

@@ -28,7 +28,7 @@ namespace realm {
     struct NotificationToken;
 }
 class RLMClassInfo;
-@class RLMFastEnumerator;
+@class RLMFastEnumerator, RLMManagedArray;
 
 @protocol RLMFastEnumerable
 @property (nonatomic, readonly) RLMRealm *realm;
@@ -64,19 +64,17 @@ NSUInteger RLMFastEnumerate(NSFastEnumerationState *state, NSUInteger len, id<RL
 - (RLMRealm *)realm;
 @end
 
-@interface RLMCancellationToken : RLMNotificationToken
-- (instancetype)initWithToken:(realm::NotificationToken)token realm:(RLMRealm *)realm;
-@end
-
 @interface RLMCollectionChange ()
 - (instancetype)initWithChanges:(realm::CollectionChangeSet)indices;
 @end
 
-template<typename Collection>
-RLMNotificationToken *RLMAddNotificationBlock(id objcCollection,
-                                              Collection& collection,
+realm::List& RLMGetBackingCollection(RLMManagedArray *);
+realm::Results& RLMGetBackingCollection(RLMResults *);
+
+template<typename RLMCollection>
+RLMNotificationToken *RLMAddNotificationBlock(RLMCollection *collection,
                                               void (^block)(id, RLMCollectionChange *, NSError *),
-                                              bool suppressInitialChange=false);
+                                              dispatch_queue_t queue);
 
 template<typename Collection>
 NSArray *RLMCollectionValueForKey(Collection& collection, NSString *key, RLMClassInfo& info);

+ 2 - 9
Carthage/Checkouts/realm-cocoa/Realm/RLMConstants.h

@@ -152,15 +152,8 @@ typedef RLM_ERROR_ENUM(NSInteger, RLMError, RLMErrorDomain) {
 
     /** Denotes an error that occurs if there is a schema version mismatch, so that a migration is required. */
     RLMErrorSchemaMismatch = 10,
-
-    /** Denotes an error that occurs when attempting to open an incompatible synchronized Realm file.
-
-     This error occurs when the Realm file was created with an older version of Realm and an automatic migration
-     to the current version is not possible. When such an error occurs, the original file is moved to a backup
-     location, and future attempts to open the synchronized Realm will result in a new file being created.
-     If you wish to migrate any data from the backup Realm, you can open it using the provided Realm configuration.
-     */
-    RLMErrorIncompatibleSyncedFile = 11,
+    // Error code 11 is obsolete
+    // RLMErrorIncompatibleSyncedFile = 11,
     /**
      Denotates an error where an operation was requested which cannot be performed on an open file.
      */

+ 4 - 4
Carthage/Checkouts/realm-cocoa/Realm/RLMListBase.h

@@ -18,7 +18,7 @@
 
 #import <Foundation/Foundation.h>
 
-@class RLMArray, RLMObjectBase, RLMResults, RLMProperty;
+@class RLMArray, RLMObjectBase, RLMResults, RLMProperty, RLMLinkingObjects;
 
 NS_ASSUME_NONNULL_BEGIN
 
@@ -33,10 +33,10 @@ NS_ASSUME_NONNULL_BEGIN
 
 @interface RLMLinkingObjectsHandle : NSObject
 - (instancetype)initWithObject:(RLMObjectBase *)object property:(RLMProperty *)property;
+- (instancetype)initWithLinkingObjects:(RLMResults *)linkingObjects;
+- (instancetype)freeze;
 
-@property (nonatomic, readonly) RLMResults *results;
-@property (nonatomic, readonly) RLMObjectBase *parent;
-@property (nonatomic, readonly) RLMProperty *property;
+@property (nonatomic, readonly) RLMLinkingObjects *results;
 @end
 
 NS_ASSUME_NONNULL_END

+ 27 - 15
Carthage/Checkouts/realm-cocoa/Realm/RLMListBase.mm

@@ -85,7 +85,8 @@
 @end
 
 @implementation RLMLinkingObjectsHandle {
-    realm::Row _row;
+    realm::TableKey _tableKey;
+    realm::ObjKey _objKey;
     RLMClassInfo *_info;
     RLMRealm *_realm;
     RLMProperty *_property;
@@ -97,8 +98,9 @@
     if (!(self = [super init])) {
         return nil;
     }
-
-    _row = object->_row;
+    auto& obj = object->_row;
+    _tableKey = obj.get_table()->get_key();
+    _objKey = obj.get_key();
     _info = object->_info;
     _realm = object->_realm;
     _property = prop;
@@ -106,30 +108,40 @@
     return self;
 }
 
+- (instancetype)initWithLinkingObjects:(RLMResults *)linkingObjects {
+    if (!(self = [super init])) {
+        return nil;
+    }
+    _realm = linkingObjects.realm;
+    _results = linkingObjects;
+
+    return self;
+}
+
+- (instancetype)freeze {
+    RLMLinkingObjectsHandle *frozen = [[self.class alloc] init];
+    frozen->_results = [self.results freeze];
+    return frozen;
+}
+
 - (RLMResults *)results {
     if (_results) {
         return _results;
     }
-    if (!_row.is_attached()) {
+    [_realm verifyThread];
+
+    auto table = _realm.group.get_table(_tableKey);
+    if (!table->is_valid(_objKey)) {
         @throw RLMException(@"Object has been deleted or invalidated.");
     }
-    [_realm verifyThread];
 
+    auto obj = _realm.group.get_table(_tableKey)->get_object(_objKey);
     auto& objectInfo = _realm->_info[_property.objectClassName];
     auto& linkOrigin = _info->objectSchema->computed_properties[_property.index].link_origin_property_name;
     auto linkingProperty = objectInfo.objectSchema->property_for_name(linkOrigin);
-    auto backlinkView = _row.get_table()->get_backlink_view(_row.get_index(),
-                                                            objectInfo.table(),
-                                                            linkingProperty->table_column);
-    realm::Results results(_realm->_realm, std::move(backlinkView));
+    realm::Results results(_realm->_realm, obj.get_backlink_view(objectInfo.table(), linkingProperty->column_key));
     _results = [RLMLinkingObjects resultsWithObjectInfo:objectInfo results:std::move(results)];
     _realm = nil;
     return _results;
 }
-
-- (RLMObjectBase *)parent {
-    RLMObjectBase *obj = RLMCreateManagedAccessor(_info->rlmObjectSchema.accessorClass, _info);
-    obj->_row = _row;
-    return obj;
-}
 @end

+ 46 - 20
Carthage/Checkouts/realm-cocoa/Realm/RLMManagedArray.mm

@@ -85,8 +85,7 @@
                            property:(__unsafe_unretained RLMProperty *const)property {
     __unsafe_unretained RLMRealm *const realm = parentObject->_realm;
     auto col = parentObject->_info->tableColumn(property);
-    auto& row = parentObject->_row;
-    return [self initWithList:realm::List(realm->_realm, *row.get_table(), col, row.get_index())
+    return [self initWithList:realm::List(realm->_realm, parentObject->_row, col)
                    parentInfo:parentObject->_info
                      property:property];
 }
@@ -107,7 +106,7 @@ void RLMEnsureArrayObservationInfo(std::unique_ptr<RLMObservationInfo>& info,
     if (!info && array.class == [RLMManagedArray class]) {
         auto lv = static_cast<RLMManagedArray *>(array);
         info = std::make_unique<RLMObservationInfo>(*lv->_ownerInfo,
-                                                    lv->_backingList.get_origin_row_index(),
+                                                    lv->_backingList.get_parent_object_key(),
                                                     observed);
     }
 }
@@ -179,7 +178,7 @@ static void changeArray(__unsafe_unretained RLMManagedArray *const ar,
                         NSKeyValueChange kind, dispatch_block_t f, IndexSetFactory&& is) {
     translateErrors([&] { ar->_backingList.verify_in_transaction(); });
     RLMObservationInfo *info = RLMGetObservationInfo(ar->_observationInfo.get(),
-                                                     ar->_backingList.get_origin_row_index(),
+                                                     ar->_backingList.get_parent_object_key(),
                                                      *ar->_ownerInfo);
     if (info) {
         NSIndexSet *indexes = is();
@@ -401,35 +400,35 @@ static void RLMInsertObject(RLMManagedArray *ar, id object, NSUInteger index) {
     }
 }
 
-- (size_t)columnForProperty:(NSString *)propertyName {
+- (realm::ColKey)columnForProperty:(NSString *)propertyName {
     if (_backingList.get_type() == realm::PropertyType::Object) {
         return _objectInfo->tableColumn(propertyName);
     }
     if (![propertyName isEqualToString:@"self"]) {
         @throw RLMException(@"Arrays of '%@' can only be aggregated on \"self\"", RLMTypeToString(_type));
     }
-    return 0;
+    return {};
 }
 
 - (id)minOfProperty:(NSString *)property {
-    size_t column = [self columnForProperty:property];
+    auto column = [self columnForProperty:property];
     auto value = translateErrors(self, [&] { return _backingList.min(column); }, @"minOfProperty");
     return value ? RLMMixedToObjc(*value) : nil;
 }
 
 - (id)maxOfProperty:(NSString *)property {
-    size_t column = [self columnForProperty:property];
+    auto column = [self columnForProperty:property];
     auto value = translateErrors(self, [&] { return _backingList.max(column); }, @"maxOfProperty");
     return value ? RLMMixedToObjc(*value) : nil;
 }
 
 - (id)sumOfProperty:(NSString *)property {
-    size_t column = [self columnForProperty:property];
+    auto column = [self columnForProperty:property];
     return RLMMixedToObjc(translateErrors(self, [&] { return _backingList.sum(column); }, @"sumOfProperty"));
 }
 
 - (id)averageOfProperty:(NSString *)property {
-    size_t column = [self columnForProperty:property];
+    auto column = [self columnForProperty:property];
     auto value = translateErrors(self, [&] { return _backingList.average(column); }, @"averageOfProperty");
     return value ? @(*value) : nil;
 }
@@ -451,6 +450,13 @@ static void RLMInsertObject(RLMManagedArray *ar, id object, NSUInteger index) {
     });
 }
 
+- (RLMResults *)distinctResultsUsingKeyPaths:(NSArray<NSString *> *)keyPaths {
+    return translateErrors([&] {
+        auto results = [RLMResults resultsWithObjectInfo:*_objectInfo results:_backingList.as_results()];
+        return [results distinctResultsUsingKeyPaths:keyPaths];
+    });
+}
+
 - (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate {
     if (_type != RLMPropertyTypeObject) {
         @throw RLMException(@"Querying is currently only implemented for arrays of Realm Objects");
@@ -497,6 +503,24 @@ static void RLMInsertObject(RLMManagedArray *ar, id object, NSUInteger index) {
     });
 }
 
+- (BOOL)isFrozen {
+    return _realm.isFrozen;
+}
+
+- (instancetype)freeze {
+    if (self.frozen) {
+        return self;
+    }
+
+    RLMRealm *frozenRealm = [_realm freeze];
+    auto& parentInfo = _ownerInfo->freeze(frozenRealm);
+    return translateRLMResultsErrors([&] {
+        return [[self.class alloc] initWithList:_backingList.freeze(frozenRealm->_realm)
+                                     parentInfo:&parentInfo
+                                       property:parentInfo.rlmObjectSchema[_key]];
+    });
+}
+
 // The compiler complains about the method's argument type not matching due to
 // it not having the generic type attached, but it doesn't seem to be possible
 // to actually include the generic type
@@ -504,16 +528,21 @@ static void RLMInsertObject(RLMManagedArray *ar, id object, NSUInteger index) {
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wmismatched-parameter-types"
 - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *, RLMCollectionChange *, NSError *))block {
-    [_realm verifyNotificationsAreSupported:true];
-    return RLMAddNotificationBlock(self, _backingList, block);
+    return RLMAddNotificationBlock(self, block, nil);
+}
+- (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *, RLMCollectionChange *, NSError *))block queue:(dispatch_queue_t)queue {
+    return RLMAddNotificationBlock(self, block, queue);
 }
 #pragma clang diagnostic pop
 
+realm::List& RLMGetBackingCollection(RLMManagedArray *self) {
+    return self->_backingList;
+}
+
 #pragma mark - Thread Confined Protocol Conformance
 
-- (std::unique_ptr<realm::ThreadSafeReferenceBase>)makeThreadSafeReference {
-    auto list_reference = _realm->_realm->obtain_thread_safe_reference(_backingList);
-    return std::make_unique<realm::ThreadSafeReference<realm::List>>(std::move(list_reference));
+- (realm::ThreadSafeReference)makeThreadSafeReference {
+    return _backingList;
 }
 
 - (RLMManagedArrayHandoverMetadata *)objectiveCMetadata {
@@ -523,13 +552,10 @@ static void RLMInsertObject(RLMManagedArray *ar, id object, NSUInteger index) {
     return metadata;
 }
 
-+ (instancetype)objectWithThreadSafeReference:(std::unique_ptr<realm::ThreadSafeReferenceBase>)reference
++ (instancetype)objectWithThreadSafeReference:(realm::ThreadSafeReference)reference
                                      metadata:(RLMManagedArrayHandoverMetadata *)metadata
                                         realm:(RLMRealm *)realm {
-    REALM_ASSERT_DEBUG(dynamic_cast<realm::ThreadSafeReference<realm::List> *>(reference.get()));
-    auto list_reference = static_cast<realm::ThreadSafeReference<realm::List> *>(reference.get());
-
-    auto list = realm->_realm->resolve_thread_safe_reference(std::move(*list_reference));
+    auto list = reference.resolve<realm::List>(realm->_realm);
     if (!list.is_valid()) {
         return nil;
     }

+ 20 - 42
Carthage/Checkouts/realm-cocoa/Realm/RLMMigration.mm

@@ -56,7 +56,6 @@ using namespace realm;
 
 @implementation RLMMigration {
     realm::Schema *_schema;
-    std::unordered_map<NSString *, realm::IndexSet> _deletedObjectIndices;
 }
 
 - (instancetype)initWithRealm:(RLMRealm *)realm oldRealm:(RLMRealm *)oldRealm schema:(realm::Schema &)schema {
@@ -87,26 +86,29 @@ using namespace realm;
     // objects. It's unclear how this could be useful, but changing it would
     // also be a pointless breaking change and it's unlikely to be hurting anyone.
     if (objects && !oldObjects) {
-        for (auto i = objects.count; i > 0; --i) {
+        for (RLMObject *object in objects) {
             @autoreleasepool {
-                block(nil, objects[i - 1]);
+                block(nil, object);
             }
         }
         return;
     }
 
-    auto count = oldObjects.count;
-    if (count == 0) {
+    if (oldObjects.count == 0 || objects.count == 0) {
         return;
     }
-    auto deletedObjects = _deletedObjectIndices.find(className);
-    for (auto i = count; i > 0; --i) {
-        auto index = i - 1;
-        if (deletedObjects != _deletedObjectIndices.end() && deletedObjects->second.contains(index)) {
-            continue;
-        }
+
+    auto& info = _realm->_info[className];
+    for (RLMObject *oldObject in oldObjects) {
         @autoreleasepool {
-            block(oldObjects[index], objects[index]);
+            Obj newObj;
+            try {
+                newObj = info.table()->get_object(oldObject->_row.get_key());
+            }
+            catch (InvalidKey const&) {
+                continue;
+            }
+            block(oldObject, (id)RLMCreateObjectAccessor(info, std::move(newObj)));
         }
     }
 }
@@ -124,8 +126,6 @@ using namespace realm;
 
         block(self, _oldRealm->_realm->schema_version());
 
-        [self deleteObjectsMarkedForDeletion];
-
         _oldRealm = nil;
         _realm = nil;
     }
@@ -140,31 +140,7 @@ using namespace realm;
 }
 
 - (void)deleteObject:(RLMObject *)object {
-    _deletedObjectIndices[object.objectSchema.className].add(object->_row.get_index());
-}
-
-- (void)deleteObjectsMarkedForDeletion {
-    for (auto& objectType : _deletedObjectIndices) {
-        TableRef table = ObjectStore::table_for_object_type(_realm.group, objectType.first.UTF8String);
-        if (!table) {
-            continue;
-        }
-
-        auto& indices = objectType.second;
-        // Just clear the table if we're removing all of the rows
-        if (table->size() == indices.count()) {
-            table->clear();
-        }
-        // Otherwise delete in reverse order to avoid invaliding any of the
-        // not-yet-deleted indices
-        else {
-            for (auto it = std::make_reverse_iterator(indices.end()), end = std::make_reverse_iterator(indices.begin()); it != end; ++it) {
-                for (size_t i = it->second; i > it->first; --i) {
-                    table->move_last_over(i - 1);
-                }
-            }
-        }
-    }
+    [_realm deleteObject:object];
 }
 
 - (BOOL)deleteDataForClassName:(NSString *)name {
@@ -176,9 +152,11 @@ using namespace realm;
     if (!table) {
         return false;
     }
-    _deletedObjectIndices[name].set(table->size());
-    if (![_realm.schema schemaForClassName:name]) {
-        realm::ObjectStore::delete_data_for_object(_realm.group, name.UTF8String);
+    if ([_realm.schema schemaForClassName:name]) {
+        table->clear();
+    }
+    else {
+        _realm.group.remove_table(table->get_key());
     }
 
     return true;

+ 65 - 3
Carthage/Checkouts/realm-cocoa/Realm/RLMObject.h

@@ -359,6 +359,13 @@ NS_ASSUME_NONNULL_BEGIN
  */
 @property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated;
 
+/**
+ Indicates if this object is frozen.
+
+ @see `-[RLMObject freeze]`
+ */
+@property (nonatomic, readonly, getter = isFrozen) BOOL frozen;
+
 
 #pragma mark - Customizing your Objects
 
@@ -548,7 +555,7 @@ typedef void (^RLMObjectChangeBlock)(BOOL deleted,
  deletes the object or modifies any of the managed properties of the object,
  including self-assignments that set a property to its existing value.
 
- For write transactions performed on different threads or in differen
+ For write transactions performed on different threads or in different
  processes, the block will be called when the managing Realm is
  (auto)refreshed to a version including the changes, while for local write
  transactions it will be called at some point in the future after the write
@@ -578,14 +585,54 @@ typedef void (^RLMObjectChangeBlock)(BOOL deleted,
  */
 - (RLMNotificationToken *)addNotificationBlock:(RLMObjectChangeBlock)block;
 
+/**
+ Registers a block to be called each time the object changes.
+
+ The block will be asynchronously called after each write transaction which
+ deletes the object or modifies any of the managed properties of the object,
+ including self-assignments that set a property to its existing value.
+
+ For write transactions performed on different threads or in different
+ processes, the block will be called when the managing Realm is
+ (auto)refreshed to a version including the changes, while for local write
+ transactions it will be called at some point in the future after the write
+ transaction is committed.
+
+ Notifications are delivered on the given queue. If the queue is blocked and
+ notifications can't be delivered instantly, multiple notifications may be
+ coalesced into a single notification.
+
+ Unlike with `RLMArray` and `RLMResults`, there is no "initial" callback made
+ after you add a new notification block.
+
+ Only objects which are managed by a Realm can be observed in this way. You
+ must retain the returned token for as long as you want updates to be sent to
+ the block. To stop receiving updates, call `-invalidate` on the token.
+
+ It is safe to capture a strong reference to the observed object within the
+ callback block. There is no retain cycle due to that the callback is retained
+ by the returned token and not by the object itself.
+
+ @warning This method cannot be called during a write transaction, when the
+          containing Realm is read-only, or on an unmanaged object.
+ @warning The queue must be a serial queue.
+
+ @param block The block to be called whenever a change occurs.
+ @param queue The serial queue to deliver notifications to.
+ @return A token which must be held for as long as you want updates to be delivered.
+ */
+- (RLMNotificationToken *)addNotificationBlock:(RLMObjectChangeBlock)block queue:(dispatch_queue_t)queue;
+
 #pragma mark - Other Instance Methods
 
 /**
  Returns YES if another Realm object instance points to the same object as the receiver in the Realm managing
  the receiver.
 
- For object types with a primary, key, `isEqual:` is overridden to use the same logic as this
- method (along with a corresponding implementation for `hash`).
+ For frozen objects and object types with a primary key, `isEqual:` is
+ overridden to use the same logic as this method (along with a corresponding
+ implementation for `hash`). Non-frozen objects without primary keys use
+ pointer identity for `isEqual:` and `hash`.
 
  @param object  The object to compare the receiver to.
 
@@ -593,6 +640,21 @@ typedef void (^RLMObjectChangeBlock)(BOOL deleted,
  */
 - (BOOL)isEqualToObject:(RLMObject *)object;
 
+/**
+ Returns a frozen (immutable) snapshot of this object.
+
+ The frozen copy is an immutable object which contains the same data as this
+ object currently contains, but will not update when writes are made to the
+ containing Realm. Unlike live objects, frozen objects can be accessed from any
+ thread.
+
+ - warning: Holding onto a frozen object for an extended period while performing write
+ transaction on the Realm may result in the Realm file growing to large sizes. See
+ `Realm.Configuration.maximumNumberOfActiveVersions` for more information.
+ - warning: This method can only be called on a managed object.
+ */
+- (instancetype)freeze NS_RETURNS_RETAINED;
+
 #pragma mark - Dynamic Accessors
 
 /// :nodoc:

+ 14 - 140
Carthage/Checkouts/realm-cocoa/Realm/RLMObject.mm

@@ -29,15 +29,8 @@
 #import "RLMRealm_Private.hpp"
 #import "RLMSchema_Private.h"
 
-#import "collection_notifications.hpp"
 #import "object.hpp"
 
-@interface RLMPropertyChange ()
-@property (nonatomic, readwrite, strong) NSString *name;
-@property (nonatomic, readwrite, strong, nullable) id previousValue;
-@property (nonatomic, readwrite, strong, nullable) id value;
-@end
-
 // We declare things in RLMObject which are actually implemented in RLMObjectBase
 // for documentation's sake, which leads to -Wunimplemented-method warnings.
 // Other alternatives to this would be to disable -Wunimplemented-method for this
@@ -159,27 +152,21 @@
     return [object isKindOfClass:RLMObject.class] && RLMObjectBaseAreEqual(self, object);
 }
 
+- (instancetype)freeze {
+    return RLMObjectFreeze(self);
+}
+
+- (BOOL)isFrozen {
+    return _realm.isFrozen;
+}
+
 - (RLMNotificationToken *)addNotificationBlock:(RLMObjectChangeBlock)block {
-    return RLMObjectAddNotificationBlock(self, ^(NSArray<NSString *> *propertyNames,
-                                                 NSArray *oldValues, NSArray *newValues, NSError *error) {
-        if (error) {
-            block(false, nil, error);
-        }
-        else if (!propertyNames) {
-            block(true, nil, nil);
-        }
-        else {
-            auto properties = [NSMutableArray arrayWithCapacity:propertyNames.count];
-            for (NSUInteger i = 0, count = propertyNames.count; i < count; ++i) {
-                auto prop = [RLMPropertyChange new];
-                prop.name = propertyNames[i];
-                prop.previousValue = RLMCoerceToNil(oldValues[i]);
-                prop.value = RLMCoerceToNil(newValues[i]);
-                [properties addObject:prop];
-            }
-            block(false, properties, nil);
-        }
-    });
+    return RLMObjectAddNotificationBlock(self, block, nil);
+}
+
+- (RLMNotificationToken *)addNotificationBlock:(RLMObjectChangeBlock)block
+                                         queue:(nonnull dispatch_queue_t)queue {
+    return RLMObjectAddNotificationBlock(self, block, queue);
 }
 
 + (NSString *)className {
@@ -249,116 +236,3 @@ BOOL RLMIsObjectOrSubclass(Class klass) {
 BOOL RLMIsObjectSubclass(Class klass) {
     return RLMIsKindOfClass(class_getSuperclass(class_getSuperclass(klass)), RLMObjectBase.class);
 }
-
-@interface RLMObjectNotificationToken : RLMCancellationToken
-@end
-@implementation RLMObjectNotificationToken {
-@public
-    realm::Object _object;
-}
-@end
-
-RLMNotificationToken *RLMObjectAddNotificationBlock(RLMObjectBase *obj, RLMObjectNotificationCallback block) {
-    if (!obj->_realm) {
-        @throw RLMException(@"Only objects which are managed by a Realm support change notifications");
-    }
-    [obj->_realm verifyNotificationsAreSupported:true];
-
-    struct {
-        void (^block)(NSArray<NSString *> *, NSArray *, NSArray *, NSError *);
-        RLMObjectBase *object;
-
-        NSArray<NSString *> *propertyNames = nil;
-        NSArray *oldValues = nil;
-        bool deleted = false;
-
-        void populateProperties(realm::CollectionChangeSet const& c) {
-            if (propertyNames) {
-                return;
-            }
-            if (!c.deletions.empty()) {
-                deleted = true;
-                return;
-            }
-            if (c.columns.empty()) {
-                return;
-            }
-
-            auto properties = [NSMutableArray new];
-            for (size_t i = 0; i < c.columns.size(); ++i) {
-                if (c.columns[i].empty()) {
-                    continue;
-                }
-                if (auto prop = object->_info->propertyForTableColumn(i)) {
-                    [properties addObject:prop.name];
-                }
-            }
-            if (properties.count) {
-                propertyNames = properties;
-            }
-        }
-
-        NSArray *readValues(realm::CollectionChangeSet const& c) {
-            if (c.empty()) {
-                return nil;
-            }
-            populateProperties(c);
-            if (!propertyNames) {
-                return nil;
-            }
-
-            auto values = [NSMutableArray arrayWithCapacity:propertyNames.count];
-            for (NSString *name in propertyNames) {
-                id value = [object valueForKey:name];
-                if (!value || [value isKindOfClass:[RLMArray class]]) {
-                    [values addObject:NSNull.null];
-                }
-                else {
-                    [values addObject:value];
-                }
-            }
-            return values;
-        }
-
-        void before(realm::CollectionChangeSet const& c) {
-            @autoreleasepool {
-                oldValues = readValues(c);
-            }
-        }
-
-        void after(realm::CollectionChangeSet const& c) {
-            @autoreleasepool {
-                auto newValues = readValues(c);
-                if (deleted) {
-                    block(nil, nil, nil, nil);
-                }
-                else if (newValues) {
-                    block(propertyNames, oldValues, newValues, nil);
-                }
-                propertyNames = nil;
-                oldValues = nil;
-            }
-        }
-
-        void error(std::exception_ptr err) {
-            @autoreleasepool {
-                try {
-                    rethrow_exception(err);
-                }
-                catch (...) {
-                    NSError *error = nil;
-                    RLMRealmTranslateException(&error);
-                    block(nil, nil, nil, error);
-                }
-            }
-        }
-    } callback{block, obj};
-
-    realm::Object object(obj->_realm->_realm, *obj->_info->objectSchema, obj->_row);
-    auto token = [[RLMObjectNotificationToken alloc] initWithToken:object.add_notification_callback(callback) realm:obj->_realm];
-    token->_object = std::move(object);
-    return token;
-}
-
-@implementation RLMPropertyChange
-@end

+ 257 - 15
Carthage/Checkouts/realm-cocoa/Realm/RLMObjectBase.mm

@@ -290,12 +290,12 @@ id RLMCreateManagedAccessor(Class cls, RLMClassInfo *info) {
 
 - (BOOL)isInvalidated {
     // if not unmanaged and our accessor has been detached, we have been deleted
-    return self.class == _objectSchema.accessorClass && !_row.is_attached();
+    return self.class == _objectSchema.accessorClass && !_row.is_valid();
 }
 
 - (BOOL)isEqual:(id)object {
     if (RLMObjectBase *other = RLMDynamicCast<RLMObjectBase>(object)) {
-        if (_objectSchema.primaryKeyProperty) {
+        if (_objectSchema.primaryKeyProperty || _realm.isFrozen) {
             return RLMObjectBaseAreEqual(self, other);
         }
     }
@@ -304,12 +304,22 @@ id RLMCreateManagedAccessor(Class cls, RLMClassInfo *info) {
 
 - (NSUInteger)hash {
     if (_objectSchema.primaryKeyProperty) {
+        // If we have a primary key property, that's an immutable value which we
+        // can use as the identity of the object.
         id primaryProperty = [self valueForKey:_objectSchema.primaryKeyProperty.name];
 
         // modify the hash of our primary key value to avoid potential (although unlikely) collisions
         return [primaryProperty hash] ^ 1;
     }
+    else if (_realm.isFrozen) {
+        // The object key can never change for frozen objects, so that's usable
+        // for objects without primary keys
+        return _row.get_key().value;
+    }
     else {
+        // Non-frozen objects without primary keys don't have any immutable
+        // concept of identity that we can hash so we have to fall back to
+        // pointer equality
         return [super hash];
     }
 }
@@ -366,29 +376,23 @@ id RLMCreateManagedAccessor(Class cls, RLMClassInfo *info) {
 
 #pragma mark - Thread Confined Protocol Conformance
 
-- (std::unique_ptr<realm::ThreadSafeReferenceBase>)makeThreadSafeReference {
-    Object object(_realm->_realm, *_info->objectSchema, _row);
-    realm::ThreadSafeReference<Object> reference = _realm->_realm->obtain_thread_safe_reference(std::move(object));
-    return std::make_unique<realm::ThreadSafeReference<Object>>(std::move(reference));
+- (realm::ThreadSafeReference)makeThreadSafeReference {
+    return Object(_realm->_realm, *_info->objectSchema, _row);
 }
 
 - (id)objectiveCMetadata {
     return nil;
 }
 
-+ (instancetype)objectWithThreadSafeReference:(std::unique_ptr<realm::ThreadSafeReferenceBase>)reference
++ (instancetype)objectWithThreadSafeReference:(realm::ThreadSafeReference)reference
                                      metadata:(__unused id)metadata
                                         realm:(RLMRealm *)realm {
-    REALM_ASSERT_DEBUG(dynamic_cast<realm::ThreadSafeReference<Object> *>(reference.get()));
-    auto object_reference = static_cast<realm::ThreadSafeReference<Object> *>(reference.get());
-
-    Object object = realm->_realm->resolve_thread_safe_reference(std::move(*object_reference));
+    Object object = reference.resolve<Object>(realm->_realm);
     if (!object.is_valid()) {
         return nil;
     }
     NSString *objectClassName = @(object.get_object_schema().name.c_str());
-
-    return RLMCreateObjectAccessor(realm->_info[objectClassName], object.row().get_index());
+    return RLMCreateObjectAccessor(realm->_info[objectClassName], object.obj());
 }
 
 @end
@@ -446,12 +450,30 @@ BOOL RLMObjectBaseAreEqual(RLMObjectBase *o1, RLMObjectBase *o2) {
         return NO;
     }
     // if either are detached
-    if (!o1->_row.is_attached() || !o2->_row.is_attached()) {
+    if (!o1->_row.is_valid() || !o2->_row.is_valid()) {
         return NO;
     }
     // if table and index are the same
     return o1->_row.get_table() == o2->_row.get_table()
-        && o1->_row.get_index() == o2->_row.get_index();
+        && o1->_row.get_key() == o2->_row.get_key();
+}
+
+id RLMObjectFreeze(RLMObjectBase *obj) {
+    if (!obj->_realm && !obj.isInvalidated) {
+        @throw RLMException(@"Unmanaged objects cannot be frozen.");
+    }
+    RLMVerifyAttached(obj);
+    if (obj->_realm.frozen) {
+        return obj;
+    }
+    RLMRealm *frozenRealm = [obj->_realm freeze];
+    RLMObjectBase *frozen = RLMCreateManagedAccessor(obj.class, &frozenRealm->_info[obj->_info->rlmObjectSchema.className]);
+    frozen->_row = frozenRealm->_realm->import_copy_of(obj->_row);
+    if (!frozen->_row.is_valid()) {
+        @throw RLMException(@"Cannot freeze an object in the same write transaction as it was created in.");
+    }
+    RLMInitializeSwiftAccessorGenerics(frozen);
+    return frozen;
 }
 
 id RLMValidatedValueForProperty(id object, NSString *key, NSString *className) {
@@ -466,3 +488,223 @@ id RLMValidatedValueForProperty(id object, NSString *key, NSString *className) {
         @throw;
     }
 }
+
+#pragma mark - Notifications
+
+namespace {
+struct ObjectChangeCallbackWrapper {
+    RLMObjectNotificationCallback block;
+    RLMObjectBase *object;
+
+    NSArray<NSString *> *propertyNames = nil;
+    NSArray *oldValues = nil;
+    bool deleted = false;
+
+    void populateProperties(realm::CollectionChangeSet const& c) {
+        if (propertyNames) {
+            return;
+        }
+        if (!c.deletions.empty()) {
+            deleted = true;
+            return;
+        }
+        if (c.columns.empty()) {
+            return;
+        }
+
+        auto properties = [NSMutableArray new];
+        for (RLMProperty *property in object->_info->rlmObjectSchema.properties) {
+            if (c.columns.count(object->_info->tableColumn(property).value)) {
+                [properties addObject:property.name];
+            }
+        }
+        if (properties.count) {
+            propertyNames = properties;
+        }
+    }
+
+    NSArray *readValues(realm::CollectionChangeSet const& c) {
+        if (c.empty()) {
+            return nil;
+        }
+        populateProperties(c);
+        if (!propertyNames) {
+            return nil;
+        }
+
+        auto values = [NSMutableArray arrayWithCapacity:propertyNames.count];
+        for (NSString *name in propertyNames) {
+            id value = [object valueForKey:name];
+            if (!value || [value isKindOfClass:[RLMArray class]]) {
+                [values addObject:NSNull.null];
+            }
+            else {
+                [values addObject:value];
+            }
+        }
+        return values;
+    }
+
+    void before(realm::CollectionChangeSet const& c) {
+        @autoreleasepool {
+            oldValues = readValues(c);
+        }
+    }
+
+    void after(realm::CollectionChangeSet const& c) {
+        @autoreleasepool {
+            auto newValues = readValues(c);
+            if (deleted) {
+                block(nil, nil, nil, nil, nil);
+            }
+            else if (newValues) {
+                block(object, propertyNames, oldValues, newValues, nil);
+            }
+            propertyNames = nil;
+            oldValues = nil;
+        }
+    }
+
+    void error(std::exception_ptr err) {
+        @autoreleasepool {
+            try {
+                rethrow_exception(err);
+            }
+            catch (...) {
+                NSError *error = nil;
+                RLMRealmTranslateException(&error);
+                block(nil, nil, nil, nil, error);
+            }
+        }
+    }
+};
+} // anonymous namespace
+
+@interface RLMPropertyChange ()
+@property (nonatomic, readwrite, strong) NSString *name;
+@property (nonatomic, readwrite, strong, nullable) id previousValue;
+@property (nonatomic, readwrite, strong, nullable) id value;
+@end
+
+@implementation RLMPropertyChange
+- (NSString *)description {
+    return [NSString stringWithFormat:@"<RLMPropertyChange: %p> %@ %@ -> %@",
+            (__bridge void *)self, _name, _previousValue, _value];
+}
+@end
+@interface RLMObjectNotificationToken : RLMNotificationToken
+@end
+
+@implementation RLMObjectNotificationToken {
+    std::mutex _mutex;
+    __unsafe_unretained RLMRealm *_realm;
+    realm::Object _object;
+    realm::NotificationToken _token;
+}
+
+- (RLMRealm *)realm {
+    return _realm;
+}
+
+- (void)suppressNextNotification {
+    std::lock_guard<std::mutex> lock(_mutex);
+    if (_object.is_valid()) {
+        _token.suppress_next();
+    }
+}
+
+- (void)invalidate {
+    std::lock_guard<std::mutex> lock(_mutex);
+    _realm = nil;
+    _token = {};
+    _object = {};
+}
+
+- (void)addNotificationBlock:(RLMObjectNotificationCallback)block
+         threadSafeReference:(RLMThreadSafeReference *)tsr
+                      config:(RLMRealmConfiguration *)config
+                       queue:(dispatch_queue_t)queue {
+    std::lock_guard<std::mutex> lock(_mutex);
+    if (!_realm) {
+        // Token was invalidated before we got this far
+        return;
+    }
+
+    NSError *error;
+    RLMRealm *realm = _realm = [RLMRealm realmWithConfiguration:config queue:queue error:&error];
+    if (!realm) {
+        block(nil, nil, nil, nil, error);
+        return;
+    }
+    RLMObjectBase *obj = [realm resolveThreadSafeReference:tsr];
+
+    _object = realm::Object(obj->_realm->_realm, *obj->_info->objectSchema, obj->_row);
+    _token = _object.add_notification_callback(ObjectChangeCallbackWrapper{block, obj});
+}
+
+- (void)addNotificationBlock:(RLMObjectNotificationCallback)block object:(RLMObjectBase *)obj {
+    _object = realm::Object(obj->_realm->_realm, *obj->_info->objectSchema, obj->_row);
+    _realm = obj->_realm;
+    _token = _object.add_notification_callback(ObjectChangeCallbackWrapper{block, obj});
+}
+
+RLMNotificationToken *RLMObjectBaseAddNotificationBlock(RLMObjectBase *obj, dispatch_queue_t queue,
+                                                        RLMObjectNotificationCallback block) {
+    if (!obj->_realm) {
+        @throw RLMException(@"Only objects which are managed by a Realm support change notifications");
+    }
+
+    if (!queue) {
+        [obj->_realm verifyNotificationsAreSupported:true];
+        auto token = [[RLMObjectNotificationToken alloc] init];
+        token->_realm = obj->_realm;
+        [token addNotificationBlock:block object:obj];
+        return token;
+    }
+
+    RLMThreadSafeReference *tsr = [RLMThreadSafeReference referenceWithThreadConfined:(id)obj];
+    auto token = [[RLMObjectNotificationToken alloc] init];
+    token->_realm = obj->_realm;
+    RLMRealmConfiguration *config = obj->_realm.configuration;
+    dispatch_async(queue, ^{
+        @autoreleasepool {
+            [token addNotificationBlock:block threadSafeReference:tsr config:config queue:queue];
+        }
+    });
+    return token;
+}
+
+@end
+
+RLMNotificationToken *RLMObjectAddNotificationBlock(RLMObjectBase *obj, RLMObjectChangeBlock block, dispatch_queue_t queue) {
+    return RLMObjectBaseAddNotificationBlock(obj, queue, ^(RLMObjectBase *, NSArray<NSString *> *propertyNames,
+                                                           NSArray *oldValues, NSArray *newValues, NSError *error) {
+        if (error) {
+            block(false, nil, error);
+        }
+        else if (!propertyNames) {
+            block(true, nil, nil);
+        }
+        else {
+            auto properties = [NSMutableArray arrayWithCapacity:propertyNames.count];
+            for (NSUInteger i = 0, count = propertyNames.count; i < count; ++i) {
+                auto prop = [RLMPropertyChange new];
+                prop.name = propertyNames[i];
+                prop.previousValue = RLMCoerceToNil(oldValues[i]);
+                prop.value = RLMCoerceToNil(newValues[i]);
+                [properties addObject:prop];
+            }
+            block(false, properties, nil);
+        }
+    });
+}
+
+uint64_t RLMObjectBaseGetCombineId(__unsafe_unretained RLMObjectBase *const obj) {
+    if (obj.invalidated) {
+        RLMVerifyAttached(obj);
+    }
+    if (obj->_realm) {
+        return obj->_row.get_key().value;
+    }
+    return reinterpret_cast<uint64_t>((__bridge void *)obj);
+}

+ 0 - 6
Carthage/Checkouts/realm-cocoa/Realm/RLMObjectSchema.mm

@@ -343,12 +343,6 @@ using namespace realm;
         return _swiftGenericProperties;
     }
 
-    // This check isn't semantically required, but avoiding accessing the local
-    // static helps perf in the obj-c case
-    if (!_isSwiftClass) {
-        return _swiftGenericProperties = @[];
-    }
-
     // Check if it's a swift class using the obj-c API
     static Class s_swiftObjectClass = NSClassFromString(@"RealmSwiftObject");
     if (![_accessorClass isSubclassOfClass:s_swiftObjectClass]) {

+ 3 - 4
Carthage/Checkouts/realm-cocoa/Realm/RLMObjectStore.h

@@ -80,14 +80,13 @@ void RLMInitializeSwiftAccessorGenerics(RLMObjectBase *object);
 
 namespace realm {
     class Table;
-    template<typename T> class BasicRowExpr;
-    using RowExpr = BasicRowExpr<Table>;
+    class Obj;
 }
 class RLMClassInfo;
 
 // Create accessors
-RLMObjectBase *RLMCreateObjectAccessor(RLMClassInfo& info, NSUInteger index) NS_RETURNS_RETAINED;
-RLMObjectBase *RLMCreateObjectAccessor(RLMClassInfo& info, realm::RowExpr row) NS_RETURNS_RETAINED;
+RLMObjectBase *RLMCreateObjectAccessor(RLMClassInfo& info, int64_t key) NS_RETURNS_RETAINED;
+RLMObjectBase *RLMCreateObjectAccessor(RLMClassInfo& info, realm::Obj&& obj) NS_RETURNS_RETAINED;
 #endif
 
 NS_ASSUME_NONNULL_END

+ 15 - 10
Carthage/Checkouts/realm-cocoa/Realm/RLMObjectStore.mm

@@ -70,6 +70,11 @@ static inline void RLMVerifyRealmRead(__unsafe_unretained RLMRealm *const realm)
         @throw RLMException(@"Realm must not be nil");
     }
     [realm verifyThread];
+    if (realm->_realm->is_closed()) {
+        // This message may seem overly specific, but frozen Realms are currently
+        // the only ones which we outright close.
+        @throw RLMException(@"Cannot read from a frozen Realm which has been invalidated.");
+    }
 }
 
 static inline void RLMVerifyInWriteTransaction(__unsafe_unretained RLMRealm *const realm) {
@@ -144,7 +149,7 @@ void RLMAddObjectToRealm(__unsafe_unretained RLMObjectBase *const object,
     try {
         realm::Object::create(c, realm->_realm, *info.objectSchema, (id)object,
                               static_cast<CreatePolicy>(updatePolicy),
-                              -1, &object->_row);
+                              {}, &object->_row);
     }
     catch (std::exception const& e) {
         @throw RLMException(e);
@@ -180,7 +185,7 @@ RLMObjectBase *RLMCreateObjectInRealmWithValue(RLMRealm *realm, NSString *classN
     RLMObjectBase *object = RLMCreateManagedAccessor(info.rlmObjectSchema.accessorClass, &info);
     try {
         object->_row = realm::Object::create(c, realm->_realm, *info.objectSchema, (id)value,
-                                             static_cast<realm::CreatePolicy>(updatePolicy)).row();
+                                             static_cast<CreatePolicy>(updatePolicy)).obj();
     }
     catch (std::exception const& e) {
         @throw RLMException(e);
@@ -198,9 +203,9 @@ void RLMDeleteObjectFromRealm(__unsafe_unretained RLMObjectBase *const object,
     RLMVerifyInWriteTransaction(object->_realm);
 
     // move last row to row we are deleting
-    if (object->_row.is_attached()) {
+    if (object->_row.is_valid()) {
         RLMTrackDeletions(realm, ^{
-            object->_row.move_last_over();
+            object->_row.remove();
         });
     }
 
@@ -237,7 +242,7 @@ RLMResults *RLMGetObjects(__unsafe_unretained RLMRealm *const realm,
     }
 
     return [RLMResults resultsWithObjectInfo:info
-                                     results:realm::Results(realm->_realm, *info.table())];
+                                     results:realm::Results(realm->_realm, info.table())];
 }
 
 id RLMGetObject(RLMRealm *realm, NSString *objectClassName, id key) {
@@ -253,21 +258,21 @@ id RLMGetObject(RLMRealm *realm, NSString *objectClassName, id key) {
                                                       key ?: NSNull.null);
         if (!obj.is_valid())
             return nil;
-        return RLMCreateObjectAccessor(info, obj.row());
+        return RLMCreateObjectAccessor(info, obj.obj());
     }
     catch (std::exception const& e) {
         @throw RLMException(e);
     }
 }
 
-RLMObjectBase *RLMCreateObjectAccessor(RLMClassInfo& info, NSUInteger index) {
-    return RLMCreateObjectAccessor(info, (*info.table())[index]);
+RLMObjectBase *RLMCreateObjectAccessor(RLMClassInfo& info, int64_t key) {
+    return RLMCreateObjectAccessor(info, info.table()->get_object(realm::ObjKey(key)));
 }
 
 // Create accessor and register with realm
-RLMObjectBase *RLMCreateObjectAccessor(RLMClassInfo& info, realm::RowExpr row) {
+RLMObjectBase *RLMCreateObjectAccessor(RLMClassInfo& info, realm::Obj&& obj) {
     RLMObjectBase *accessor = RLMCreateManagedAccessor(info.rlmObjectSchema.accessorClass, &info);
-    accessor->_row = row;
+    accessor->_row = std::move(obj);
     RLMInitializeSwiftAccessorGenerics(accessor);
     return accessor;
 }

+ 13 - 2
Carthage/Checkouts/realm-cocoa/Realm/RLMObject_Private.h

@@ -50,11 +50,16 @@ FOUNDATION_EXTERN id _Nullable RLMValidatedValueForProperty(id object, NSString
 // Compare two RLObjectBases
 FOUNDATION_EXTERN BOOL RLMObjectBaseAreEqual(RLMObjectBase * _Nullable o1, RLMObjectBase * _Nullable o2);
 
-typedef void (^RLMObjectNotificationCallback)(NSArray<NSString *> *_Nullable propertyNames,
+typedef void (^RLMObjectNotificationCallback)(RLMObjectBase *_Nullable object,
+                                              NSArray<NSString *> *_Nullable propertyNames,
                                               NSArray *_Nullable oldValues,
                                               NSArray *_Nullable newValues,
                                               NSError *_Nullable error);
-FOUNDATION_EXTERN RLMNotificationToken *RLMObjectAddNotificationBlock(RLMObjectBase *obj, RLMObjectNotificationCallback block);
+FOUNDATION_EXTERN RLMNotificationToken *RLMObjectBaseAddNotificationBlock(RLMObjectBase *obj,
+                                                                          dispatch_queue_t _Nullable queue,
+                                                                          RLMObjectNotificationCallback block);
+RLMNotificationToken *RLMObjectAddNotificationBlock(RLMObjectBase *obj, RLMObjectChangeBlock block,
+                                                    dispatch_queue_t _Nullable queue);
 
 // Returns whether the class is a descendent of RLMObjectBase
 FOUNDATION_EXTERN BOOL RLMIsObjectOrSubclass(Class klass);
@@ -64,6 +69,12 @@ FOUNDATION_EXTERN BOOL RLMIsObjectSubclass(Class klass);
 
 FOUNDATION_EXTERN const NSUInteger RLMDescriptionMaxDepth;
 
+FOUNDATION_EXTERN id RLMObjectFreeze(RLMObjectBase *obj) NS_RETURNS_RETAINED;
+
+// Gets an object identifier suitable for use with Combine. This value may
+// change when an unmanaged object is added to the Realm.
+FOUNDATION_EXTERN uint64_t RLMObjectBaseGetCombineId(RLMObjectBase *);
+
 @interface RLMManagedPropertyAccessor : NSObject
 + (void)initializeObject:(void *)object parent:(RLMObjectBase *)parent property:(RLMProperty *)property;
 + (id)get:(void *)pointer;

部分文件因为文件数量过多而无法显示