Partial sync (#1361)

This commit is contained in:
Kenneth Geisshirt 2017-10-02 20:29:36 +02:00 committed by GitHub
parent fe121ea27b
commit feb59ae859
12 changed files with 192 additions and 15 deletions

View File

@ -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

View File

@ -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"
],

View File

@ -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.**
*/
/**

View File

@ -60,6 +60,7 @@ function setupRealm(realm, realmId) {
'schemaVersion',
'syncSession',
'isInTransaction',
'subscribeToObjects',
].forEach((name) => {
Object.defineProperty(realm, name, {get: util.getterForProperty(name)});
});

View File

@ -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.

5
lib/index.d.ts vendored
View File

@ -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' {

View File

@ -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

View File

@ -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 */,

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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) {