mirror of
https://github.com/status-im/realm-js.git
synced 2025-01-11 06:46:03 +00:00
Merge pull request #472 from realm/sk-primary-key-api
Implement objectForPrimaryKey() method
This commit is contained in:
commit
61e74c3059
@ -5,6 +5,7 @@ x.x.x Release notes (yyyy-MM-dd)
|
||||
|
||||
### Enhancements
|
||||
* Added `isValid()` method to `List` and `Results` to check for deleleted or invalidated objects
|
||||
* Added `objectForPrimaryKey(type, key)` method to `Realm`
|
||||
|
||||
### Bugfixes
|
||||
* None
|
||||
|
@ -106,6 +106,16 @@ class Realm {
|
||||
*/
|
||||
objects(type) {}
|
||||
|
||||
/**
|
||||
* Searches for a Realm object by its primary key.
|
||||
* @param {Realm~ObjectType} type - The type of Realm object to search for.
|
||||
* @param {number|string} key - The primary key value of the object to search for.
|
||||
* @throws {Error} If type passed into this method is invalid or if the object type did
|
||||
* not have a `primaryKey` specified in its {@link Realm~ObjectSchema ObjectSchema}.
|
||||
* @returns {Realm.Object|undefined} if no object is found.
|
||||
*/
|
||||
objectForPrimaryKey(type, key) {}
|
||||
|
||||
/**
|
||||
* Add a listener `callback` for the specified event `name`.
|
||||
* @param {string} name - The name of event that should cause the callback to be called.
|
||||
|
@ -56,6 +56,13 @@ function setupRealm(realm, realmId) {
|
||||
});
|
||||
}
|
||||
|
||||
function getObjectType(realm, type) {
|
||||
if (typeof type == 'function') {
|
||||
return objects.typeForConstructor(realm[keys.realm], type);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
export default class Realm {
|
||||
constructor(config) {
|
||||
let schemas = typeof config == 'object' && config.schema;
|
||||
@ -90,21 +97,18 @@ export default class Realm {
|
||||
}
|
||||
|
||||
create(type, ...args) {
|
||||
if (typeof type == 'function') {
|
||||
type = objects.typeForConstructor(this[keys.realm], type);
|
||||
}
|
||||
|
||||
let method = util.createMethod(objectTypes.REALM, 'create', true);
|
||||
return method.apply(this, [type, ...args]);
|
||||
return method.apply(this, [getObjectType(this, type), ...args]);
|
||||
}
|
||||
|
||||
objects(type, ...args) {
|
||||
if (typeof type == 'function') {
|
||||
type = objects.typeForConstructor(this[keys.realm], type);
|
||||
}
|
||||
|
||||
let method = util.createMethod(objectTypes.REALM, 'objects');
|
||||
return method.apply(this, [type, ...args]);
|
||||
return method.apply(this, [getObjectType(this, type), ...args]);
|
||||
}
|
||||
|
||||
objectForPrimaryKey(type, ...args) {
|
||||
let method = util.createMethod(objectTypes.REALM, 'objectForPrimaryKey');
|
||||
return method.apply(this, [getObjectType(this, type), ...args]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,6 +141,7 @@ public:
|
||||
|
||||
// methods
|
||||
static void objects(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
|
||||
static void object_for_primary_key(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
|
||||
static void create(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
|
||||
static void delete_one(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
|
||||
static void delete_all(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
|
||||
@ -180,6 +181,7 @@ public:
|
||||
|
||||
MethodMap<T> const methods = {
|
||||
{"objects", wrap<objects>},
|
||||
{"objectForPrimaryKey", wrap<object_for_primary_key>},
|
||||
{"create", wrap<create>},
|
||||
{"delete", wrap<delete_one>},
|
||||
{"deleteAll", wrap<delete_all>},
|
||||
@ -206,20 +208,35 @@ public:
|
||||
return name;
|
||||
}
|
||||
|
||||
// converts constructor object or type name to type name
|
||||
static std::string validated_object_type_for_value(SharedRealm &realm, ContextType ctx, const ValueType &value) {
|
||||
static const ObjectSchema& validated_object_schema_for_value(ContextType ctx, const SharedRealm &realm, const ValueType &value) {
|
||||
std::string object_type;
|
||||
|
||||
if (Value::is_constructor(ctx, value)) {
|
||||
FunctionType constructor = Value::to_constructor(ctx, value);
|
||||
|
||||
auto delegate = get_delegate<T>(realm.get());
|
||||
for (auto &pair : delegate->m_constructors) {
|
||||
if (FunctionType(pair.second) == constructor) {
|
||||
return pair.first;
|
||||
object_type = pair.first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("Constructor was not registered in the schema for this Realm");
|
||||
|
||||
if (object_type.empty()) {
|
||||
throw std::runtime_error("Constructor was not registered in the schema for this Realm");
|
||||
}
|
||||
}
|
||||
return Value::validated_to_string(ctx, value, "objectType");
|
||||
else {
|
||||
object_type = Value::validated_to_string(ctx, value, "objectType");
|
||||
}
|
||||
|
||||
auto &schema = realm->config().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.");
|
||||
}
|
||||
return *object_schema;
|
||||
}
|
||||
|
||||
static std::string normalize_path(std::string path) {
|
||||
@ -462,9 +479,25 @@ void RealmClass<T>::objects(ContextType ctx, ObjectType this_object, size_t argc
|
||||
validate_argument_count(argc, 1);
|
||||
|
||||
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
|
||||
std::string type = validated_object_type_for_value(realm, ctx, arguments[0]);
|
||||
auto &object_schema = validated_object_schema_for_value(ctx, realm, arguments[0]);
|
||||
|
||||
return_value.set(ResultsClass<T>::create_instance(ctx, realm, type));
|
||||
return_value.set(ResultsClass<T>::create_instance(ctx, realm, object_schema));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void RealmClass<T>::object_for_primary_key(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
validate_argument_count(argc, 2);
|
||||
|
||||
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
|
||||
auto &object_schema = validated_object_schema_for_value(ctx, realm, arguments[0]);
|
||||
auto realm_object = realm::Object::get_for_primary_key(ctx, realm, object_schema, arguments[1]);
|
||||
|
||||
if (realm_object.is_valid()) {
|
||||
return_value.set(RealmObjectClass<T>::create_instance(ctx, std::move(realm_object)));
|
||||
}
|
||||
else {
|
||||
return_value.set_undefined();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
@ -472,17 +505,11 @@ void RealmClass<T>::create(ContextType ctx, ObjectType this_object, size_t argc,
|
||||
validate_argument_count(argc, 2, 3);
|
||||
|
||||
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
|
||||
std::string className = validated_object_type_for_value(realm, ctx, arguments[0]);
|
||||
auto &schema = realm->config().schema;
|
||||
auto object_schema = schema->find(className);
|
||||
|
||||
if (object_schema == schema->end()) {
|
||||
throw std::runtime_error("Object type '" + className + "' not found in schema.");
|
||||
}
|
||||
auto &object_schema = validated_object_schema_for_value(ctx, realm, arguments[0]);
|
||||
|
||||
ObjectType object = Value::validated_to_object(ctx, arguments[1], "properties");
|
||||
if (Value::is_array(ctx, arguments[1])) {
|
||||
object = Schema<T>::dict_for_property_array(ctx, *object_schema, object);
|
||||
object = Schema<T>::dict_for_property_array(ctx, object_schema, object);
|
||||
}
|
||||
|
||||
bool update = false;
|
||||
@ -490,7 +517,7 @@ void RealmClass<T>::create(ContextType ctx, ObjectType this_object, size_t argc,
|
||||
update = Value::validated_to_boolean(ctx, arguments[2], "update");
|
||||
}
|
||||
|
||||
auto realm_object = realm::Object::create<ValueType>(ctx, realm, *object_schema, object, update);
|
||||
auto realm_object = realm::Object::create<ValueType>(ctx, realm, object_schema, object, update);
|
||||
return_value.set(RealmObjectClass<T>::create_instance(ctx, std::move(realm_object)));
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ struct ResultsClass : ClassDefinition<T, realm::Results, CollectionClass<T>> {
|
||||
|
||||
static ObjectType create_instance(ContextType, const realm::Results &, bool live = true);
|
||||
static ObjectType create_instance(ContextType, const realm::List &, bool live = true);
|
||||
static ObjectType create_instance(ContextType, SharedRealm, const std::string &type, bool live = true);
|
||||
static ObjectType create_instance(ContextType, SharedRealm, const ObjectSchema &, bool live = true);
|
||||
static ObjectType create_instance(ContextType, SharedRealm, const ObjectSchema &, Query, bool live = true);
|
||||
|
||||
template<typename U>
|
||||
@ -87,16 +87,9 @@ typename T::Object ResultsClass<T>::create_instance(ContextType ctx, const realm
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
typename T::Object ResultsClass<T>::create_instance(ContextType ctx, SharedRealm realm, const std::string &type, bool live) {
|
||||
auto table = ObjectStore::table_for_object_type(realm->read_group(), type);
|
||||
auto &schema = realm->config().schema;
|
||||
auto object_schema = schema->find(type);
|
||||
|
||||
if (object_schema == schema->end()) {
|
||||
throw std::runtime_error("Object type '" + type + "' not present in Realm.");
|
||||
}
|
||||
|
||||
auto results = new realm::Results(realm, *object_schema, *table);
|
||||
typename T::Object ResultsClass<T>::create_instance(ContextType ctx, SharedRealm realm, const ObjectSchema &object_schema, bool live) {
|
||||
auto table = ObjectStore::table_for_object_type(realm->read_group(), object_schema.name);
|
||||
auto results = new realm::Results(realm, object_schema, *table);
|
||||
results->set_live(live);
|
||||
|
||||
return create_object<T, ResultsClass<T>>(ctx, results);
|
||||
|
@ -45,6 +45,9 @@ namespace realm {
|
||||
template<typename ValueType, typename ContextType>
|
||||
static inline Object create(ContextType ctx, SharedRealm realm, const ObjectSchema &object_schema, ValueType value, bool try_update);
|
||||
|
||||
template<typename ValueType, typename ContextType>
|
||||
static Object get_for_primary_key(ContextType ctx, SharedRealm realm, const ObjectSchema &object_schema, ValueType primary_value);
|
||||
|
||||
SharedRealm realm() { return m_realm; }
|
||||
const ObjectSchema &get_object_schema() { return *m_object_schema; }
|
||||
Row row() { return m_row; }
|
||||
@ -60,6 +63,9 @@ namespace realm {
|
||||
inline void set_property_value_impl(ContextType ctx, const Property &property, ValueType value, bool try_update);
|
||||
template<typename ValueType, typename ContextType>
|
||||
inline ValueType get_property_value_impl(ContextType ctx, const Property &property);
|
||||
|
||||
template<typename ValueType, typename ContextType>
|
||||
static size_t get_for_primary_key_impl(ContextType ctx, const ConstTableRef &table, const Property &primary_prop, ValueType primary_value);
|
||||
|
||||
inline void verify_attached();
|
||||
};
|
||||
@ -137,6 +143,13 @@ namespace realm {
|
||||
const std::string property_name;
|
||||
};
|
||||
|
||||
class MissingPrimaryKeyException : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
MissingPrimaryKeyException(const std::string object_type, const std::string message) : std::runtime_error(message), object_type(object_type) {}
|
||||
const std::string object_type;
|
||||
};
|
||||
|
||||
class MutationOutsideTransactionException : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
@ -301,16 +314,11 @@ namespace realm {
|
||||
size_t row_index = realm::not_found;
|
||||
realm::TableRef table = ObjectStore::table_for_object_type(realm->read_group(), object_schema.name);
|
||||
const Property *primary_prop = object_schema.primary_key_property();
|
||||
|
||||
if (primary_prop) {
|
||||
// search for existing object based on primary key type
|
||||
ValueType primary_value = Accessor::dict_value_for_key(ctx, value, object_schema.primary_key);
|
||||
if (primary_prop->type == PropertyType::String) {
|
||||
auto primary_string = Accessor::to_string(ctx, primary_value);
|
||||
row_index = table->find_first_string(primary_prop->table_column, primary_string);
|
||||
}
|
||||
else {
|
||||
row_index = table->find_first_int(primary_prop->table_column, Accessor::to_long(ctx, primary_value));
|
||||
}
|
||||
row_index = get_for_primary_key_impl(ctx, table, *primary_prop, primary_value);
|
||||
|
||||
if (!try_update && row_index != realm::not_found) {
|
||||
throw DuplicatePrimaryKeyValueException(object_schema.name, *primary_prop,
|
||||
@ -348,7 +356,34 @@ namespace realm {
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
|
||||
template<typename ValueType, typename ContextType>
|
||||
inline Object Object::get_for_primary_key(ContextType ctx, SharedRealm realm, const ObjectSchema &object_schema, ValueType primary_value)
|
||||
{
|
||||
auto primary_prop = object_schema.primary_key_property();
|
||||
if (!primary_prop) {
|
||||
throw MissingPrimaryKeyException(object_schema.name, object_schema.name + " does not have a primary key");
|
||||
}
|
||||
|
||||
auto table = ObjectStore::table_for_object_type(realm->read_group(), object_schema.name);
|
||||
auto row_index = get_for_primary_key_impl(ctx, table, *primary_prop, primary_value);
|
||||
|
||||
return Object(realm, object_schema, row_index == realm::not_found ? Row() : table->get(row_index));
|
||||
}
|
||||
|
||||
template<typename ValueType, typename ContextType>
|
||||
inline size_t Object::get_for_primary_key_impl(ContextType ctx, const ConstTableRef &table, const Property &primary_prop, ValueType primary_value) {
|
||||
using Accessor = NativeAccessor<ValueType, ContextType>;
|
||||
|
||||
if (primary_prop.type == PropertyType::String) {
|
||||
auto primary_string = Accessor::to_string(ctx, primary_value);
|
||||
return table->find_first_string(primary_prop.table_column, primary_string);
|
||||
}
|
||||
else {
|
||||
return table->find_first_int(primary_prop.table_column, Accessor::to_long(ctx, primary_value));
|
||||
}
|
||||
}
|
||||
|
||||
inline void Object::verify_attached() {
|
||||
if (!m_row.is_attached()) {
|
||||
throw InvalidatedObjectException(m_object_schema->name,
|
||||
|
@ -638,6 +638,43 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
|
||||
testRealmObjectForPrimaryKey: function() {
|
||||
var realm = new Realm({schema: [schemas.IntPrimary, schemas.StringPrimary, schemas.TestObject]});
|
||||
|
||||
realm.write(function() {
|
||||
realm.create('IntPrimaryObject', {primaryCol: 0, valueCol: 'val0'});
|
||||
realm.create('IntPrimaryObject', {primaryCol: 1, valueCol: 'val1'});
|
||||
|
||||
realm.create('StringPrimaryObject', {primaryCol: '', valueCol: -1});
|
||||
realm.create('StringPrimaryObject', {primaryCol: 'val0', valueCol: 0});
|
||||
realm.create('StringPrimaryObject', {primaryCol: 'val1', valueCol: 1});
|
||||
|
||||
realm.create('TestObject', {doubleCol: 0});
|
||||
});
|
||||
|
||||
TestCase.assertEqual(realm.objectForPrimaryKey('IntPrimaryObject', -1), undefined);
|
||||
TestCase.assertEqual(realm.objectForPrimaryKey('IntPrimaryObject', 0).valueCol, 'val0');
|
||||
TestCase.assertEqual(realm.objectForPrimaryKey('IntPrimaryObject', 1).valueCol, 'val1');
|
||||
|
||||
TestCase.assertEqual(realm.objectForPrimaryKey('StringPrimaryObject', 'invalid'), undefined);
|
||||
TestCase.assertEqual(realm.objectForPrimaryKey('StringPrimaryObject', '').valueCol, -1);
|
||||
TestCase.assertEqual(realm.objectForPrimaryKey('StringPrimaryObject', 'val0').valueCol, 0);
|
||||
TestCase.assertEqual(realm.objectForPrimaryKey('StringPrimaryObject', 'val1').valueCol, 1);
|
||||
|
||||
TestCase.assertThrows(function() {
|
||||
realm.objectForPrimaryKey('TestObject', 0);
|
||||
});
|
||||
TestCase.assertThrows(function() {
|
||||
realm.objectForPrimaryKey();
|
||||
});
|
||||
TestCase.assertThrows(function() {
|
||||
realm.objectForPrimaryKey('IntPrimary');
|
||||
});
|
||||
TestCase.assertThrows(function() {
|
||||
realm.objectForPrimaryKey('InvalidClass', 0);
|
||||
});
|
||||
},
|
||||
|
||||
testNotifications: function() {
|
||||
var realm = new Realm({schema: []});
|
||||
var notificationCount = 0;
|
||||
|
@ -106,6 +106,15 @@ exports.IntPrimary = {
|
||||
}
|
||||
};
|
||||
|
||||
exports.StringPrimary = {
|
||||
name: 'StringPrimaryObject',
|
||||
primaryKey: 'primaryCol',
|
||||
properties: {
|
||||
primaryCol: Realm.Types.STRING,
|
||||
valueCol: Realm.Types.INT,
|
||||
}
|
||||
};
|
||||
|
||||
exports.AllTypes = {
|
||||
name: 'AllTypesObject',
|
||||
primaryKey: 'primaryCol',
|
||||
|
Loading…
x
Reference in New Issue
Block a user