From 632f9d737e0b61d07a3f47c72780df1d7a2c0ef3 Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Fri, 20 Nov 2015 14:05:18 -0800 Subject: [PATCH] Add methods to create snapshot of List and Results The Results class was updated to match the style of List and include a flag (m_live) that determines if it should sync updates. If an object in the static Results is deleted, then it will return null. --- lib/lists.js | 6 +++++ lib/results.js | 1 + src/js_list.cpp | 18 ++++++++++++++ src/js_results.cpp | 47 ++++++++++++++++++++++++++++++------ src/js_results.hpp | 2 ++ src/object-store/list.cpp | 5 ++++ src/object-store/list.hpp | 2 ++ src/object-store/results.cpp | 17 +++++++++++-- src/object-store/results.hpp | 4 +++ tests/lib/ArrayTests.js | 34 ++++++++++++++++++++++++++ 10 files changed, 126 insertions(+), 10 deletions(-) diff --git a/lib/lists.js b/lib/lists.js index bc1c8230..5232f843 100644 --- a/lib/lists.js +++ b/lib/lists.js @@ -13,6 +13,12 @@ module.exports = { class List {} +// Non-mutating methods: +util.createMethods(List.prototype, constants.propTypes.LIST, [ + 'snapshot', +]); + +// Mutating methods: util.createMethods(List.prototype, constants.propTypes.LIST, [ 'pop', 'shift', diff --git a/lib/results.js b/lib/results.js index 25f6cb6d..befc590f 100644 --- a/lib/results.js +++ b/lib/results.js @@ -14,6 +14,7 @@ module.exports = { class Results {} util.createMethods(Results.prototype, constants.objectTypes.RESULTS, [ + 'snapshot', 'sortByProperty', ]); diff --git a/src/js_list.cpp b/src/js_list.cpp index 987701fd..d5f0c59b 100644 --- a/src/js_list.cpp +++ b/src/js_list.cpp @@ -4,6 +4,7 @@ #include "js_list.hpp" #include "js_object.hpp" +#include "js_results.hpp" #include "js_util.hpp" #include "object_accessor.hpp" @@ -184,6 +185,22 @@ JSValueRef ListSplice(JSContextRef ctx, JSObjectRef function, JSObjectRef thisOb return NULL; } +JSValueRef ListStaticResults(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { + try { + List *list = RJSGetInternal(thisObject); + RJSValidateArgumentCount(argumentCount, 0); + + Query query = list->get_query(); + return RJSResultsCreate(ctx, list->realm(), list->object_schema, query, false); + } + catch (std::exception &exp) { + if (jsException) { + *jsException = RJSMakeError(ctx, exp); + } + } + return NULL; +} + JSObjectRef RJSListCreate(JSContextRef ctx, realm::List &list) { return RJSWrapObject(ctx, RJSListClass(), new List(list)); } @@ -194,6 +211,7 @@ static const JSStaticFunction RJSListFuncs[] = { {"shift", ListShift, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, {"unshift", ListUnshift, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, {"splice", ListSplice, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, + {"snapshot", ListStaticResults, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, {NULL, NULL}, }; diff --git a/src/js_results.cpp b/src/js_results.cpp index 789d18da..173dd208 100644 --- a/src/js_results.cpp +++ b/src/js_results.cpp @@ -22,9 +22,12 @@ JSValueRef ResultsGetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef return JSValueMakeNumber(ctx, size); } - return RJSObjectCreate(ctx, Object(results->get_realm(), - results->object_schema, - results->get(RJSValidatedPositiveIndex(indexStr)))); + auto row = results->get(RJSValidatedPositiveIndex(indexStr)); + if (!row) { + return JSValueMakeNull(ctx); + } + + return RJSObjectCreate(ctx, Object(results->get_realm(), results->object_schema, row)); } catch (std::out_of_range &exp) { // getters for nonexistent properties in JS should always return undefined @@ -62,19 +65,40 @@ bool ResultsSetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef proper void ResultsPropertyNames(JSContextRef ctx, JSObjectRef object, JSPropertyNameAccumulatorRef propertyNames) { Results *results = RJSGetInternal(object); + size_t size = results->size(); + char str[32]; - for (int i = 0; i < results->size(); i++) { - sprintf(str, "%i", i); + for (size_t i = 0; i < size; i++) { + sprintf(str, "%zu", i); JSStringRef name = JSStringCreateWithUTF8CString(str); JSPropertyNameAccumulatorAddName(propertyNames, name); JSStringRelease(name); } } -JSValueRef SortByProperty(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { +JSValueRef ResultsStaticCopy(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { + try { + Results *results = RJSGetInternal(thisObject); + RJSValidateArgumentCount(argumentCount, 0); + + Results *copy = new Results(*results); + copy->set_live(false); + + return RJSWrapObject(ctx, RJSResultsClass(), copy); + } + catch (std::exception &exp) { + if (jsException) { + *jsException = RJSMakeError(ctx, exp); + } + } + return NULL; +} + +JSValueRef ResultsSortByProperty(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { try { Results *results = RJSGetInternal(thisObject); RJSValidateArgumentRange(argumentCount, 1, 2); + std::string propName = RJSValidatedStringForValue(ctx, arguments[0]); const Property *prop = results->object_schema.property_for_name(propName); if (!prop) { @@ -105,7 +129,6 @@ JSObjectRef RJSResultsCreate(JSContextRef ctx, SharedRealm realm, std::string cl return RJSWrapObject(ctx, RJSResultsClass(), new Results(realm, *object_schema, *table)); } - JSObjectRef RJSResultsCreate(JSContextRef ctx, SharedRealm realm, std::string className, std::string queryString, std::vector args) { TableRef table = ObjectStore::table_for_object_type(realm->read_group(), className); Query query = table->where(); @@ -121,8 +144,16 @@ JSObjectRef RJSResultsCreate(JSContextRef ctx, SharedRealm realm, std::string cl return RJSWrapObject(ctx, RJSResultsClass(), new Results(realm, *object_schema, std::move(query))); } +JSObjectRef RJSResultsCreate(JSContextRef ctx, SharedRealm realm, const ObjectSchema &objectSchema, const Query &query, bool live) { + Results *results = new Results(realm, objectSchema, query); + results->set_live(live); + + return RJSWrapObject(ctx, RJSResultsClass(), results); +} + static const JSStaticFunction RJSResultsFuncs[] = { - {"sortByProperty", SortByProperty, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, + {"snapshot", ResultsStaticCopy, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, + {"sortByProperty", ResultsSortByProperty, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, {NULL, NULL}, }; diff --git a/src/js_results.hpp b/src/js_results.hpp index 3867a6b1..c33fd7f3 100644 --- a/src/js_results.hpp +++ b/src/js_results.hpp @@ -6,9 +6,11 @@ namespace realm { class Realm; + class Query; typedef std::shared_ptr SharedRealm; } JSClassRef RJSResultsClass(); JSObjectRef RJSResultsCreate(JSContextRef ctx, realm::SharedRealm realm, std::string className); JSObjectRef RJSResultsCreate(JSContextRef ctx, realm::SharedRealm realm, std::string className, std::string query, std::vector args); +JSObjectRef RJSResultsCreate(JSContextRef ctx, realm::SharedRealm realm, const realm::ObjectSchema &objectSchema, const realm::Query &query, bool live = true); diff --git a/src/object-store/list.cpp b/src/object-store/list.cpp index 1cbafb8d..80d07bc4 100644 --- a/src/object-store/list.cpp +++ b/src/object-store/list.cpp @@ -59,6 +59,11 @@ void List::remove(size_t row_ndx) { m_link_view->remove(row_ndx); } +Query List::get_query() { + verify_attached(); + return m_link_view->get_target_table().where(m_link_view); +} + void List::verify_valid_row(size_t row_ndx, bool insertion) { size_t size = m_link_view->size(); if (row_ndx > size || (!insertion && row_ndx == size)) { diff --git a/src/object-store/list.hpp b/src/object-store/list.hpp index fdde4c62..85ec6221 100644 --- a/src/object-store/list.hpp +++ b/src/object-store/list.hpp @@ -47,6 +47,8 @@ namespace realm { template void set(ContextType ctx, ValueType value, size_t list_ndx); + Query get_query(); + void verify_valid_row(size_t row_ndx, bool insertion = false); void verify_attached(); void verify_in_tranaction(); diff --git a/src/object-store/results.cpp b/src/object-store/results.cpp index b2a63789..45578796 100644 --- a/src/object-store/results.cpp +++ b/src/object-store/results.cpp @@ -64,6 +64,17 @@ void Results::validate_write() const throw InvalidTransactionException("Must be in a write transaction"); } +void Results::set_live(bool live) +{ + if (!live && m_mode == Mode::Table) { + m_query = m_table->where(); + m_mode = Mode::Query; + } + + update_tableview(); + m_live = live; +} + size_t Results::size() { validate_read(); @@ -91,7 +102,7 @@ RowExpr Results::get(size_t row_ndx) case Mode::TableView: update_tableview(); if (row_ndx < m_table_view.size()) - return m_table_view.get(row_ndx); + return (!m_live && !m_table_view.is_row_attached(row_ndx)) ? RowExpr() : m_table_view.get(row_ndx); break; } @@ -147,7 +158,9 @@ void Results::update_tableview() m_mode = Mode::TableView; break; case Mode::TableView: - m_table_view.sync_if_needed(); + if (m_live) { + m_table_view.sync_if_needed(); + } break; } } diff --git a/src/object-store/results.hpp b/src/object-store/results.hpp index 115ca636..7d8563ff 100644 --- a/src/object-store/results.hpp +++ b/src/object-store/results.hpp @@ -60,6 +60,9 @@ public: // Get the object type which will be returned by get() StringData get_object_type() const noexcept { return object_schema.name; } + // Set whether the TableView should sync if needed before accessing results + void set_live(bool live); + // Get the size of this results // Can be either O(1) or O(N) depending on the state of things size_t size(); @@ -153,6 +156,7 @@ private: TableView m_table_view; Table* m_table = nullptr; SortOrder m_sort; + bool m_live = true; Mode m_mode = Mode::Empty; diff --git a/tests/lib/ArrayTests.js b/tests/lib/ArrayTests.js index f5110179..ddd629e9 100644 --- a/tests/lib/ArrayTests.js +++ b/tests/lib/ArrayTests.js @@ -341,4 +341,38 @@ module.exports = BaseTest.extend({ TestCase.assertEqual(array.length, 2); TestCase.assertEqual(objects.length, 4); }, + + testStaticResults: function() { + var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]}); + var objects = realm.objects('TestObject'); + var array; + + realm.write(function() { + var obj = realm.create('LinkTypesObject', [[1], [2], [[3], [4]]]); + array = obj.arrayCol; + }); + + var objectsCopy = objects.snapshot(); + var arrayCopy = array.snapshot(); + + TestCase.assertEqual(objectsCopy.length, 4); + TestCase.assertEqual(arrayCopy.length, 2); + + realm.write(function() { + array.push([5]); + TestCase.assertEqual(objectsCopy.length, 4); + TestCase.assertEqual(arrayCopy.length, 2); + + TestCase.assertEqual(objectsCopy.snapshot().length, 4); + TestCase.assertEqual(arrayCopy.snapshot().length, 2); + + TestCase.assertEqual(objects.snapshot().length, 5); + TestCase.assertEqual(array.snapshot().length, 3); + + realm.delete(array[0]); + TestCase.assertEqual(objectsCopy.length, 4); + TestCase.assertEqual(arrayCopy.length, 2); + TestCase.assertEqual(arrayCopy[0], null); + }); + }, });