Partial sync (#1361)
This commit is contained in:
parent
fe121ea27b
commit
feb59ae859
|
@ -6,6 +6,7 @@ X.Y.Z-rc Release notes
|
|||
### Enhancements
|
||||
* Support migration from Realms sync 1.0 to sync 2.0 versions
|
||||
* Handling of the situation when the client has to reset the Realm due to diverging histories (#795).
|
||||
* Added `Realm.subscribeToObjects()` to listen for changes in partially synced Realms.
|
||||
|
||||
### Bug fixes
|
||||
* None
|
||||
|
@ -16,6 +17,7 @@ X.Y.Z-rc Release notes
|
|||
* Upgtading to Realm Core 4.0.1 (bug fixes)
|
||||
* Upgrading to Realm Sync 2.0.0-rc26 (sync protocol 22 + bug fixes)
|
||||
|
||||
|
||||
2.0.0-rc14 Release notes (2017-9-29)
|
||||
=============================================================
|
||||
### Breaking changes
|
||||
|
|
|
@ -93,6 +93,7 @@
|
|||
"src/object-store/src/sync/sync_user.cpp",
|
||||
"src/object-store/src/sync/sync_session.cpp",
|
||||
"src/object-store/src/sync/sync_config.cpp",
|
||||
"src/object-store/src/sync/partial_sync.cpp",
|
||||
"src/object-store/src/sync/impl/sync_file.cpp",
|
||||
"src/object-store/src/sync/impl/sync_metadata.cpp"
|
||||
],
|
||||
|
|
|
@ -216,7 +216,7 @@ class Realm {
|
|||
*/
|
||||
cancelTransaction() {}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Replaces all string columns in this Realm with a string enumeration column and compacts the
|
||||
* database file.
|
||||
*
|
||||
|
@ -233,6 +233,17 @@ 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) {}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -333,7 +344,10 @@ Realm.defaultPath;
|
|||
* The purpose of open_ssl_verify_callback is to enable custom certificate handling and to solve cases where
|
||||
* OpenSSL erroneously rejects valid certificates possibly because OpenSSL doesn't have access to the
|
||||
* proper trust certificates.
|
||||
*
|
||||
* - `partial` - Whether this Realm should be opened in 'partial synchronization' mode.
|
||||
* Partial synchronisation only synchronizes those objects that match the query specified in contrast
|
||||
* to the normal mode of operation that synchronises all objects in a remote Realm.
|
||||
* **Partial synchronization is a tech preview. Its APIs are subject to change.**
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
|
@ -60,6 +60,7 @@ function setupRealm(realm, realmId) {
|
|||
'schemaVersion',
|
||||
'syncSession',
|
||||
'isInTransaction',
|
||||
'subscribeToObjects',
|
||||
].forEach((name) => {
|
||||
Object.defineProperty(realm, name, {get: util.getterForProperty(name)});
|
||||
});
|
||||
|
|
|
@ -141,6 +141,24 @@ module.exports = function(realmConstructor) {
|
|||
//enable deprecated setAccessToken
|
||||
realmConstructor.Sync.setAccessToken = realmConstructor.Sync.setFeatureToken;
|
||||
}
|
||||
|
||||
|
||||
// instance methods
|
||||
Object.defineProperties(realmConstructor.prototype, {
|
||||
subscribeToObjects: function(objectType, query) {
|
||||
const realm = this;
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
callback = function(results, err) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(results);
|
||||
}
|
||||
};
|
||||
realm._subscriibeToObjects(objectType, query, callback);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Remove this now useless object.
|
||||
|
|
|
@ -600,6 +600,11 @@ declare class Realm {
|
|||
* @returns boolean
|
||||
*/
|
||||
compact(): boolean;
|
||||
|
||||
/**
|
||||
* @returns Promise<Results<T>>
|
||||
*/
|
||||
subscribeToObjects<T>(objectType: string, query: string): Promise<Realm.Results<T>>;
|
||||
}
|
||||
|
||||
declare module 'realm' {
|
||||
|
|
|
@ -59,6 +59,7 @@ LOCAL_SRC_FILES += src/object-store/src/sync/sync_manager.cpp
|
|||
LOCAL_SRC_FILES += src/object-store/src/sync/sync_session.cpp
|
||||
LOCAL_SRC_FILES += src/object-store/src/sync/sync_user.cpp
|
||||
LOCAL_SRC_FILES += src/object-store/src/sync/sync_config.cpp
|
||||
LOCAL_SRC_FILES += src/object-store/src/sync/partial_sync.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
|
||||
endif
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
3FCE2A8B1F58BDEF00D4855B /* primitive_list_notifier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3FCE2A891F58BDE500D4855B /* primitive_list_notifier.cpp */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
|
@ -194,6 +195,8 @@
|
|||
3FCE2A951F58BE1D00D4855B /* sync_permission.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = sync_permission.cpp; path = src/sync/sync_permission.cpp; sourceTree = "<group>"; };
|
||||
3FCE2A981F58BE3600D4855B /* descriptor_ordering.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = descriptor_ordering.hpp; path = "object-store/src/descriptor_ordering.hpp"; sourceTree = SOURCE_ROOT; };
|
||||
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 = "<group>"; };
|
||||
423737B01F7E333400FAEDFF /* partial_sync.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = partial_sync.hpp; path = src/sync/partial_sync.hpp; sourceTree = "<group>"; };
|
||||
426FCDFF1F7DA2F9005565DC /* sync_config.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = sync_config.cpp; path = src/sync/sync_config.cpp; sourceTree = "<group>"; };
|
||||
502B07E31E2CD1FA007A84ED /* object.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = object.cpp; path = src/object.cpp; sourceTree = "<group>"; };
|
||||
502B07E51E2CD20D007A84ED /* object.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = object.hpp; path = src/object.hpp; sourceTree = "<group>"; };
|
||||
|
@ -464,6 +467,8 @@
|
|||
02E315CC1DB80DE000555337 /* sync */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
423737AF1F7E333400FAEDFF /* partial_sync.cpp */,
|
||||
423737B01F7E333400FAEDFF /* partial_sync.hpp */,
|
||||
426FCDFF1F7DA2F9005565DC /* sync_config.cpp */,
|
||||
504CF8521EBCAE3600A9A4B6 /* impl */,
|
||||
02E315CD1DB80DF200555337 /* sync_client.hpp */,
|
||||
|
@ -936,6 +941,7 @@
|
|||
5D97DC4E1F7DAB1400B856A4 /* sync_config.cpp in Sources */,
|
||||
504CF8601EBCAE3600A9A4B6 /* sync_file.cpp in Sources */,
|
||||
02E315D21DB80DF200555337 /* sync_file.cpp in Sources */,
|
||||
420FB79F1F7FBFE900D43D0F /* partial_sync.cpp in Sources */,
|
||||
02E315C91DB80DDD00555337 /* sync_manager.cpp in Sources */,
|
||||
504CF8611EBCAE3600A9A4B6 /* sync_metadata.cpp in Sources */,
|
||||
02E315D31DB80DF200555337 /* sync_metadata.cpp in Sources */,
|
||||
|
|
|
@ -51,6 +51,12 @@ struct Arguments {
|
|||
throw std::invalid_argument(util::format("Invalid arguments: at most %1 expected, but %2 supplied.", max, count));
|
||||
}
|
||||
}
|
||||
|
||||
void validate_count(size_t actual) const {
|
||||
if (count != actual) {
|
||||
throw std::invalid_argument(util::format("Invalid arguments: %1 expected, but %s supplied.", actual, count));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
|
@ -60,7 +66,7 @@ template<typename T>
|
|||
struct PropertyType {
|
||||
using GetterType = void(typename T::Context, typename T::Object, ReturnValue<T> &);
|
||||
using SetterType = void(typename T::Context, typename T::Object, typename T::Value);
|
||||
|
||||
|
||||
typename T::PropertyGetterCallback getter;
|
||||
typename T::PropertySetterCallback setter;
|
||||
};
|
||||
|
@ -95,7 +101,7 @@ template<typename T, typename U, typename V = void>
|
|||
struct ClassDefinition {
|
||||
using Internal = U;
|
||||
using Parent = V;
|
||||
|
||||
|
||||
// Every subclass *must* at least have a name.
|
||||
// std::string const name;
|
||||
|
||||
|
|
|
@ -34,12 +34,14 @@
|
|||
#include "js_sync.hpp"
|
||||
#include "sync/sync_config.hpp"
|
||||
#include "sync/sync_manager.hpp"
|
||||
#include "sync/partial_sync.hpp"
|
||||
#endif
|
||||
|
||||
#include "shared_realm.hpp"
|
||||
#include "binding_context.hpp"
|
||||
#include "object_accessor.hpp"
|
||||
#include "platform.hpp"
|
||||
#include "results.hpp"
|
||||
|
||||
namespace realm {
|
||||
namespace js {
|
||||
|
@ -156,7 +158,7 @@ class RealmClass : public ClassDefinition<T, SharedRealm, ObservableClass<T>> {
|
|||
public:
|
||||
using ObjectDefaultsMap = typename Schema<T>::ObjectDefaultsMap;
|
||||
using ConstructorMap = typename Schema<T>::ConstructorMap;
|
||||
|
||||
|
||||
using WaitHandler = void(std::error_code);
|
||||
using ProgressHandler = void(uint64_t transferred_bytes, uint64_t transferrable_bytes);
|
||||
|
||||
|
@ -180,6 +182,9 @@ public:
|
|||
static void close(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void compact(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void delete_model(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 &);
|
||||
|
@ -236,6 +241,9 @@ public:
|
|||
{"close", wrap<close>},
|
||||
{"compact", wrap<compact>},
|
||||
{"deleteModel", wrap<delete_model>},
|
||||
#if REALM_ENABLE_SYNC
|
||||
{"_subscribeToObjects", wrap<subscribe_to_objects>},
|
||||
#endif
|
||||
};
|
||||
|
||||
PropertyMap<T> const properties = {
|
||||
|
@ -401,7 +409,7 @@ void RealmClass<T>::constructor(ContextType ctx, ObjectType this_object, size_t
|
|||
else if (config.path.empty()) {
|
||||
config.path = js::default_path();
|
||||
}
|
||||
|
||||
|
||||
static const String in_memory_string = "inMemory";
|
||||
ValueType in_memory_value = Object::get_property(ctx, object, in_memory_string);
|
||||
if (!Value::is_undefined(ctx, in_memory_value) && Value::validated_to_boolean(ctx, in_memory_value, "inMemory")) {
|
||||
|
@ -691,14 +699,14 @@ void RealmClass<T>::wait_for_download_completion(ContextType ctx, ObjectType thi
|
|||
auto encryption_key = Value::validated_to_binary(ctx, encryption_key_value, "encryptionKey");
|
||||
config.encryption_key.assign(encryption_key.data(), encryption_key.data() + encryption_key.size());
|
||||
}
|
||||
|
||||
|
||||
Protected<ObjectType> thiz(ctx, this_object);
|
||||
SyncClass<T>::populate_sync_config(ctx, thiz, config_object, config);
|
||||
|
||||
Protected<FunctionType> protected_callback(ctx, callback_function);
|
||||
Protected<ObjectType> protected_this(ctx, this_object);
|
||||
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
|
||||
|
||||
|
||||
EventLoopDispatcher<WaitHandler> wait_handler([=](std::error_code error_code) {
|
||||
HANDLESCOPE
|
||||
if (!error_code) {
|
||||
|
@ -718,7 +726,7 @@ void RealmClass<T>::wait_for_download_completion(ContextType ctx, ObjectType thi
|
|||
});
|
||||
std::function<WaitHandler> waitFunc = std::move(wait_handler);
|
||||
|
||||
std::function<ProgressHandler> progressFunc;
|
||||
std::function<ProgressHandler> progressFunc;
|
||||
|
||||
SharedRealm realm;
|
||||
try {
|
||||
|
@ -771,8 +779,8 @@ void RealmClass<T>::wait_for_download_completion(ContextType ctx, ObjectType thi
|
|||
|
||||
if (progressFuncDefined) {
|
||||
session->register_progress_notifier(std::move(progressFunc), SyncSession::NotifierType::download, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
session->wait_for_download_completion([=](std::error_code error_code) {
|
||||
realm->close(); //capture and keep realm instance for until here
|
||||
waitFunc(error_code);
|
||||
|
@ -1016,5 +1024,41 @@ void RealmClass<T>::compact(ContextType ctx, ObjectType this_object, Arguments a
|
|||
return_value.set(realm->compact());
|
||||
}
|
||||
|
||||
#if REALM_ENABLE_SYNC
|
||||
template<typename T>
|
||||
void RealmClass<T>::subscribe_to_objects(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_count(3);
|
||||
|
||||
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
|
||||
auto class_name = Value::validated_to_string(ctx, args[0]);
|
||||
auto query = Value::validated_to_string(ctx, args[1]);
|
||||
auto callback = Value::validated_to_function(ctx, args[2]);
|
||||
|
||||
Protected<ObjectType> protected_this(ctx, this_object);
|
||||
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
|
||||
auto cb = [=](realm::Results results, std::exception_ptr err) {
|
||||
if (err) {
|
||||
try {
|
||||
rethrow_exception(err);
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
typename T::Value arguments[2];
|
||||
arguments[0] = Value::from_null(protected_ctx);
|
||||
arguments[1] = Value::from_string(protected_ctx, e.what());
|
||||
Function<T>::callback(ctx, callback, protected_this, 2, arguments);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
typename T::Value arguments[2];
|
||||
arguments[0] = ResultsClass<T>::create_instance(protected_ctx, results);
|
||||
arguments[1] = Value::from_null(protected_ctx);
|
||||
Function<T>::callback(protected_ctx, callback, protected_this, 2, arguments);
|
||||
};
|
||||
|
||||
partial_sync::register_query(realm, class_name, query, std::move(cb));
|
||||
}
|
||||
#endif
|
||||
|
||||
} // js
|
||||
} // realm
|
||||
|
|
|
@ -653,14 +653,20 @@ void SyncClass<T>::populate_sync_config(ContextType ctx, ObjectType realm_constr
|
|||
ssl_verify_callback = std::move(ssl_verify_functor);
|
||||
}
|
||||
|
||||
bool is_partial = false;
|
||||
ValueType partial_value = Object::get_property(ctx, sync_config_object, "partial");
|
||||
if (!Value::is_undefined(ctx, partial_value)) {
|
||||
is_partial = Value::validated_to_boolean(ctx, partial_value);
|
||||
}
|
||||
|
||||
// FIXME - use make_shared
|
||||
config.sync_config = std::shared_ptr<SyncConfig>(new SyncConfig{shared_user, raw_realm_url,
|
||||
SyncSessionStopPolicy::AfterChangesUploaded,
|
||||
std::move(bind), std::move(error_handler),
|
||||
nullptr, util::none,
|
||||
client_validate_ssl, ssl_trust_certificate_path,
|
||||
std::move(ssl_verify_callback)});
|
||||
|
||||
std::move(ssl_verify_callback),
|
||||
is_partial});
|
||||
|
||||
|
||||
config.schema_mode = SchemaMode::Additive;
|
||||
|
|
|
@ -493,7 +493,7 @@ module.exports = {
|
|||
};
|
||||
|
||||
Realm.open(config)
|
||||
.then(realm =>
|
||||
.then(realm =>
|
||||
_reject("Should fail with IncompatibleSyncedRealmError"))
|
||||
.catch(e => {
|
||||
if (e.name == "IncompatibleSyncedRealmError") {
|
||||
|
@ -502,7 +502,7 @@ module.exports = {
|
|||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
function printObject(o) {
|
||||
var out = '';
|
||||
for (var p in o) {
|
||||
|
@ -781,6 +781,79 @@ module.exports = {
|
|||
});
|
||||
},
|
||||
|
||||
testPartialSync() {
|
||||
// FIXME: try to enable for React Native
|
||||
if (!isNodeProccess) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const username = uuid();
|
||||
const realmName = uuid();
|
||||
|
||||
return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
|
||||
.then(() => {
|
||||
Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user1 => {
|
||||
TestCase.assertDefined(user1, 'user1');
|
||||
let config1 = {
|
||||
sync: {
|
||||
user: user1,
|
||||
url: `realm://localhost:9080/~/${realmName}`,
|
||||
},
|
||||
schema: [{ name: 'Integer', properties: { value: 'int' } }],
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
return Realm.open(config1)
|
||||
.then(realm1 => {
|
||||
for(let i = 0; i < 10; i++) {
|
||||
realm1.write(() => {
|
||||
realm1.create('Integer', {value: i});
|
||||
});
|
||||
}
|
||||
|
||||
const progressCallback = (transferred, total) => {
|
||||
if (transferred === total) {
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
realm.syncSession.addProgressNotification('upload', 'reportIndefinitely', progressCallback);
|
||||
})
|
||||
.then(() => {
|
||||
realm.close();
|
||||
realm.deleteFile(config1);
|
||||
user1.logout();
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
.then(() => {
|
||||
Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user2 => {
|
||||
TestCase.assertDefined(user2, 'user2');
|
||||
return new Promise((resolve, reject) => {
|
||||
let config2 = {
|
||||
sync: {
|
||||
user: user2,
|
||||
url: `realm://localhost:9080/~/${realmName}`,
|
||||
partial: true,
|
||||
},
|
||||
schema: [{ name: 'Integer', properties: { value: 'int' } }],
|
||||
};
|
||||
|
||||
const realm2 = new Realm(config2);
|
||||
realm2.subscribeToObjects('Integer', 'value > 5').then((results, error) => {
|
||||
return results;
|
||||
}).then((results) => {
|
||||
TestCase.assertEqual(results.length, 4);
|
||||
for(obj in results) {
|
||||
TestCase.assertTrue(obj.value > 5, '<= 5');
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
reject();
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
testClientReset() {
|
||||
// FIXME: try to enable for React Native
|
||||
if (!isNodeProccess) {
|
||||
|
|
Loading…
Reference in New Issue