mirror of
https://github.com/status-im/realm-js.git
synced 2025-02-22 19:28:33 +00:00
Expose an object's internal object ID, and allow fetching an object by its object ID (#1460)
* Expose an object's internal object ID, and allow fetching an object by its object ID * Throw an exception if methods related to object IDs are used on non-synced Realms. * Use `std::stoull` to ensure we can return the entire range of possible values. * Add tests for _objectId() / _objectForObjectId(). * Adding change log * Skip ObjectIdTests.testSynced for non-Node.
This commit is contained in:
parent
837e8d90a3
commit
848a5b1c09
15
CHANGELOG.md
15
CHANGELOG.md
@ -1,3 +1,18 @@
|
||||
X.Y.Z Release notes
|
||||
=============================================================
|
||||
### Breaking changes
|
||||
* None.
|
||||
|
||||
### Enchancements
|
||||
* None.
|
||||
|
||||
### Bug fixes
|
||||
* None.
|
||||
|
||||
### Internal
|
||||
* Added support for object IDs.
|
||||
|
||||
|
||||
2.0.4 Release notes (2017-11-7)
|
||||
=============================================================
|
||||
### Breaking changes
|
||||
|
@ -129,6 +129,7 @@ util.createMethods(Realm.prototype, objectTypes.REALM, [
|
||||
'removeAllListeners',
|
||||
'close',
|
||||
'_waitForDownload',
|
||||
'_objectForObjectId',
|
||||
]);
|
||||
|
||||
// Mutating methods:
|
||||
|
@ -31,7 +31,9 @@ export default class RealmObject {
|
||||
createMethods(RealmObject.prototype, objectTypes.OBJECT, [
|
||||
'isValid',
|
||||
'objectSchema',
|
||||
'linkingObjects'
|
||||
'linkingObjects',
|
||||
'_objectId',
|
||||
'_isSameObject',
|
||||
]);
|
||||
|
||||
export function clearRegisteredConstructors() {
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cctype>
|
||||
#include <list>
|
||||
#include <map>
|
||||
|
||||
@ -182,6 +183,7 @@ public:
|
||||
static void close(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
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
|
||||
@ -241,6 +243,7 @@ public:
|
||||
{"compact", wrap<compact>},
|
||||
{"deleteModel", wrap<delete_model>},
|
||||
{"_waitForDownload", wrap<wait_for_download_completion>},
|
||||
{"_objectForObjectId", wrap<object_for_object_id>},
|
||||
#if REALM_ENABLE_SYNC
|
||||
{"_subscribeToObjects", wrap<subscribe_to_objects>},
|
||||
#endif
|
||||
@ -986,6 +989,62 @@ void RealmClass<T>::compact(ContextType ctx, ObjectType this_object, Arguments a
|
||||
return_value.set(realm->compact());
|
||||
}
|
||||
|
||||
#if REALM_ENABLE_SYNC
|
||||
namespace {
|
||||
|
||||
// FIXME: Sync should provide this: https://github.com/realm/realm-sync/issues/1796
|
||||
inline sync::ObjectID object_id_from_string(std::string const& string)
|
||||
{
|
||||
if (string.front() != '{' || string.back() != '}')
|
||||
throw std::invalid_argument("Invalid object ID.");
|
||||
|
||||
size_t dash_index = string.find('-');
|
||||
if (dash_index == std::string::npos)
|
||||
throw std::invalid_argument("Invalid object ID.");
|
||||
|
||||
std::string high_string = string.substr(1, dash_index - 1);
|
||||
std::string low_string = string.substr(dash_index + 1, string.size() - dash_index - 2);
|
||||
|
||||
if (high_string.size() == 0 || high_string.size() > 16 || low_string.size() == 0 || low_string.size() > 16)
|
||||
throw std::invalid_argument("Invalid object ID.");
|
||||
|
||||
auto isxdigit = static_cast<int(*)(int)>(std::isxdigit);
|
||||
if (!std::all_of(high_string.begin(), high_string.end(), isxdigit) ||
|
||||
!std::all_of(low_string.begin(), low_string.end(), isxdigit)) {
|
||||
throw std::invalid_argument("Invalid object ID.");
|
||||
}
|
||||
|
||||
return sync::ObjectID(std::stoull(high_string, nullptr, 16), std::stoull(low_string, nullptr, 16));
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
#endif // REALM_ENABLE_SYNC
|
||||
|
||||
template<typename T>
|
||||
void RealmClass<T>::object_for_object_id(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue& return_value) {
|
||||
args.validate_count(2);
|
||||
|
||||
#if REALM_ENABLE_SYNC
|
||||
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
|
||||
if (!sync::has_object_ids(realm->read_group()))
|
||||
throw std::logic_error("Realm._objectForObjectId() can only be used with synced Realms.");
|
||||
|
||||
std::string object_type = Value::validated_to_string(ctx, args[0]);
|
||||
validated_object_schema_for_value(ctx, realm, args[0], object_type);
|
||||
|
||||
std::string object_id_string = Value::validated_to_string(ctx, args[1]);
|
||||
auto object_id = object_id_from_string(object_id_string);
|
||||
|
||||
const Group& group = realm->read_group();
|
||||
size_t ndx = sync::row_for_object_id(group, *ObjectStore::table_for_object_type(group, object_type), object_id);
|
||||
if (ndx != realm::npos) {
|
||||
return_value.set(RealmObjectClass<T>::create_instance(ctx, realm::Object(realm, object_type, ndx)));
|
||||
}
|
||||
#else
|
||||
throw std::logic_error("Realm._objectForObjectId() can only be used with synced Realms.");
|
||||
#endif // REALM_ENABLE_SYNC
|
||||
}
|
||||
|
||||
#if REALM_ENABLE_SYNC
|
||||
template<typename T>
|
||||
void RealmClass<T>::subscribe_to_objects(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
|
@ -43,6 +43,7 @@ struct RealmObjectClass : ClassDefinition<T, realm::Object> {
|
||||
using Object = js::Object<T>;
|
||||
using Function = js::Function<T>;
|
||||
using ReturnValue = js::ReturnValue<T>;
|
||||
using Arguments = js::Arguments<T>;
|
||||
|
||||
static ObjectType create_instance(ContextType, realm::Object);
|
||||
|
||||
@ -53,6 +54,8 @@ struct RealmObjectClass : ClassDefinition<T, realm::Object> {
|
||||
static void is_valid(ContextType, FunctionType, ObjectType, size_t, const ValueType [], ReturnValue &);
|
||||
static void get_object_schema(ContextType, FunctionType, ObjectType, size_t, const ValueType [], ReturnValue &);
|
||||
static void linking_objects(ContextType, FunctionType, ObjectType, size_t, const ValueType [], ReturnValue &);
|
||||
static void get_object_id(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void is_same_object(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
|
||||
const std::string name = "RealmObject";
|
||||
|
||||
@ -66,6 +69,8 @@ struct RealmObjectClass : ClassDefinition<T, realm::Object> {
|
||||
{"isValid", wrap<is_valid>},
|
||||
{"objectSchema", wrap<get_object_schema>},
|
||||
{"linkingObjects", wrap<linking_objects>},
|
||||
{"_objectId", wrap<get_object_id>},
|
||||
{"_isSameObject", wrap<is_same_object>},
|
||||
};
|
||||
};
|
||||
|
||||
@ -152,6 +157,50 @@ std::vector<String<T>> RealmObjectClass<T>::get_property_names(ContextType ctx,
|
||||
return names;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void RealmObjectClass<T>::get_object_id(ContextType ctx, ObjectType object, Arguments args, ReturnValue& return_value) {
|
||||
args.validate_maximum(0);
|
||||
|
||||
#if REALM_ENABLE_SYNC
|
||||
auto realm_object = get_internal<T, RealmObjectClass<T>>(object);
|
||||
const Group& group = realm_object->realm()->read_group();
|
||||
if (!sync::has_object_ids(group))
|
||||
throw std::logic_error("_objectId() can only be used with objects from synced Realms.");
|
||||
|
||||
const Row& row = realm_object->row();
|
||||
auto object_id = sync::object_id_for_row(group, *row.get_table(), row.get_index());
|
||||
return_value.set(object_id.to_string());
|
||||
#else
|
||||
throw std::logic_error("_objectId() can only be used with objects from synced Realms.");
|
||||
#endif
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void RealmObjectClass<T>::is_same_object(ContextType ctx, ObjectType object, Arguments args, ReturnValue& return_value) {
|
||||
args.validate_count(1);
|
||||
|
||||
ObjectType otherObject = Value::validated_to_object(ctx, args[0]);
|
||||
if (!Object::template is_instance<RealmObjectClass<T>>(ctx, otherObject)) {
|
||||
return_value.set(false);
|
||||
return;
|
||||
}
|
||||
|
||||
auto self = get_internal<T, RealmObjectClass<T>>(object);
|
||||
auto other = get_internal<T, RealmObjectClass<T>>(otherObject);
|
||||
|
||||
if (!self->realm() || self->realm() != other->realm()) {
|
||||
return_value.set(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self->is_valid() || !other->is_valid()) {
|
||||
return_value.set(false);
|
||||
return;
|
||||
}
|
||||
|
||||
return_value.set(self->row().get_table() == other->row().get_table()
|
||||
&& self->row().get_index() == other->row().get_index());
|
||||
}
|
||||
} // js
|
||||
} // realm
|
||||
|
||||
|
@ -42,6 +42,7 @@ var TESTS = {
|
||||
QueryTests: require('./query-tests'),
|
||||
MigrationTests: require('./migration-tests'),
|
||||
EncryptionTests: require('./encryption-tests'),
|
||||
ObjectIDTests: require('./object-id-tests'),
|
||||
// GarbageCollectionTests: require('./garbage-collection'),
|
||||
};
|
||||
|
||||
|
84
tests/js/object-id-tests.js
Normal file
84
tests/js/object-id-tests.js
Normal file
@ -0,0 +1,84 @@
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright 2017 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.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/* eslint-env es6, node */
|
||||
|
||||
'use strict';
|
||||
|
||||
const Realm = require('realm');
|
||||
const TestCase = require('./asserts');
|
||||
|
||||
const isNodeProccess = (typeof process === 'object' && process + '' === '[object process]');
|
||||
|
||||
function uuid() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
testNonSynced: function() {
|
||||
let realm = new Realm({schema: [{ name: 'Dog', properties: { name: 'string' } }]});
|
||||
var dog;
|
||||
realm.write(() => {
|
||||
dog = realm.create('Dog', ['Fido']);
|
||||
});
|
||||
TestCase.assertThrowsContaining(() => dog._objectId(), "_objectId() can only be used with objects from synced Realms");
|
||||
TestCase.assertThrowsContaining(() => realm._objectForObjectId('Dog', 'foo'), "Realm._objectForObjectId() can only be used with synced Realms");
|
||||
},
|
||||
|
||||
testSynced: function() {
|
||||
if (!global.enableSyncTests || !isNodeProccess)
|
||||
return;
|
||||
|
||||
return Realm.Sync.User.register('http://localhost:9080', uuid(), 'password').then(user => {
|
||||
const config = { sync: { user, url: 'realm://localhost:9080/~/myrealm' },
|
||||
schema: [{ name: 'IntegerPrimaryKey', properties: { int: 'int?' }, primaryKey: 'int' },
|
||||
{ name: 'StringPrimaryKey', properties: { string: 'string?' }, primaryKey: 'string' },
|
||||
{ name: 'NoPrimaryKey', properties: { string: 'string' }},
|
||||
],
|
||||
};
|
||||
return Realm.open(config).then(realm => {
|
||||
var integer, nullInteger;
|
||||
var string, nullString;
|
||||
var none;
|
||||
realm.write(() => {
|
||||
integer = realm.create('IntegerPrimaryKey', [12345]);
|
||||
nullInteger = realm.create('IntegerPrimaryKey', [null]);
|
||||
string = realm.create('StringPrimaryKey', ["hello, world"]);
|
||||
nullString = realm.create('StringPrimaryKey', [null]);
|
||||
none = realm.create('NoPrimaryKey', ["hello, world"]);
|
||||
});
|
||||
|
||||
let integerId = integer._objectId();
|
||||
let nullIntegerId = nullInteger._objectId();
|
||||
let stringId = string._objectId();
|
||||
let nullStringId = nullString._objectId();
|
||||
let noneId = none._objectId();
|
||||
|
||||
TestCase.assertTrue(integer._isSameObject(realm._objectForObjectId('IntegerPrimaryKey', integerId)));
|
||||
TestCase.assertTrue(nullInteger._isSameObject(realm._objectForObjectId('IntegerPrimaryKey', nullIntegerId)));
|
||||
TestCase.assertTrue(string._isSameObject(realm._objectForObjectId('StringPrimaryKey', stringId)));
|
||||
TestCase.assertTrue(nullString._isSameObject(realm._objectForObjectId('StringPrimaryKey', nullStringId)));
|
||||
TestCase.assertTrue(none._isSameObject(realm._objectForObjectId('NoPrimaryKey', noneId)));
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user