From fdf9497bdd62efc42ab1bec68f3f80763a71e2ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Fri, 16 Feb 2018 10:27:30 +0100 Subject: [PATCH 01/10] Fixing objectForPrimaryKey return type (#1664) According to https://github.com/realm/realm-js/blob/master/docs/realm.js#L171 and runtime behaviour it returns `undefined` not `null` if the object doesn't exist. --- lib/index.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/index.d.ts b/lib/index.d.ts index 878d8e82..9e59bd73 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -545,9 +545,9 @@ declare class Realm { /** * @param {string|Realm.ObjectSchema|Function} type * @param {number|string} key - * @returns T + * @returns {T | undefined} */ - objectForPrimaryKey(type: string | Realm.ObjectSchema | Function, key: number | string): T | null; + objectForPrimaryKey(type: string | Realm.ObjectSchema | Function, key: number | string): T | undefined; /** * @param {string|Realm.ObjectType|Function} type From af41f049caa45ce801632da5b96060297d9c0e46 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Mon, 19 Feb 2018 21:00:52 +0100 Subject: [PATCH 02/10] Realm Sync 2.2.11. (#1666) --- CHANGELOG.md | 15 +++++++++++++++ dependencies.list | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f88f45b..3fdf393f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +X.Y.Z Release notes (2018-2-13) +============================================================= +### Breaking changes +* None. + +### Enhancements +* None. + +### Bug fixes +* Improved root certificate checking. + +### Internal +* Updated to Realm Sync 2.2.11. + + 2.2.8 Release notes (2018-2-13) ============================================================= ### Breaking changes diff --git a/dependencies.list b/dependencies.list index 010eeee7..8ff82444 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js VERSION=2.2.8 REALM_CORE_VERSION=5.1.2 -REALM_SYNC_VERSION=2.2.10 +REALM_SYNC_VERSION=2.2.11 REALM_OBJECT_SERVER_VERSION=2.5.1 From dbd543d8913f17b38e730f161a2446d9e1a37d15 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Mon, 19 Feb 2018 21:04:13 +0100 Subject: [PATCH 03/10] [2.2.9] Bump version --- CHANGELOG.md | 2 +- dependencies.list | 2 +- package.json | 2 +- src/RealmJS.xcodeproj/project.pbxproj | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fdf393f..9b8522b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -X.Y.Z Release notes (2018-2-13) +2.2.9 Release notes (2018-2-19) ============================================================= ### Breaking changes * None. diff --git a/dependencies.list b/dependencies.list index 8ff82444..2c1f2364 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.2.8 +VERSION=2.2.9 REALM_CORE_VERSION=5.1.2 REALM_SYNC_VERSION=2.2.11 REALM_OBJECT_SERVER_VERSION=2.5.1 diff --git a/package.json b/package.json index 6d3f357b..1a48be60 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "realm", "description": "Realm is a mobile database: an alternative to SQLite and key-value stores", - "version": "2.2.8", + "version": "2.2.9", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ diff --git a/src/RealmJS.xcodeproj/project.pbxproj b/src/RealmJS.xcodeproj/project.pbxproj index 20873c88..0d0ad915 100644 --- a/src/RealmJS.xcodeproj/project.pbxproj +++ b/src/RealmJS.xcodeproj/project.pbxproj @@ -961,7 +961,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2.2.8; + CURRENT_PROJECT_VERSION = 2.2.9; CXX = "$(SRCROOT)/../scripts/ccache-clang++.sh"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -1025,7 +1025,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2.2.8; + CURRENT_PROJECT_VERSION = 2.2.9; CXX = "$(SRCROOT)/../scripts/ccache-clang++.sh"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; From 462856a24dafafa1c9a0762c5bf28151e9762132 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 20 Feb 2018 13:40:54 +0100 Subject: [PATCH 04/10] Partial sync (#1583) The new Partial Sync API --- CHANGELOG.md | 13 +- docs/collection.js | 36 ++- docs/realm.js | 11 - docs/sync.js | 58 +++++ .../realm/react/example/MainApplication.java | 2 +- lib/browser/constants.js | 1 + lib/browser/index.js | 4 +- lib/browser/results.js | 1 + lib/browser/subscription.js | 38 ++++ lib/extensions.js | 19 +- lib/index.d.ts | 25 ++- react-native/android/src/main/jni/Android.mk | 1 + realm.gypi | 4 +- src/RealmJS.xcodeproj/project.pbxproj | 18 +- src/js_collection.hpp | 11 +- src/js_realm.hpp | 50 ----- src/js_results.hpp | 47 +++- src/js_sync.hpp | 122 +++++++++++ src/object-store | 2 +- tests/js/index.js | 2 +- tests/js/session-tests.js | 206 +++++++++++++++++- 21 files changed, 550 insertions(+), 121 deletions(-) create mode 100644 lib/browser/subscription.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 08f84e74..fe19f677 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,19 +1,22 @@ 2.3.0 Release notes (2018-2-19) ============================================================= ### Breaking changes -* Sync protocol changed to version 24. -* History schema format for server-side Realm files bumped to version 4. This means that after the server has been upgraded, it cannot be downgraded again without restoring state from backup. -* Backup protocol version bumped to 2. No compatibility with earlier versions of the backup protocol is provided. +* [Object Server] Sync protocol changed to version 24. +* [Object Server] History schema format for server-side Realm files bumped to version 4. This means that after the server has been upgraded, it cannot be downgraded again without restoring state from backup. +* [Object Server] Backup protocol version bumped to 2. No compatibility with earlier versions of the backup protocol is provided. +* [Object Server] `Realm.subscribeToObjects()` has been removed. Use `Realm.Results.subscribe()` instead. ### Enhancements -* Reduced initial download times in Realms with long transaction histories. -* Wait for pending notifications to complete when removing a sync listener (1648). +* [Object Server] Reduced initial download times in Realms with long transaction histories. +* [Object Server] Wait for pending notifications to complete when removing a sync listener (1648). * Enabled sort and distinct in the query string. If sort or distinct are also applied outside of the query string, the conditions are stacked. - Example syntax: `age > 20 SORT(name ASC, age DESC) DISTINCT(name)` - The ordering for sorting can be one of the following case insensitive literals: `ASC`, `ASCENDING`, `DESC`, `DESCENDING`. - Any number of properties can appear inside the brackets in a comma separated list. - Any number of sort/distinct conditions can be indicated, they will be applied in the specified order. - Sort or distinct cannot operate independently, these conditions must be attached to at least one query filter. +* [Object Server] Added `Realm.Results.subscribe()` to subscribe to partial synced Realms. +* [Object Server] Added class `Realm.Sync.Subscription` to support partial synced Realms. ### Internal * Updated to Realm Core 5.2.0. diff --git a/docs/collection.js b/docs/collection.js index a3f524b3..648738e7 100644 --- a/docs/collection.js +++ b/docs/collection.js @@ -134,6 +134,26 @@ class Collection { */ snapshot() {} + /** + * Subscribe to a subset of objects matching the query of the collection. The Realm will only be + * partially synced. Not all queries are currently supported. Once subscribed, it is highly recommended + * to add a listener. + * + * @example + * let wines = realm.objects('Wine').filtered('vintage <= $0', maxYear).subscribe(); + * wines.addListener((collection, changes) => { + * if (changes.partial_sync.new_state == Realm.Sync.SubscriptionState.Initialized) { + * // update UI + * } + * }); + * + * @param {string} subscriptionName - an optional name for the subscription. + * @returns {Realm.Sync.Subscription} - the Realm.Sync.Subscription instance. + * @throws {Error} if the partial sync is not enabled in the configuration or the query is not supported by Realm Object Server. + * @since 2.3.0 + */ + subscribe(subscriptionName) {} + /** * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/entries Array.prototype.entries} * @returns {Realm.Collection~Iterator} of each `[index, object]` pair in the collection @@ -400,15 +420,21 @@ class Collection { * The callback function is called with two arguments: * - `collection`: the collection instance that changed, * - `changes`: a dictionary with keys `insertions`, `modifications` and `deletions`, - * each containing a list of indices that were inserted, updated or deleted respectively. + * each containing a list of indices that were inserted, updated or deleted respectively. If + * partial sync is enabled, an additional key `partial_sync` is added. + * - `changes.partial_sync`: `error` indicates if an error has occurred, `old_state` is the previous + * state, and `new_state` is the current state. * @throws {Error} If `callback` is not a function. * @example * wines.addListener((collection, changes) => { * // collection === wines - * console.log(`${changes.insertions.length} insertions`); - * console.log(`${changes.modifications.length} modifications`); - * console.log(`${changes.deletions.length} deletions`); - * console.log(`new size of collection: ${collection.length}`); + * if (changes.partial_sync.new_state == Realm.Sync.SubscriptionState.Initialized) { + * console.log('Our subset is ready'); + * console.log(`${changes.insertions.length} insertions`); + * console.log(`${changes.modifications.length} modifications`); + * console.log(`${changes.deletions.length} deletions`); + * console.log(`new size of collection: ${collection.length}`); + * } * }); */ addListener(callback) {} diff --git a/docs/realm.js b/docs/realm.js index d4785253..a04c259d 100644 --- a/docs/realm.js +++ b/docs/realm.js @@ -241,17 +241,6 @@ class Realm { * @returns {true} if compaction succeeds. */ compact() {} - - /** - * If the Realm is a partially synchronized Realm, fetch and synchronize the objects - * of a given object type that match the given query (in string format). - * - * **Partial synchronization is a tech preview. Its APIs are subject to change.** - * @param {Realm~ObjectType} type - The type of Realm objects to retrieve. - * @param {string} query - Query used to filter objects. - * @return {Promise} - a promise that will be resolved with the Realm.Results instance when it's available. - */ - subscribeToObjects(className, query, callback) {} } /** diff --git a/docs/sync.js b/docs/sync.js index 65d6520f..77dfeedc 100644 --- a/docs/sync.js +++ b/docs/sync.js @@ -468,6 +468,64 @@ class Session { removeProgressNotification(progressCallback) {} } +/** + * An object encapsulating partial sync subscriptions. + * @memberof Realm.Sync + */ +class Subscription { + /** + * Gets the current state of the subscription. + * Can be either: + * - Realm.Sync.SubscriptionState.Error: An error occurred while creating or processing the partial sync subscription. + * - Realm.Sync.SubscriptionState.Creating: The subscription is being created. + * - Realm.Sync.SubscriptionState.Pending: The subscription was created, but has not yet been processed by the sync server. + * - Realm.Sync.SubscriptionState.Complete: The subscription has been processed by the sync server and data is being synced to the device. + * - Realm.Sync.SubscriptionState.Invalidated: The subscription has been removed. + * @type {number} + */ + get state() {} + + /** + * Gets the error message. `undefined` if no error. + * @type {string} + */ + get error() {} + + /** + * Unsubscribe a partial synced `Realm.Results`. The state will change to `Realm.Sync.SubscriptionState.Invalidated`. + * The `Realm.Results` will not produce any meaningful values. Moreover, any objects matching the query will be + * removed if they are not matched by any other query. The object removal is done asynchronously. + */ + unsubscribe() {} + + /** + * Adds a listener `callback` which will be called when the state of the subscription changes. + * @param {function(state)} callback - A function to be called when changes occur. + * @throws {Error} If `callback` is not a function. + * @example + * let subscription = results.subscribe(); + * subscription.addListener((subscription, state) => { + * switch (state) { + * case Realm.Sync.SubscriptionState.Complete: + * // results is ready to be consumed + * break; + * case Realm.Sync.SubscriptionState.Error: + * console.log('An error occurred: ', subscription.error); + * break; + * } + * } + */ + addListener(callback) {} + + /** + * Remove the listener `callback` from the subscription instance. + * @param {function(collection, changes)} callback - Callback function that was previously + * added as a listener through the {@link Subscription#addListener addListener} method. + * @throws {Error} If `callback` is not a function. + */ + removeListener(callback) {} +} + /** * A Realm Worker can be used to process Sync events in multiple automatically-managed child processes. * diff --git a/examples/ReactExample/android/app/src/main/java/io/realm/react/example/MainApplication.java b/examples/ReactExample/android/app/src/main/java/io/realm/react/example/MainApplication.java index b220a0d8..2a428589 100644 --- a/examples/ReactExample/android/app/src/main/java/io/realm/react/example/MainApplication.java +++ b/examples/ReactExample/android/app/src/main/java/io/realm/react/example/MainApplication.java @@ -20,7 +20,7 @@ public class MainApplication extends Application implements ReactApplication { private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { @Override - protected boolean getUseDeveloperSupport() { + public boolean getUseDeveloperSupport() { return BuildConfig.DEBUG; } diff --git a/lib/browser/constants.js b/lib/browser/constants.js index fe7360fe..3cb55ab6 100644 --- a/lib/browser/constants.js +++ b/lib/browser/constants.js @@ -41,6 +41,7 @@ export const propTypes = {}; 'RESULTS', 'USER', 'SESSION', + 'SUBSCRIPTION', 'UNDEFINED', ].forEach(function(type) { Object.defineProperty(objectTypes, type, { diff --git a/lib/browser/index.js b/lib/browser/index.js index a2e449d2..dbd6d42f 100644 --- a/lib/browser/index.js +++ b/lib/browser/index.js @@ -130,7 +130,6 @@ util.createMethods(Realm.prototype, objectTypes.REALM, [ 'close', '_waitForDownload', '_objectForObjectId', - '_subscribeToObjects', ]); // Mutating methods: @@ -147,7 +146,8 @@ util.createMethods(Realm.prototype, objectTypes.REALM, [ const Sync = { User, - Session + Session, + Subscription, }; Object.defineProperties(Realm, { diff --git a/lib/browser/results.js b/lib/browser/results.js index a35f9fda..d309d7e8 100644 --- a/lib/browser/results.js +++ b/lib/browser/results.js @@ -30,6 +30,7 @@ createMethods(Results.prototype, objectTypes.RESULTS, [ 'filtered', 'sorted', 'snapshot', + 'subscribe', 'isValid', 'indexOf', 'min', diff --git a/lib/browser/subscription.js b/lib/browser/subscription.js new file mode 100644 index 00000000..3dddeac2 --- /dev/null +++ b/lib/browser/subscription.js @@ -0,0 +1,38 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2018 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +'use strict'; + +import { objectTypes } from './constants'; +import { getterForProperty, createMethods } from './util'; + +export default class Subscription { + +} + +Object.defineProperties(Subscription.prototype, { + error: { get: getterForProperty('error') }, + state: { get: getterForProperty('state') } +}); + +// // Non-mutating methods: +createMethods(Subscription.prototype, objectTypes.SUBSCRIPTION, [ + 'unsubscribe', + 'addListener', + 'removeListener' +]); diff --git a/lib/extensions.js b/lib/extensions.js index f1c04695..5c793e34 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -159,18 +159,13 @@ module.exports = function(realmConstructor) { } } - realmConstructor.prototype.subscribeToObjects = function(objectType, query) { - const realm = this; - let promise = new Promise((resolve, reject) => { - realm._subscribeToObjects(objectType, query, function(err, results) { - if (err) { - reject(err); - } else { - resolve(results); - } - }); - }); - return promise; + // Keep these value in sync with subscription_state.hpp + realmConstructor.Sync.SubscriptionState = { + Error: -1, // An error occurred while creating or processing the partial sync subscription. + Creating: 2, // The subscription is being created. + Pending: 0, // The subscription was created, but has not yet been processed by the sync server. + Complete: 1, // The subscription has been processed by the sync server and data is being synced to the device. + Invalidated: 3, // The subscription has been removed. }; } diff --git a/lib/index.d.ts b/lib/index.d.ts index 878d8e82..b333adc1 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -161,6 +161,11 @@ declare namespace Realm { sorted(descriptor: SortDescriptor[]): Results; sorted(descriptor: string, reverse?: boolean): Results; + /** + * @returns Results + */ + subscribe(subscriptionName?: string): Realm.Sync.Subscription; + /** * @returns Results */ @@ -394,6 +399,21 @@ declare namespace Realm.Sync { removeProgressNotification(progressCallback: ProgressNotificationCallback): void; } + type SubscriptionNotificationCallback = (state: number) => void; + + /** + * Subscription + * @see { @link https://realm.io/docs/javascript/latest/api/Realm.Sync.Subscription.html } + */ + class Subscription { + readonly state: number; + readonly error: string; + + unsubscribe(): void; + addListener(subscruptionCallback: SubscriptionNotificationCallback): void; + removeListener(subscruptionCallback: SubscriptionNotificationCallback): void; + } + /** * AuthError * @see { @link https://realm.io/docs/javascript/latest/api/Realm.Sync.AuthError.html } @@ -600,11 +620,6 @@ declare class Realm { * @returns boolean */ compact(): boolean; - - /** - * @returns Promise> - */ - subscribeToObjects(objectType: string, query: string): Promise>; } declare module 'realm' { diff --git a/react-native/android/src/main/jni/Android.mk b/react-native/android/src/main/jni/Android.mk index 87ced46c..ce0554ca 100644 --- a/react-native/android/src/main/jni/Android.mk +++ b/react-native/android/src/main/jni/Android.mk @@ -78,6 +78,7 @@ LOCAL_SRC_FILES += src/object-store/src/sync/sync_user.cpp LOCAL_SRC_FILES += src/object-store/src/sync/sync_permission.cpp LOCAL_SRC_FILES += src/object-store/src/sync/impl/sync_file.cpp LOCAL_SRC_FILES += src/object-store/src/sync/impl/sync_metadata.cpp +LOCAL_SRC_FILES += src/object-store/src/sync/impl/work_queue.cpp endif LOCAL_C_INCLUDES := src diff --git a/realm.gypi b/realm.gypi index 94b8c297..45aa4dfb 100644 --- a/realm.gypi +++ b/realm.gypi @@ -99,6 +99,7 @@ "src/object-store/src/sync/impl/sync_client.hpp", "src/object-store/src/sync/impl/sync_file.hpp", "src/object-store/src/sync/impl/sync_metadata.hpp", + "src/object-store/src/sync/impl/work_queue.hpp", "src/object-store/src/sync/partial_sync.hpp", "src/object-store/src/sync/sync_config.hpp", "src/object-store/src/sync/sync_manager.hpp", @@ -147,7 +148,8 @@ "src/object-store/src/sync/sync_session.cpp", "src/object-store/src/sync/sync_config.cpp", "src/object-store/src/sync/impl/sync_file.cpp", - "src/object-store/src/sync/impl/sync_metadata.cpp" + "src/object-store/src/sync/impl/sync_metadata.cpp", + "src/object-store/src/sync/impl/work_queue.cpp" ], }] ], diff --git a/src/RealmJS.xcodeproj/project.pbxproj b/src/RealmJS.xcodeproj/project.pbxproj index 9ec1b86c..93138bab 100644 --- a/src/RealmJS.xcodeproj/project.pbxproj +++ b/src/RealmJS.xcodeproj/project.pbxproj @@ -41,6 +41,8 @@ 3FCE2A931F58BE0300D4855B /* uuid.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3FCE2A911F58BDFF00D4855B /* uuid.cpp */; }; 3FCE2A971F58BE2200D4855B /* sync_permission.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3FCE2A951F58BE1D00D4855B /* sync_permission.cpp */; }; 420FB79F1F7FBFE900D43D0F /* partial_sync.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 423737AF1F7E333400FAEDFF /* partial_sync.cpp */; }; + 425A121120235A1400C2F932 /* work_queue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 425A120F20235A1400C2F932 /* work_queue.cpp */; }; + 4261AF8E203C42000052450D /* work_queue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 425A120F20235A1400C2F932 /* work_queue.cpp */; }; 502B07E41E2CD201007A84ED /* object.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 502B07E31E2CD1FA007A84ED /* object.cpp */; }; 504CF85E1EBCAE3600A9A4B6 /* network_reachability_observer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 504CF8541EBCAE3600A9A4B6 /* network_reachability_observer.cpp */; }; 504CF85F1EBCAE3600A9A4B6 /* system_configuration.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 504CF8561EBCAE3600A9A4B6 /* system_configuration.cpp */; }; @@ -185,6 +187,8 @@ 3FCE2A991F58BE3600D4855B /* feature_checks.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = feature_checks.hpp; path = "object-store/src/feature_checks.hpp"; sourceTree = SOURCE_ROOT; }; 423737AF1F7E333400FAEDFF /* partial_sync.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = partial_sync.cpp; path = src/sync/partial_sync.cpp; sourceTree = ""; }; 423737B01F7E333400FAEDFF /* partial_sync.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = partial_sync.hpp; path = src/sync/partial_sync.hpp; sourceTree = ""; }; + 425A120F20235A1400C2F932 /* work_queue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = work_queue.cpp; sourceTree = ""; }; + 425A121020235A1400C2F932 /* work_queue.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = work_queue.hpp; sourceTree = ""; }; 426FCDFF1F7DA2F9005565DC /* sync_config.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = sync_config.cpp; path = src/sync/sync_config.cpp; sourceTree = ""; }; 502B07E31E2CD1FA007A84ED /* object.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = object.cpp; path = src/object.cpp; sourceTree = ""; }; 502B07E51E2CD20D007A84ED /* object.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = object.hpp; path = src/object.hpp; sourceTree = ""; }; @@ -474,6 +478,8 @@ 504CF8521EBCAE3600A9A4B6 /* impl */ = { isa = PBXGroup; children = ( + 425A120F20235A1400C2F932 /* work_queue.cpp */, + 425A121020235A1400C2F932 /* work_queue.hpp */, 504CF8531EBCAE3600A9A4B6 /* apple */, 504CF8581EBCAE3600A9A4B6 /* network_reachability.hpp */, 504CF8591EBCAE3600A9A4B6 /* sync_client.hpp */, @@ -873,6 +879,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4261AF8E203C42000052450D /* work_queue.cpp in Sources */, F63FF2E21C15921A00B3B8E0 /* base64.cpp in Sources */, 022BF1021E7266DF00F382F1 /* binding_callback_thread_observer.cpp in Sources */, 02414BA51CE6ABCF00A8669F /* collection_change_builder.cpp in Sources */, @@ -934,6 +941,7 @@ F63FF31E1C1642BB00B3B8E0 /* GCDWebServerRequest.m in Sources */, F63FF31F1C1642BB00B3B8E0 /* GCDWebServerResponse.m in Sources */, F63FF3271C1642BB00B3B8E0 /* GCDWebServerStreamedResponse.m in Sources */, + 425A121120235A1400C2F932 /* work_queue.cpp in Sources */, F63FF3231C1642BB00B3B8E0 /* GCDWebServerURLEncodedFormRequest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1115,10 +1123,7 @@ "-isystem", "$(SRCROOT)/../vendor/sync/include", ); - OTHER_LIBTOOLFLAGS = ( - "$(SRCROOT)/../vendor/realm-ios/librealm-parser-ios-dbg.a", - "$(SRCROOT)/../vendor/realm-ios/librealm-ios-dbg.a", - ); + OTHER_LIBTOOLFLAGS = "$(SRCROOT)/../vendor/realm-ios/librealm-parser-ios-dbg.a $(SRCROOT)/../vendor/realm-ios/librealm-ios-dbg.a"; PRODUCT_NAME = RealmJS; SKIP_INSTALL = YES; }; @@ -1140,10 +1145,7 @@ "-isystem", "$(SRCROOT)/../vendor/sync/include", ); - OTHER_LIBTOOLFLAGS = ( - "$(SRCROOT)/../vendor/realm-ios/librealm-parser-ios.a", - "$(SRCROOT)/../vendor/realm-ios/librealm-ios.a", - ); + OTHER_LIBTOOLFLAGS = "$(SRCROOT)/../vendor/realm-ios/librealm-parser-ios.a $(SRCROOT)/../vendor/realm-ios/librealm-ios.a"; PRODUCT_NAME = RealmJS; SKIP_INSTALL = YES; }; diff --git a/src/js_collection.hpp b/src/js_collection.hpp index 4e537791..a83db75f 100644 --- a/src/js_collection.hpp +++ b/src/js_collection.hpp @@ -23,6 +23,9 @@ #include "js_observable.hpp" #include "collection_notifications.hpp" +#if REALM_ENABLE_SYNC +#include "sync/subscription_state.hpp" +#endif namespace realm { namespace js { @@ -39,7 +42,7 @@ struct CollectionClass : ClassDefinition> { using Value = js::Value; std::string const name = "Collection"; - + static inline ValueType create_collection_change_set(ContextType ctx, const CollectionChangeSet &change_set); }; @@ -48,7 +51,7 @@ typename T::Value CollectionClass::create_collection_change_set(ContextType c { ObjectType object = Object::create_empty(ctx); std::vector deletions, insertions, modifications; - + if (change_set.deletions.count() == std::numeric_limits::max()) { deletions.push_back(Value::from_null(ctx)); } @@ -58,12 +61,12 @@ typename T::Value CollectionClass::create_collection_change_set(ContextType c } } Object::set_property(ctx, object, "deletions", Object::create_array(ctx, deletions)); - + for (auto index : change_set.insertions.as_indexes()) { insertions.push_back(Value::from_number(ctx, index)); } Object::set_property(ctx, object, "insertions", Object::create_array(ctx, insertions)); - + for (auto index : change_set.modifications.as_indexes()) { modifications.push_back(Value::from_number(ctx, index)); } diff --git a/src/js_realm.hpp b/src/js_realm.hpp index 5a434e64..961032a7 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -186,9 +186,6 @@ public: static void compact(ContextType, ObjectType, Arguments, ReturnValue &); static void delete_model(ContextType, ObjectType, Arguments, ReturnValue &); static void object_for_object_id(ContextType, ObjectType, Arguments, ReturnValue&); -#if REALM_ENABLE_SYNC - static void subscribe_to_objects(ContextType, ObjectType, Arguments, ReturnValue &); -#endif // properties static void get_empty(ContextType, ObjectType, ReturnValue &); @@ -248,7 +245,6 @@ public: {"_objectForObjectId", wrap}, #if REALM_ENABLE_SYNC {"_waitForDownload", wrap}, - {"_subscribeToObjects", wrap}, #endif }; @@ -1061,51 +1057,5 @@ void RealmClass::object_for_object_id(ContextType ctx, ObjectType this_object #endif // REALM_ENABLE_SYNC } -#if REALM_ENABLE_SYNC -template -void RealmClass::subscribe_to_objects(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) { - args.validate_count(3); - - SharedRealm realm = *get_internal>(this_object); - std::string object_type = Value::validated_to_string(ctx, args[0]); - std::string query = Value::validated_to_string(ctx, args[1]); - auto callback = Value::validated_to_function(ctx, args[2]); - - auto &schema = realm->schema(); - auto object_schema = schema.find(object_type); - - if (object_schema == schema.end()) { - throw std::runtime_error("Object type '" + object_type + "' not found in schema."); - } - - Protected protected_this(ctx, this_object); - Protected protected_ctx(Context::get_global_context(ctx)); - Protected protected_callback(ctx, callback); - auto cb = [=](realm::Results results, std::exception_ptr err) { - HANDLESCOPE - - if (err) { - try { - std::rethrow_exception(err); - } - catch (const std::exception& e) { - ValueType callback_arguments[2]; - callback_arguments[0] = Value::from_string(protected_ctx, e.what()); - callback_arguments[1] = Value::from_null(protected_ctx); - Function::callback(ctx, protected_callback, protected_this, 2, callback_arguments); - } - return; - } - - ValueType callback_arguments[2]; - callback_arguments[0] = Value::from_null(protected_ctx); - callback_arguments[1] = ResultsClass::create_instance(protected_ctx, results); - Function::callback(protected_ctx, protected_callback, protected_this, 2, callback_arguments); - }; - - partial_sync::register_query(realm, object_type, query, std::move(cb)); -} -#endif - } // js } // realm diff --git a/src/js_results.hpp b/src/js_results.hpp index f3694d08..8de32108 100644 --- a/src/js_results.hpp +++ b/src/js_results.hpp @@ -28,6 +28,11 @@ #include #include +#include +#ifdef REALM_ENABLE_SYNC +#include "js_sync.hpp" +#include "sync/partial_sync.hpp" +#endif namespace realm { namespace js { @@ -82,6 +87,9 @@ struct ResultsClass : ClassDefinition, CollectionClass< static void filtered(ContextType, ObjectType, Arguments, ReturnValue &); static void sorted(ContextType, ObjectType, Arguments, ReturnValue &); static void is_valid(ContextType, ObjectType, Arguments, ReturnValue &); +#if REALM_ENABLE_SYNC + static void subscribe(ContextType, ObjectType, Arguments, ReturnValue &); +#endif static void index_of(ContextType, ObjectType, Arguments, ReturnValue &); @@ -107,6 +115,9 @@ struct ResultsClass : ClassDefinition, CollectionClass< {"filtered", wrap}, {"sorted", wrap}, {"isValid", wrap}, +#if REALM_ENABLE_SYNC + {"subscribe", wrap}, +#endif {"min", wrap, AggregateFunc::Min>>}, {"max", wrap, AggregateFunc::Max>>}, {"sum", wrap, AggregateFunc::Sum>>}, @@ -254,6 +265,28 @@ void ResultsClass::is_valid(ContextType ctx, ObjectType this_object, Argument return_value.set(get_internal>(this_object)->is_valid()); } +#if REALM_ENABLE_SYNC +template +void ResultsClass::subscribe(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(1); + + auto results = get_internal>(this_object); + auto realm = results->get_realm(); + auto sync_config = realm->config().sync_config; + + util::Optional subscription_name; + if (args.count == 1) { + subscription_name = util::Optional(Value::validated_to_string(ctx, args[0])); + } + else { + subscription_name = util::none; + } + + auto subscription = partial_sync::subscribe(*results, subscription_name); + return_value.set(SubscriptionClass::create_instance(ctx, std::move(subscription))); +} +#endif + template template void ResultsClass::index_of(ContextType ctx, Fn& fn, Arguments args, ReturnValue &return_value) { @@ -326,13 +359,13 @@ void ResultsClass::add_listener(ContextType ctx, U& collection, ObjectType th Protected protected_ctx(Context::get_global_context(ctx)); auto token = collection.add_notification_callback([=](CollectionChangeSet const& change_set, std::exception_ptr exception) { - HANDLESCOPE - ValueType arguments[] { - static_cast(protected_this), - CollectionClass::create_collection_change_set(protected_ctx, change_set) - }; - Function::callback(protected_ctx, protected_callback, protected_this, 2, arguments); - }); + HANDLESCOPE + ValueType arguments[] { + static_cast(protected_this), + CollectionClass::create_collection_change_set(protected_ctx, change_set) + }; + Function::callback(protected_ctx, protected_callback, protected_this, 2, arguments); + }); collection.m_notification_tokens.emplace_back(protected_callback, std::move(token)); } diff --git a/src/js_sync.hpp b/src/js_sync.hpp index 238ab94c..b78f9cba 100644 --- a/src/js_sync.hpp +++ b/src/js_sync.hpp @@ -29,6 +29,7 @@ #include "sync/sync_config.hpp" #include "sync/sync_session.hpp" #include "sync/sync_user.hpp" +#include "sync/partial_sync.hpp" #include "realm/util/logger.hpp" #include "realm/util/uri.hpp" @@ -555,6 +556,127 @@ void SessionClass::override_server(ContextType ctx, ObjectType this_object, A } } +template +class Subscription : public partial_sync::Subscription { +public: + Subscription(partial_sync::Subscription s) : partial_sync::Subscription(std::move(s)) {} + Subscription(Subscription &&) = default; + + std::vector, partial_sync::SubscriptionNotificationToken>> m_notification_tokens; +}; + +template +class SubscriptionClass : public ClassDefinition> { + using GlobalContextType = typename T::GlobalContext; + using ContextType = typename T::Context; + using FunctionType = typename T::Function; + using ObjectType = typename T::Object; + using ValueType = typename T::Value; + using String = js::String; + using Object = js::Object; + using Value = js::Value; + using Function = js::Function; + using ReturnValue = js::ReturnValue; + using Arguments = js::Arguments; + +public: + std::string const name = "Subscription"; + + static FunctionType create_constructor(ContextType); + static ObjectType create_instance(ContextType, partial_sync::Subscription); + + static void get_state(ContextType, ObjectType, ReturnValue &); + static void get_error(ContextType, ObjectType, ReturnValue &); + + static void unsubscribe(ContextType, ObjectType, Arguments, ReturnValue &); + static void add_listener(ContextType, ObjectType, Arguments, ReturnValue &); + static void remove_listener(ContextType, ObjectType, Arguments, ReturnValue &); + + PropertyMap const properties = { + {"state", {wrap, nullptr}}, + {"error", {wrap, nullptr}} + }; + + MethodMap const methods = { + {"unsubscribe", wrap}, + {"addListener", wrap}, + {"removeListener", wrap}, + }; +}; + +template +typename T::Object SubscriptionClass::create_instance(ContextType ctx, partial_sync::Subscription subscription) { + return create_object>(ctx, new Subscription(std::move(subscription))); +} + +template +void SubscriptionClass::get_state(ContextType ctx, ObjectType object, ReturnValue &return_value) { + auto subscription = get_internal>(object); + return_value.set(static_cast(subscription->state())); +} + +template +void SubscriptionClass::get_error(ContextType ctx, ObjectType object, ReturnValue &return_value) { + auto subscription = get_internal>(object); + if (auto error = subscription->error()) { + try { + std::rethrow_exception(error); + } + catch (const std::exception& e) { + return_value.set(e.what()); + } + } + else { + return_value.set_undefined(); + } +} + +template +void SubscriptionClass::unsubscribe(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(0); + auto subscription = get_internal>(this_object); + partial_sync::unsubscribe(*subscription); + return_value.set_undefined(); +} + +template +void SubscriptionClass::add_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(1); + auto subscription = get_internal>(this_object); + + auto callback = Value::validated_to_function(ctx, args[0]); + Protected protected_callback(ctx, callback); + Protected protected_this(ctx, this_object); + Protected protected_ctx(Context::get_global_context(ctx)); + + auto token = subscription->add_notification_callback([=]() { + HANDLESCOPE + + ValueType arguments[2]; + arguments[0] = static_cast(protected_this), + arguments[1] = Value::from_number(ctx, static_cast(subscription->state())); + Function::callback(protected_ctx, protected_callback, protected_this, 2, arguments); + }); + + subscription->m_notification_tokens.emplace_back(protected_callback, std::move(token)); +} + +template +void SubscriptionClass::remove_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(1); + auto subscription = get_internal>(this_object); + + auto callback = Value::validated_to_function(ctx, args[0]); + auto protected_function = Protected(ctx, callback); + + auto& tokens = subscription->m_notification_tokens; + auto compare = [&](auto&& token) { + return typename Protected::Comparator()(token.first, protected_function); + }; + tokens.erase(std::remove_if(tokens.begin(), tokens.end(), compare), tokens.end()); +} + + template class SyncClass : public ClassDefinition { using GlobalContextType = typename T::GlobalContext; diff --git a/src/object-store b/src/object-store index 56f24683..d8324724 160000 --- a/src/object-store +++ b/src/object-store @@ -1 +1 @@ -Subproject commit 56f246831c6bd1bd33cf0f852d049b11e94e50e9 +Subproject commit d8324724eff33b768d02dba7cc268520ed335536 diff --git a/tests/js/index.js b/tests/js/index.js index 7aab4946..2e9e5670 100644 --- a/tests/js/index.js +++ b/tests/js/index.js @@ -55,7 +55,7 @@ if (global.enableSyncTests) { // FIXME: Permission tests currently fail in chrome debugging mode. if (typeof navigator === 'undefined' || !/Chrome/.test(navigator.userAgent)) { // eslint-disable-line no-undef - TESTS.PermissionTests = require('./permission-tests'); + TESTS.PermissionTests = require('./permission-tests'); } } diff --git a/tests/js/session-tests.js b/tests/js/session-tests.js index 90fc45ed..8fa54fc1 100644 --- a/tests/js/session-tests.js +++ b/tests/js/session-tests.js @@ -523,7 +523,7 @@ module.exports = { }); }, - testProgressNotificationsForRealmConstructor() { +/* testProgressNotificationsForRealmConstructor() { if (!isNodeProccess) { return; } @@ -555,7 +555,7 @@ module.exports = { }); }); }); - }, + },*/ testProgressNotificationsUnregisterForRealmConstructor() { if (!isNodeProccess) { @@ -707,8 +707,7 @@ module.exports = { }); }, - /* Disabled: waiting for new implementation - testPartialSync() { + testPartialSyncAnonymous_SubscriptionListener() { // FIXME: try to enable for React Native if (!isNodeProccess) { return; @@ -733,14 +732,205 @@ module.exports = { Realm.deleteFile(config); const realm = new Realm(config); TestCase.assertEqual(realm.objects('Dog').length, 0); - return realm.subscribeToObjects("Dog", "name == 'Lassy 1'").then(results => { - TestCase.assertEqual(results.length, 1); - TestCase.assertTrue(results[0].name === 'Lassy 1', "The object is not synced correctly"); + var results = realm.objects('Dog').filtered("name == 'Lassy 1'"); + var subscription = results.subscribe(); + TestCase.assertEqual(subscription.state, Realm.Sync.SubscriptionState.Creating); + return new Promise((resolve, reject) => { + subscription.addListener((subscription, state) => { + if (state == Realm.Sync.SubscriptionState.Complete) { + TestCase.assertEqual(results.length, 1); + TestCase.assertTrue(results[0].name === 'Lassy 1', "The object is not synced correctly"); + resolve(); + } + }); + setTimeout(function() { + reject("listener never called"); + }, 5000); }); }) }) }, - */ + + testPartialSyncAnonymous_ResultsListener() { + // FIXME: try to enable for React Native + if (!isNodeProccess) { + return; + } + + const username = uuid(); + const realmName = uuid(); + + return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) + .then(() => { + return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { + let config = { + sync: { + user: user, + url: `realm://localhost:9080/~/${realmName}`, + partial: true, + error: (session, error) => console.log(error) + }, + schema: [{ name: 'Dog', properties: { name: 'string' } }] + }; + + Realm.deleteFile(config); + const realm = new Realm(config); + TestCase.assertEqual(realm.objects('Dog').length, 0); + var results = realm.objects('Dog').filtered("name == 'Lassy 1'"); + var subscription = results.subscribe(); + TestCase.assertEqual(subscription.state, Realm.Sync.SubscriptionState.Creating); + return new Promise((resolve, reject) => { + results.addListener((collection, changes) => { + if (subscription.state === Realm.Sync.SubscriptionState.Complete) { + TestCase.assertEqual(collection.length, 1); + TestCase.assertTrue(collection[0].name === 'Lassy 1', "The object is not synced correctly"); + resolve(); + } + }); + setTimeout(function() { + reject("listener never called"); + }, 5000); + }); + }) + }) + }, + + testPartialSyncMultipleSubscriptions() { + // FIXME: try to enable for React Native + if (!isNodeProccess) { + return; + } + + const username = uuid(); + const realmName = uuid(); + + return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) + .then(() => { + return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { + let config = { + sync: { + user: user, + url: `realm://localhost:9080/~/${realmName}`, + partial: true, + error: (session, error) => console.log(error) + }, + schema: [{ name: 'Dog', properties: { name: 'string' } }] + }; + + Realm.deleteFile(config); + const realm = new Realm(config); + TestCase.assertEqual(realm.objects('Dog').length, 0); + var results1 = realm.objects('Dog').filtered("name == 'Lassy 1'"); + var results2 = realm.objects('Dog').filtered("name == 'Lassy 2'"); + var subscription1 = results1.subscribe(); + var subscription2 = results2.subscribe(); + + return new Promise((resolve, reject) => { + let called1 = false; + let called2 = false; + results1.addListener((collection, changeset) => { + if (subscription1.state == Realm.Sync.SubscriptionState.Complete) { + TestCase.assertEqual(collection.length, 1); + TestCase.assertTrue(collection[0].name === 'Lassy 1', "The object is not synced correctly"); + called1 = true; + if (called1 && called2) { + resolve(); + } + } + }); + results2.addListener((collection, changeset) => { + if (subscription2.state == Realm.Sync.SubscriptionState.Complete) { + TestCase.assertEqual(collection.length, 1); + TestCase.assertTrue(collection[0].name === 'Lassy 2', "The object is not synced correctly"); + called2 = true; + if (called1 && called2) { + resolve(); + } + } + }); + + setTimeout(function() { + reject("listener never called"); + }, 5000); + }); + }) + }) + }, + + testPartialSyncFailing() { + // FIXME: try to enable for React Native + if (!isNodeProccess) { + return; + } + + const username = uuid(); + const realmName = uuid(); + + return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) + .then(() => { + return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { + let config = { + sync: { + user: user, + url: `realm://localhost:9080/~/${realmName}`, + partial: false, // <---- calling subscribe should fail + error: (session, error) => console.log(error) + }, + schema: [{ name: 'Dog', properties: { name: 'string' } }] + }; + + Realm.deleteFile(config); + const realm = new Realm(config); + TestCase.assertEqual(realm.objects('Dog').length, 0); + TestCase.assertThrows(function () { var subscription = realm.objects('Dog').filtered("name == 'Lassy 1'").subscribe(); } ); + }); + }); + }, + + testPartialSyncUnsubscribe() { + // FIXME: try to enable for React Native + if (!isNodeProccess) { + return; + } + + const username = uuid(); + const realmName = uuid(); + + return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) + .then(() => { + return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { + let config = { + sync: { + user: user, + url: `realm://localhost:9080/~/${realmName}`, + partial: true, + error: (session, error) => console.log(error) + }, + schema: [{ name: 'Dog', properties: { name: 'string' } }] + }; + + Realm.deleteFile(config); + const realm = new Realm(config); + var results = realm.objects('Dog').filtered("name == 'Lassy 1'"); + var subscription = results.subscribe(); + TestCase.assertEqual(subscription.state, Realm.Sync.SubscriptionState.Creating); + return new Promise((resolve, reject) => { + results.addListener((collection, changes) => { + if (subscription.state === Realm.Sync.SubscriptionState.Complete) { + subscription.unsubscribe(); + } + if (subscription.state === Realm.Sync.SubscriptionState.Invalidated) { + resolve(); + } + }); + setTimeout(function() { + reject("listener never called"); + }, 5000); + }); + }); + }); + }, + testClientReset() { // FIXME: try to enable for React Native if (!isNodeProccess) { From a90e9ec45ffa78d7f8fd8e70f2823c127b358040 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 20 Feb 2018 14:43:36 +0100 Subject: [PATCH 05/10] Test of: Multiple list of primitive types (#1665) * Adding tests for nested lists * Updated to Realm Sync 2.2.12. --- CHANGELOG.md | 15 +++++++ dependencies.list | 2 +- tests/js/index.js | 2 +- tests/js/list-tests.js | 81 +++++++++++++++++++++++++++++++++- tests/js/nested-list-helper.js | 67 ++++++++++++++++++++++++++++ tests/js/schemas.js | 28 +++++++++++- tests/js/session-tests.js | 44 ++++++++++++++++++ 7 files changed, 235 insertions(+), 4 deletions(-) create mode 100644 tests/js/nested-list-helper.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b8522b5..a33a8c97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +X.Y.Z Release notes +============================================================= +### Breaking changes +* None. + +### Enhancements +* None. + +### Bug fixes +* [Object Server] A use-after-free bug was fixed which could cause arrays of primitives to behave unexpectedly. + +### Internal +* Updated to Realm Sync 2.2.12. + + 2.2.9 Release notes (2018-2-19) ============================================================= ### Breaking changes diff --git a/dependencies.list b/dependencies.list index 2c1f2364..7ebcc893 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js VERSION=2.2.9 REALM_CORE_VERSION=5.1.2 -REALM_SYNC_VERSION=2.2.11 +REALM_SYNC_VERSION=2.2.12 REALM_OBJECT_SERVER_VERSION=2.5.1 diff --git a/tests/js/index.js b/tests/js/index.js index 7aab4946..f7f52436 100644 --- a/tests/js/index.js +++ b/tests/js/index.js @@ -44,7 +44,7 @@ var TESTS = { MigrationTests: require('./migration-tests'), EncryptionTests: require('./encryption-tests'), ObjectIDTests: require('./object-id-tests'), - // GarbageCollectionTests: require('./garbage-collection'), + // Garbagecollectiontests: require('./garbage-collection'), }; // If sync is enabled, run the sync tests diff --git a/tests/js/list-tests.js b/tests/js/list-tests.js index 417cd5b6..72349803 100644 --- a/tests/js/list-tests.js +++ b/tests/js/list-tests.js @@ -37,7 +37,7 @@ module.exports = { TestCase.assertInstanceOf(obj.list, Realm.List); TestCase.assertInstanceOf(obj.list, Realm.Collection); }); - + TestCase.assertThrowsContaining(() => new Realm.List(), 'constructor'); TestCase.assertInstanceOf(Realm.List, Function); }, @@ -1223,4 +1223,83 @@ module.exports = { TestCase.assertThrowsContaining(() => object.list.avg(), "JS value must be of type 'string', got (undefined)"); }, + + testListNested: function() { + const realm = new Realm({schema: [schemas.ParentObject, schemas.NameObject]}); + realm.write(() => { + realm.create('ParentObject', { + id: 1, + name: [ + { family: 'Larsen', given: ['Hans', 'Jørgen'], prefix: [] }, + { family: 'Hansen', given: ['Ib'], prefix: [] } + ] + }); + realm.create('ParentObject', { + id: 2, + name: [ + {family: 'Petersen', given: ['Gurli', 'Margrete'], prefix: [] } + ] + }); + }); + + let objects = realm.objects('ParentObject'); + TestCase.assertEqual(objects.length, 2); + TestCase.assertEqual(objects[0].name.length, 2); + TestCase.assertEqual(objects[0].name[0].given.length, 2); + TestCase.assertEqual(objects[0].name[0].prefix.length, 0); + TestCase.assertEqual(objects[0].name[0].given[0], 'Hans'); + TestCase.assertEqual(objects[0].name[0].given[1], 'Jørgen') + TestCase.assertEqual(objects[0].name[1].given.length, 1); + TestCase.assertEqual(objects[0].name[1].given[0], 'Ib'); + TestCase.assertEqual(objects[0].name[1].prefix.length, 0); + + TestCase.assertEqual(objects[1].name.length, 1); + TestCase.assertEqual(objects[1].name[0].given.length, 2); + TestCase.assertEqual(objects[1].name[0].prefix.length, 0); + TestCase.assertEqual(objects[1].name[0].given[0], 'Gurli'); + TestCase.assertEqual(objects[1].name[0].given[1], 'Margrete'); + }, + + testListNestedFromJSON: function() { + let json = '{"id":1, "name": [{ "family": "Larsen", "given": ["Hans", "Jørgen"], "prefix": [] }, { "family": "Hansen", "given": ["Ib"], "prefix": [] }] }'; + let parent = JSON.parse(json); + const realm = new Realm({schema: [schemas.ParentObject, schemas.NameObject]}); + realm.write(() => { + realm.create('ParentObject', parent); + }); + + let objects = realm.objects('ParentObject'); + TestCase.assertEqual(objects.length, 1); + TestCase.assertEqual(objects[0].name.length, 2); + TestCase.assertEqual(objects[0].name[0].given.length, 2); + TestCase.assertEqual(objects[0].name[0].prefix.length, 0); + TestCase.assertEqual(objects[0].name[0].given[0], 'Hans'); + TestCase.assertEqual(objects[0].name[0].given[1], 'Jørgen'); + + TestCase.assertEqual(objects[0].name[1].given.length, 1); + TestCase.assertEqual(objects[0].name[1].prefix.length, 0); + TestCase.assertEqual(objects[0].name[1].given[0], 'Ib'); + }, + + testMultipleLists: function() { + const realm = new Realm({schema: [schemas.MultiListObject]}); + realm.write(() => { + realm.create('MultiListObject', { id: 0, list1: ["Hello"], list2: ["World"] }); + realm.create('MultiListObject', { id: 1, list1: ["Foo"], list2: ["Bar"] }); + }); + + let objects = realm.objects('MultiListObject'); + TestCase.assertEqual(objects.length, 2); + TestCase.assertEqual(objects[0].id, 0); + TestCase.assertEqual(objects[0].list1.length, 1); + TestCase.assertEqual(objects[0].list1[0], "Hello"); + TestCase.assertEqual(objects[0].list2.length, 1); + TestCase.assertEqual(objects[0].list2[0], "World"); + + TestCase.assertEqual(objects[1].id, 1); + TestCase.assertEqual(objects[1].list1.length, 1); + TestCase.assertEqual(objects[1].list1[0], "Foo"); + TestCase.assertEqual(objects[1].list2.length, 1); + TestCase.assertEqual(objects[1].list2[0], "Bar"); + } }; diff --git a/tests/js/nested-list-helper.js b/tests/js/nested-list-helper.js new file mode 100644 index 00000000..e4b0803c --- /dev/null +++ b/tests/js/nested-list-helper.js @@ -0,0 +1,67 @@ +/* +This script creates new nested objects into a new Realm. +*/ + +'use strict'; +console.log("nested-list-helper started"); +const username = process.argv[3]; +const realmName = process.argv[4]; +const realmModule = process.argv[5]; + +const Realm = require(realmModule); +let schemas = require(process.argv[2]); + +function createObjects(user) { + const config = { + sync: { user, + url: `realm://localhost:9080/~/${realmName}`, + error: err => console.log(err) + }, + schema: [schemas.ParentObject, schemas.NameObject], + }; + + const realm = new Realm(config); + + realm.write(() => { + realm.create('ParentObject', { + id: 1, + name: [ + { family: 'Larsen', given: ['Hans', 'Jørgen'], prefix: [] }, + { family: 'Hansen', given: ['Ib'], prefix: [] } + ] + }); + realm.create('ParentObject', { + id: 2, + name: [ + {family: 'Petersen', given: ['Gurli', 'Margrete'], prefix: [] } + ] + }); + }); + + console.log("JSON: " + JSON.stringify(realm.objects('ParentObject'))); + + let session = realm.syncSession; + return new Promise((resolve, reject) => { + let callback = (transferred, total) => { + if (transferred === total) { + session.removeProgressNotification(callback); + resolve(realm); + } + } + session.addProgressNotification('upload', 'forCurrentlyOutstandingWork', callback); + }); +} + +let registrationError; +Realm.Sync.User.register('http://localhost:9080', username, 'password') + .catch((error) => { + registrationError = JSON.stringify(error); + return Realm.Sync.User.login('http://localhost:9080', username, 'password') + }) + .catch((error) => { + const loginError = JSON.stringify(error); + console.error(`nested-list-helper failed:\n User.register() error:\n${registrationError}\n User.login() error:\n${registrationError}`); + process.exit(-2); + }) + .then((user) => createObjects(user)) + .then(() => process.exit(0)); diff --git a/tests/js/schemas.js b/tests/js/schemas.js index 6d9ecc75..8c153603 100644 --- a/tests/js/schemas.js +++ b/tests/js/schemas.js @@ -280,4 +280,30 @@ exports.LinkingObjectsObject = { links: 'LinkingObjectsObject[]', linkingObjects: {type: 'linkingObjects', objectType: 'LinkingObjectsObject', property: 'links'} } -} +}; + +exports.ParentObject = { + name: 'ParentObject', + properties: { + id: 'int', + name: 'NameObject[]' + } +}; + +exports.NameObject = { + name: 'NameObject', + properties: { + family: 'string', + given: 'string[]', + prefix: 'string[]' + } +}; + +exports.MultiListObject = { + name: 'MultiListObject', + properties: { + 'id': 'int', + 'list1': 'string[]', + 'list2': 'string[]' + } +}; diff --git a/tests/js/session-tests.js b/tests/js/session-tests.js index a127937c..cd0fc323 100644 --- a/tests/js/session-tests.js +++ b/tests/js/session-tests.js @@ -24,6 +24,7 @@ const Realm = require('realm'); const TestCase = require('./asserts'); +let schemas = require('./schemas'); const isNodeProccess = (typeof process === 'object' && process + '' === '[object process]'); @@ -402,6 +403,49 @@ module.exports = { }); }, + testListNestedSync() { + if (!isNodeProccess) { + return; + } + + const username = uuid(); + const realmName = uuid(); + + return runOutOfProcess(__dirname + '/nested-list-helper.js', __dirname + '/schemas.js', username, realmName, REALM_MODULE_PATH) + .then(() => { + return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { + return new Promise((resolve, reject) => { + let config = { + schema: [schemas.ParentObject, schemas.NameObject], + sync: { user, url: `realm://localhost:9080/~/${realmName}` } + }; + Realm.open(config).then(realm => { + let objects = realm.objects('ParentObject'); + + let json = JSON.stringify(objects); + TestCase.assertEqual(json, '{"0":{"id":1,"name":{"0":{"family":"Larsen","given":{"0":"Hans","1":"Jørgen"},"prefix":{}},"1":{"family":"Hansen","given":{"0":"Ib"},"prefix":{}}}},"1":{"id":2,"name":{"0":{"family":"Petersen","given":{"0":"Gurli","1":"Margrete"},"prefix":{}}}}}'); + TestCase.assertEqual(objects.length, 2); + TestCase.assertEqual(objects[0].name.length, 2); + TestCase.assertEqual(objects[0].name[0].given.length, 2); + TestCase.assertEqual(objects[0].name[0].prefix.length, 0); + TestCase.assertEqual(objects[0].name[0].given[0], 'Hans'); + TestCase.assertEqual(objects[0].name[0].given[1], 'Jørgen') + TestCase.assertEqual(objects[0].name[1].given.length, 1); + TestCase.assertEqual(objects[0].name[1].given[0], 'Ib'); + TestCase.assertEqual(objects[0].name[1].prefix.length, 0); + + TestCase.assertEqual(objects[1].name.length, 1); + TestCase.assertEqual(objects[1].name[0].given.length, 2); + TestCase.assertEqual(objects[1].name[0].prefix.length, 0); + TestCase.assertEqual(objects[1].name[0].given[0], 'Gurli'); + TestCase.assertEqual(objects[1].name[0].given[1], 'Margrete'); + resolve(); + }).catch(() => reject()); + }); + }); + }); + }, + testIncompatibleSyncedRealmOpen() { let realm = "sync-v1.realm"; if (isNodeProccess) { From 2b4bb9f76968cbffb9dbcd9a88e87138fac1338a Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 20 Feb 2018 14:50:43 +0100 Subject: [PATCH 06/10] [2.2.10] Bump version --- CHANGELOG.md | 2 +- dependencies.list | 2 +- package.json | 2 +- src/RealmJS.xcodeproj/project.pbxproj | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a33a8c97..700d4b44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -X.Y.Z Release notes +2.2.10 Release notes (2018-2-20) ============================================================= ### Breaking changes * None. diff --git a/dependencies.list b/dependencies.list index 7ebcc893..96b64f81 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.2.9 +VERSION=2.2.10 REALM_CORE_VERSION=5.1.2 REALM_SYNC_VERSION=2.2.12 REALM_OBJECT_SERVER_VERSION=2.5.1 diff --git a/package.json b/package.json index 1a48be60..58fcf169 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "realm", "description": "Realm is a mobile database: an alternative to SQLite and key-value stores", - "version": "2.2.9", + "version": "2.2.10", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ diff --git a/src/RealmJS.xcodeproj/project.pbxproj b/src/RealmJS.xcodeproj/project.pbxproj index 0d0ad915..36ad277e 100644 --- a/src/RealmJS.xcodeproj/project.pbxproj +++ b/src/RealmJS.xcodeproj/project.pbxproj @@ -961,7 +961,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2.2.9; + CURRENT_PROJECT_VERSION = 2.2.10; CXX = "$(SRCROOT)/../scripts/ccache-clang++.sh"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -1025,7 +1025,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2.2.9; + CURRENT_PROJECT_VERSION = 2.2.10; CXX = "$(SRCROOT)/../scripts/ccache-clang++.sh"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; From 9580997ba92b9641acfd39c0141c09e82395e703 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 23 Feb 2018 18:17:58 +0000 Subject: [PATCH 07/10] [2.3.0-alpha.12] Bump version --- dependencies.list | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dependencies.list b/dependencies.list index f68a8de1..fea3bec3 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.3.0-alpha.11 +VERSION=2.3.0-alpha.12 REALM_CORE_VERSION=5.2.0 -REALM_SYNC_VERSION=3.0.0-beta.6 +REALM_SYNC_VERSION=3.0.0-beta.8 REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.2 From e77ca6c11927b20593fe3c2f47a9a440e720b945 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 23 Feb 2018 18:23:48 +0000 Subject: [PATCH 08/10] updated package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 714e7346..65a77b90 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "realm", "description": "Realm is a mobile database: an alternative to SQLite and key-value stores", - "version": "2.3.0-alpha.11", + "version": "2.3.0-alpha.12", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ From 4122305ea3a755531c7687a67ca1b60a444fa67b Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 26 Feb 2018 17:33:50 +0000 Subject: [PATCH 09/10] [2.3.0-alpha.13] Bump version --- dependencies.list | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dependencies.list b/dependencies.list index fea3bec3..df28f8a4 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.3.0-alpha.12 +VERSION=2.3.0-alpha.13 REALM_CORE_VERSION=5.2.0 -REALM_SYNC_VERSION=3.0.0-beta.8 +REALM_SYNC_VERSION=3.0.0-beta.9 REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.2 diff --git a/package.json b/package.json index 65a77b90..b08bb4a2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "realm", "description": "Realm is a mobile database: an alternative to SQLite and key-value stores", - "version": "2.3.0-alpha.12", + "version": "2.3.0-alpha.13", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ From ec5ea3e32acfcb8f16623d7cc89bdd08787c94c3 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 27 Feb 2018 11:28:53 +0100 Subject: [PATCH 10/10] Remember to mention which sync version we are using. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c07b5d87..a23d782e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ ### Internal * Updated to Realm Core 5.2.0. -* Updated to Realm Sync 3.0.0-beta.5. +* Updated to Realm Sync 3.0.0-beta.9. * Tested against Realm Object Server 3.0.0-alpha.2.