Add support for bulk updates (#808)

This commit is contained in:
Ashwin Phatak 2017-10-10 16:25:47 +05:30 committed by GitHub
parent 957a6dd292
commit 4bcef8baff
6 changed files with 288 additions and 1 deletions

View File

@ -1,3 +1,14 @@
X.Y.Z Release notes
=============================================================
### Breaking changes
* None
### Enhancements
* Added `update` method to `Realm.Results` to support bulk updates (#808).
### Bug fixes
* None
2.0.0-rc19 Release notes (2017-10-7)
=============================================================
### Breaking changes

View File

@ -28,4 +28,12 @@
* @memberof Realm
*/
class Results extends Collection {
/**
* Bulk update objects in the collection.
* @param {string} property - The name of the property.
* @param {string} value - The updated property value.
* @throws {Error} If no property with the name exists.
* @since 2.0.0-rc20
*/
update(property, value) {}
}

View File

@ -25,6 +25,7 @@ import { createMethods } from './util';
export default class Results extends Collection {
}
// Non-mutating methods:
createMethods(Results.prototype, objectTypes.RESULTS, [
'filtered',
'sorted',
@ -40,6 +41,11 @@ createMethods(Results.prototype, objectTypes.RESULTS, [
'removeAllListeners',
]);
// Mutating methods:
createMethods(Results.prototype, objectTypes.RESULTS, [
'update',
], true);
export function createResults(realmId, info) {
return createCollection(Results.prototype, realmId, info);
}

8
lib/index.d.ts vendored
View File

@ -250,7 +250,13 @@ declare namespace Realm {
* @see { @link https://realm.io/docs/javascript/latest/api/Realm.Results.html }
*/
interface Results<T> extends Collection<T> {
/**
* Bulk update objects in the collection.
* @param {string} property
* @param {any} value
* @returns void
*/
update(property: string, value: any): void;
}
const Results: {

View File

@ -87,6 +87,8 @@ struct ResultsClass : ClassDefinition<T, realm::js::Results<T>, CollectionClass<
template<typename Fn>
static void index_of(ContextType, Fn&, Arguments, ReturnValue &);
static void update(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
// aggregate functions
static void min(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void max(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
@ -118,6 +120,7 @@ struct ResultsClass : ClassDefinition<T, realm::js::Results<T>, CollectionClass<
{"removeListener", wrap<remove_listener>},
{"removeAllListeners", wrap<remove_all_listeners>},
{"indexOf", wrap<index_of>},
{"update", wrap<update>},
};
PropertyMap<T> const properties = {
@ -298,6 +301,32 @@ void ResultsClass<T>::index_of(ContextType ctx, Fn& fn, Arguments args, ReturnVa
}
}
template<typename T>
void ResultsClass<T>::update(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 2);
std::string property = Value::validated_to_string(ctx, arguments[0], "property");
auto results = get_internal<T, ResultsClass<T>>(this_object);
auto schema = results->get_object_schema();
if (!schema.property_for_name(StringData(property))) {
throw std::invalid_argument(util::format("No such property: %1", property));
}
auto realm = results->get_realm();
if (!realm->is_in_transaction()) {
throw std::runtime_error("Can only 'update' objects within a transaction.");
}
// TODO: This approach just moves the for-loop from JS to C++
// Ideally, we'd implement this in OS or Core in an optimized fashion
for (auto i = results->size(); i > 0; i--) {
auto realm_object = realm::Object(realm, schema, results->get(i - 1));
auto obj = RealmObjectClass<T>::create_instance(ctx, realm_object);
RealmObjectClass<T>::set_property(ctx, obj, property, arguments[1]);
}
}
template<typename T>
void ResultsClass<T>::index_of(ContextType ctx, ObjectType this_object,
Arguments args, ReturnValue &return_value) {

View File

@ -626,4 +626,231 @@ module.exports = {
TestCase.assertEqual(results.length, 0);
TestCase.assertEqual(calls, 2);
},
testResultsUpdate: function() {
const N = 5;
var realm = new Realm({schema: [schemas.NullableBasicTypes]});
realm.write(() => {
for(var i = 0; i < N; i++) {
realm.create('NullableBasicTypesObject', { intCol: 10 });
}
});
// update should work on a basic result set
var results = realm.objects('NullableBasicTypesObject');
TestCase.assertEqual(results.length, 5);
realm.write(() => {
results.update('intCol', 20);
});
TestCase.assertEqual(results.length, 5);
TestCase.assertEqual(realm.objects('NullableBasicTypesObject').filtered('intCol = 20').length, 5);
// update should work on a filtered result set
results = realm.objects('NullableBasicTypesObject').filtered('intCol = 20');
realm.write(() => {
results.update('intCol', 10);
});
TestCase.assertEqual(results.length, 0);
TestCase.assertEqual(realm.objects('NullableBasicTypesObject').filtered('intCol = 10').length, 5);
// update should work on a sorted result set
results = realm.objects('NullableBasicTypesObject').filtered('intCol == 10').sorted('intCol');
realm.write(() => {
results.update('intCol', 20);
});
TestCase.assertEqual(results.length, 0);
TestCase.assertEqual(realm.objects('NullableBasicTypesObject').filtered('intCol = 20').length, 5);
// update should work on a result snapshot
results = realm.objects('NullableBasicTypesObject').filtered('intCol == 20').snapshot();
realm.write(() => {
results.update('intCol', 10);
});
TestCase.assertEqual(results.length, 5); // snapshot length should not change
TestCase.assertEqual(realm.objects('NullableBasicTypesObject').filtered('intCol = 10').length, 5);
realm.close();
},
testResultsUpdateDataTypes: function() {
const N = 5;
var realm = new Realm({schema: [schemas.NullableBasicTypes]});
realm.write(() => {
for(var i = 0; i < N; i++) {
realm.create('NullableBasicTypesObject', {
boolCol: false,
stringCol: 'hello',
intCol: 10,
floatCol: 10.0,
doubleCol: 10.0,
dateCol: new Date(10)
});
}
});
const testCases = [
// col name, initial filter, initial filter pre-update count, initial filter post-update count, updated value, updated filter, updated filter post-update count
[ 'boolCol', 'boolCol = false', N, 0, true, 'boolCol = true', N ],
[ 'stringCol', 'stringCol = "hello"', N, 0, 'world', 'stringCol = "world"', N ],
[ 'intCol', 'intCol = 10', N, 0, 20, 'intCol = 20', N ],
[ 'floatCol', 'floatCol = 10.0', N, 0, 20.0, 'floatCol = 20.0', N ],
[ 'doubleCol', 'doubleCol = 10.0', N, 0, 20.0, 'doubleCol = 20.0', N ],
];
testCases.forEach(function(testCase) {
var results = realm.objects('NullableBasicTypesObject').filtered(testCase[1]);
TestCase.assertEqual(results.length, testCase[2]);
realm.write(() => {
results.update(testCase[0], testCase[4]);
});
TestCase.assertEqual(results.length, testCase[3]);
results = realm.objects('NullableBasicTypesObject').filtered(testCase[5]);
TestCase.assertEqual(results.length, testCase[6]);
});
realm.close();
},
testResultUpdateDateColumn: function() {
const N = 5;
var realm = new Realm({schema: [schemas.NullableBasicTypes]});
// date column
realm.write(() => {
for(var i = 0; i < N; i++) {
realm.create('NullableBasicTypesObject', {
dateCol: new Date(1000)
});
}
});
var results = realm.objects('NullableBasicTypesObject').filtered('dateCol = $0', new Date(1000));
TestCase.assertEqual(results.length, N);
realm.write(() => {
results.update('dateCol', new Date(2000));
});
TestCase.assertEqual(results.length, 0);
results = realm.objects('NullableBasicTypesObject').filtered('dateCol = $0', new Date(2000));
TestCase.assertEqual(results.length, N);
realm.close();
},
testResultsUpdateDataColumn: function() {
const N = 5;
var RANDOM_DATA = new Uint8Array([
0xd8, 0x21, 0xd6, 0xe8, 0x00, 0x57, 0xbc, 0xb2, 0x6a, 0x15, 0x77, 0x30, 0xac, 0x77, 0x96, 0xd9,
0x67, 0x1e, 0x40, 0xa7, 0x6d, 0x52, 0x83, 0xda, 0x07, 0x29, 0x9c, 0x70, 0x38, 0x48, 0x4e, 0xff,
]);
var realm = new Realm({schema: [schemas.NullableBasicTypes]});
// date column
realm.write(() => {
for(var i = 0; i < N; i++) {
realm.create('NullableBasicTypesObject', {
dataCol: null
});
}
});
var results = realm.objects('NullableBasicTypesObject');
TestCase.assertEqual(results.length, N);
realm.write(() => {
results.update('dataCol', RANDOM_DATA);
});
for(var i = 0; i < results.length; i++) {
TestCase.assertArraysEqual(new Uint8Array(results[i].dataCol), RANDOM_DATA);
}
realm.close();
},
testResultsUpdateEmpty() {
var realm = new Realm({schema: [schemas.NullableBasicTypes]});
var emptyResults = realm.objects('NullableBasicTypesObject').filtered('stringCol = "hello"');
TestCase.assertEqual(emptyResults.length, 0);
realm.write(() => {
emptyResults.update('stringCol', 'no-op');
});
TestCase.assertEqual(emptyResults.length, 0);
TestCase.assertEqual(realm.objects('NullableBasicTypesObject').filtered('stringCol = "no-op"').length, 0);
realm.close();
},
testResultsUpdateInvalidated() {
var realm = new Realm({schema: [schemas.TestObject]});
realm.write(function() {
for (var i = 10; i > 0; i--) {
realm.create('TestObject', [i]);
}
});
var resultsVariants = [
realm.objects('TestObject'),
realm.objects('TestObject').filtered('doubleCol > 1'),
realm.objects('TestObject').filtered('doubleCol > 1').sorted('doubleCol'),
realm.objects('TestObject').filtered('doubleCol > 1').snapshot()
];
// test isValid
resultsVariants.forEach(function(objects) {
TestCase.assertEqual(objects.isValid(), true);
});
// close and test update
realm.close();
realm = new Realm({
schemaVersion: 1,
schema: [schemas.TestObject, schemas.BasicTypes]
});
resultsVariants.forEach(function(objects) {
TestCase.assertEqual(objects.isValid(), false);
TestCase.assertThrows(function() { objects.update('doubleCol', 42); });
});
},
testResultsUpdateWrongProperty() {
var realm = new Realm({schema: [schemas.NullableBasicTypes]});
const N = 5;
realm.write(() => {
for(var i = 0; i < N; i++) {
realm.create('NullableBasicTypesObject', {
stringCol: 'hello'
});
}
});
var results = realm.objects('NullableBasicTypesObject').filtered('stringCol = "hello"');
TestCase.assertEqual(results.length, N);
TestCase.assertThrows(function() {
realm.write(() => {
results.update('unknownCol', 'world');
});
});
TestCase.assertThrows(function() {
results.update('stringCol', 'world');
});
realm.close();
}
};