From cd0bb079b750d62600b26bc318284abd3be1990e Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Fri, 3 Jun 2016 16:43:36 -0700 Subject: [PATCH 1/3] Implement objectForPrimaryKey() method Resolves #328 --- lib/browser/index.js | 9 ++++ src/js_realm.hpp | 58 ++++++++++++++++++------ src/js_results.hpp | 15 ++---- src/object-store/src/object_accessor.hpp | 51 +++++++++++++++++---- tests/js/realm-tests.js | 37 +++++++++++++++ tests/js/schemas.js | 9 ++++ 6 files changed, 145 insertions(+), 34 deletions(-) diff --git a/lib/browser/index.js b/lib/browser/index.js index f14f22ef..b189a7e4 100644 --- a/lib/browser/index.js +++ b/lib/browser/index.js @@ -106,6 +106,15 @@ export default class Realm { let method = util.createMethod(objectTypes.REALM, 'objects'); return method.apply(this, [type, ...args]); } + + objectForPrimaryKey(type, ...args) { + if (typeof type == 'function') { + type = objects.typeForConstructor(this[keys.realm], type); + } + + let method = util.createMethod(objectTypes.REALM, 'objectForPrimaryKey'); + return method.apply(this, [type, ...args]); + } } // Non-mutating methods: diff --git a/src/js_realm.hpp b/src/js_realm.hpp index ae2d6b08..271f7fa4 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -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 const methods = { {"objects", wrap}, + {"objectForPrimaryKey", wrap}, {"create", wrap}, {"delete", wrap}, {"deleteAll", wrap}, @@ -207,19 +209,35 @@ public: } // 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(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 +480,25 @@ void RealmClass::objects(ContextType ctx, ObjectType this_object, size_t argc validate_argument_count(argc, 1); SharedRealm realm = *get_internal>(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::create_instance(ctx, realm, type)); + return_value.set(ResultsClass::create_instance(ctx, realm, object_schema)); +} + +template +void RealmClass::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>(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::create_instance(ctx, std::move(realm_object))); + } + else { + return_value.set_undefined(); + } } template @@ -472,17 +506,11 @@ void RealmClass::create(ContextType ctx, ObjectType this_object, size_t argc, validate_argument_count(argc, 2, 3); SharedRealm realm = *get_internal>(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::dict_for_property_array(ctx, *object_schema, object); + object = Schema::dict_for_property_array(ctx, object_schema, object); } bool update = false; @@ -490,7 +518,7 @@ void RealmClass::create(ContextType ctx, ObjectType this_object, size_t argc, update = Value::validated_to_boolean(ctx, arguments[2], "update"); } - auto realm_object = realm::Object::create(ctx, realm, *object_schema, object, update); + auto realm_object = realm::Object::create(ctx, realm, object_schema, object, update); return_value.set(RealmObjectClass::create_instance(ctx, std::move(realm_object))); } diff --git a/src/js_results.hpp b/src/js_results.hpp index 640d67fb..5ef44699 100644 --- a/src/js_results.hpp +++ b/src/js_results.hpp @@ -40,7 +40,7 @@ struct ResultsClass : ClassDefinition> { 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 @@ -87,16 +87,9 @@ typename T::Object ResultsClass::create_instance(ContextType ctx, const realm } template -typename T::Object ResultsClass::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::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>(ctx, results); diff --git a/src/object-store/src/object_accessor.hpp b/src/object-store/src/object_accessor.hpp index c20282b6..f57f42e7 100644 --- a/src/object-store/src/object_accessor.hpp +++ b/src/object-store/src/object_accessor.hpp @@ -45,6 +45,9 @@ namespace realm { template static inline Object create(ContextType ctx, SharedRealm realm, const ObjectSchema &object_schema, ValueType value, bool try_update); + template + 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 inline ValueType get_property_value_impl(ContextType ctx, const Property &property); + + template + 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 + 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 + inline size_t Object::get_for_primary_key_impl(ContextType ctx, const ConstTableRef &table, const Property &primary_prop, ValueType primary_value) { + using Accessor = NativeAccessor; + + 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, diff --git a/tests/js/realm-tests.js b/tests/js/realm-tests.js index 628adfde..ec426327 100644 --- a/tests/js/realm-tests.js +++ b/tests/js/realm-tests.js @@ -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; diff --git a/tests/js/schemas.js b/tests/js/schemas.js index 888ef44a..76482b31 100644 --- a/tests/js/schemas.js +++ b/tests/js/schemas.js @@ -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', From 3c657c3bbfd55a02ebe17799380f8f364c4cc723 Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Fri, 3 Jun 2016 16:59:50 -0700 Subject: [PATCH 2/3] Update docs and CHANGELOG with objectForPrimaryKey --- CHANGELOG.md | 1 + docs/realm.js | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42fd136d..4bc4b402 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/docs/realm.js b/docs/realm.js index ade8d8a8..780e3243 100644 --- a/docs/realm.js +++ b/docs/realm.js @@ -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. From cdcb99a502b8643f113f0818f4979b34b3e8d5f7 Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Thu, 9 Jun 2016 13:07:05 -0700 Subject: [PATCH 3/3] Improvements from PR feedback --- lib/browser/index.js | 25 ++++++++++--------------- src/js_realm.hpp | 1 - 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/lib/browser/index.js b/lib/browser/index.js index b189a7e4..add249a2 100644 --- a/lib/browser/index.js +++ b/lib/browser/index.js @@ -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,30 +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) { - if (typeof type == 'function') { - type = objects.typeForConstructor(this[keys.realm], type); - } - let method = util.createMethod(objectTypes.REALM, 'objectForPrimaryKey'); - return method.apply(this, [type, ...args]); + return method.apply(this, [getObjectType(this, type), ...args]); } } diff --git a/src/js_realm.hpp b/src/js_realm.hpp index 271f7fa4..c5f13233 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -208,7 +208,6 @@ public: return name; } - // converts constructor object or type name to type name static const ObjectSchema& validated_object_schema_for_value(ContextType ctx, const SharedRealm &realm, const ValueType &value) { std::string object_type;