diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e76a245..f25727de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * Support relative paths when opening realms * Support case insensitive queries * Support for indexed bool, string, and int properties +* Added method to retrieve schemaVersion from an unopened Realm ### Bugfixes * Fix for crash on Android when initializing the Realm module diff --git a/docs/realm.js b/docs/realm.js index 8c20a1aa..7267bb19 100644 --- a/docs/realm.js +++ b/docs/realm.js @@ -103,6 +103,17 @@ class Realm { write(callback) {} } +/** + * Get the current schema version of the Realm at the given path. + * @param {string} path - The path to the file where the + * Realm database is stored. + * @param {ArrayBuffer|ArrayBufferView} [encryptionKey] - Required only when + * accessing encrypted Realms. + * @throws {Error} When passing an invalid or non-matching encryption key. + * @returns {number} version of the schema, or `-1` if no Realm exists at `path`. + */ +Realm.schemaVersion = function(path, encryptionKey) {}; + /** * The default path where to create and access the Realm file. * @type {string} diff --git a/lib/browser/index.js b/lib/browser/index.js index 27eb76c2..418d1863 100644 --- a/lib/browser/index.js +++ b/lib/browser/index.js @@ -172,6 +172,11 @@ Object.defineProperties(Realm, { get: util.getterForProperty('defaultPath'), set: util.setterForProperty('defaultPath'), }, + schemaVersion: { + value: function(path, encryptionKey) { + return rpc.callMethod(undefined, Realm[keys.id], 'schemaVersion', Array.from(arguments)); + } + }, clearTestState: { value: function() { util.clearMutationListeners(); diff --git a/src/js_realm.cpp b/src/js_realm.cpp index 412fb829..0bca0d23 100644 --- a/src/js_realm.cpp +++ b/src/js_realm.cpp @@ -149,6 +149,13 @@ static bool SetDefaultPath(JSContextRef ctx, JSObjectRef object, JSStringRef pro return true; } +inline std::string RJSNormalizePath(std::string path) { + if (path.size() && path[0] != '/') { + return default_realm_file_directory() + "/" + path; + } + return path; +} + JSObjectRef RealmConstructor(JSContextRef ctx, JSObjectRef constructor, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { try { Realm::Config config; @@ -202,9 +209,7 @@ JSObjectRef RealmConstructor(JSContextRef ctx, JSObjectRef constructor, size_t a return NULL; } - if (config.path.size() && config.path[0] != '/') { - config.path = default_realm_file_directory() + "/" + config.path; - } + config.path = RJSNormalizePath(config.path); ensure_directory_exists_for_file(config.path); SharedRealm realm = Realm::get_shared_realm(config); @@ -232,6 +237,39 @@ static const JSStaticValue RealmStaticProperties[] = { {NULL, NULL} }; +JSValueRef RealmSchemaVersion(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { + try { + RJSValidateArgumentRange(argumentCount, 1, 2); + + Realm::Config config; + config.path = RJSNormalizePath(RJSValidatedStringForValue(ctx, arguments[0])); + if (argumentCount == 2) { + JSValueRef encryptionKeyValue = arguments[1]; + std::string encryptionKey = RJSAccessor::to_binary(ctx, encryptionKeyValue); + config.encryption_key = std::vector(encryptionKey.begin(), encryptionKey.end()); + } + + auto version = Realm::get_schema_version(config); + if (version == ObjectStore::NotVersioned) { + return JSValueMakeNumber(ctx, -1); + } + else { + return JSValueMakeNumber(ctx, version); + } + } + catch (std::exception &exp) { + if (jsException) { + *jsException = RJSMakeError(ctx, exp); + } + } + return NULL; +} + +static const JSStaticFunction RealmConstructorFuncs[] = { + {"schemaVersion", RealmSchemaVersion, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, + {NULL, NULL}, +}; + JSClassRef RJSRealmConstructorClass() { JSClassDefinition realmConstructorDefinition = kJSClassDefinitionEmpty; realmConstructorDefinition.attributes = kJSClassAttributeNoAutomaticPrototype; @@ -239,6 +277,7 @@ JSClassRef RJSRealmConstructorClass() { realmConstructorDefinition.callAsConstructor = RealmConstructor; realmConstructorDefinition.hasInstance = RealmHasInstance; realmConstructorDefinition.staticValues = RealmStaticProperties; + realmConstructorDefinition.staticFunctions = RealmConstructorFuncs; return JSClassCreate(&realmConstructorDefinition); } diff --git a/tests/js/realm-tests.js b/tests/js/realm-tests.js index 779c1e15..465ec0c5 100644 --- a/tests/js/realm-tests.js +++ b/tests/js/realm-tests.js @@ -117,6 +117,27 @@ module.exports = BaseTest.extend({ TestCase.assertEqual(Realm.defaultPath, newPath, "defaultPath should have been updated"); }, + testRealmSchemaVersion: function() { + TestCase.assertEqual(Realm.schemaVersion(Realm.defaultPath), -1); + + var realm = new Realm({schema: []}); + TestCase.assertEqual(Realm.schemaVersion(Realm.defaultPath), 0); + + realm = new Realm({schema: [], schemaVersion: 2, path: 'another.realm'}); + TestCase.assertEqual(Realm.schemaVersion('another.realm'), 2); + + var encryptionKey = new Int8Array(64); + realm = new Realm({schema: [], schemaVersion: 3, path: 'encrypted.realm', encryptionKey: encryptionKey}); + TestCase.assertEqual(Realm.schemaVersion('encrypted.realm', encryptionKey), 3); + + TestCase.assertThrows(function() { + Realm.schemaVersion('encrypted.realm', encryptionKey, 'extra'); + }); + TestCase.assertThrows(function() { + Realm.schemaVersion('encrypted.realm', 'asdf'); + }); + }, + testRealmCreate: function() { var realm = new Realm({schema: [schemas.IntPrimary, schemas.AllTypes, schemas.TestObject]});