Merge pull request #472 from realm/sk-primary-key-api

Implement objectForPrimaryKey() method
This commit is contained in:
Ari Lazier 2016-06-13 14:18:29 -07:00 committed by GitHub
commit 61e74c3059
8 changed files with 161 additions and 45 deletions

View File

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

View File

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

View File

@ -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]);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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