diff --git a/lib/lists.js b/lib/lists.js index 66dbd08f..bb91641d 100644 --- a/lib/lists.js +++ b/lib/lists.js @@ -18,6 +18,7 @@ class List {} // Non-mutating methods: util.createMethods(List.prototype, objectTypes.LIST, [ 'filtered', + 'sorted', 'snapshot', ]); diff --git a/lib/results.js b/lib/results.js index 4e127eb1..ba889bce 100644 --- a/lib/results.js +++ b/lib/results.js @@ -15,8 +15,8 @@ class Results {} util.createMethods(Results.prototype, constants.objectTypes.RESULTS, [ 'filtered', - 'snapshot', 'sorted', + 'snapshot', ]); function create(realmId, info) { diff --git a/src/js_list.cpp b/src/js_list.cpp index ba5878c8..7966c57a 100644 --- a/src/js_list.cpp +++ b/src/js_list.cpp @@ -191,6 +191,7 @@ JSValueRef ListStaticResults(JSContextRef ctx, JSObjectRef function, JSObjectRef try { List *list = RJSGetInternal(thisObject); RJSValidateArgumentCount(argumentCount, 0); + return RJSResultsCreate(ctx, list->realm(), list->get_object_schema(), std::move(list->get_query()), false); } catch (std::exception &exp) { @@ -204,10 +205,10 @@ JSValueRef ListStaticResults(JSContextRef ctx, JSObjectRef function, JSObjectRef JSValueRef ListFiltered(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { try { List *list = RJSGetInternal(thisObject); - RJSValidateArgumentCountIsAtLeast(argumentCount, 1); + SharedRealm sharedRealm = *RJSGetInternal(thisObject); - return RJSResultsCreate(ctx, sharedRealm, list->get_object_schema(), std::move(list->get_query()), argumentCount, arguments); + return RJSResultsCreateFiltered(ctx, sharedRealm, list->get_object_schema(), std::move(list->get_query()), argumentCount, arguments); } catch (std::exception &exp) { if (jsException) { @@ -217,7 +218,23 @@ JSValueRef ListFiltered(JSContextRef ctx, JSObjectRef function, JSObjectRef this return NULL; } -JSObjectRef RJSListCreate(JSContextRef ctx, realm::List &list) { +JSValueRef ListSorted(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { + try { + List *list = RJSGetInternal(thisObject); + RJSValidateArgumentRange(argumentCount, 1, 2); + + SharedRealm sharedRealm = *RJSGetInternal(thisObject); + return RJSResultsCreateSorted(ctx, sharedRealm, list->get_object_schema(), std::move(list->get_query()), argumentCount, arguments); + } + catch (std::exception &exp) { + if (jsException) { + *jsException = RJSMakeError(ctx, exp); + } + } + return NULL; +} + +JSObjectRef RJSListCreate(JSContextRef ctx, List &list) { return RJSWrapObject(ctx, RJSListClass(), new List(list)); } @@ -228,6 +245,7 @@ static const JSStaticFunction RJSListFuncs[] = { {"unshift", ListUnshift, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, {"splice", ListSplice, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, {"filtered", ListFiltered, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, + {"sorted", ListSorted, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, {"snapshot", ListStaticResults, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, {NULL, NULL}, }; diff --git a/src/js_results.cpp b/src/js_results.cpp index 42cfe330..ce7d4dae 100644 --- a/src/js_results.cpp +++ b/src/js_results.cpp @@ -96,54 +96,8 @@ JSValueRef ResultsSorted(JSContextRef ctx, JSObjectRef function, JSObjectRef thi Results *results = RJSGetInternal(thisObject); RJSValidateArgumentRange(argumentCount, 1, 2); - size_t prop_count; - std::vector prop_names; - std::vector ascending; - - if (RJSIsValueArray(ctx, arguments[0])) { - RJSValidateArgumentCount(argumentCount, 1, "Second argument is not allowed if passed an array of sort descriptors"); - - JSObjectRef js_prop_names = RJSValidatedValueToObject(ctx, arguments[0]); - prop_count = RJSValidatedListLength(ctx, js_prop_names); - if (!prop_count) { - throw std::invalid_argument("Sort descriptor array must not be empty"); - } - - prop_names.resize(prop_count); - ascending.resize(prop_count); - - for (unsigned int i = 0; i < prop_count; i++) { - JSValueRef val = RJSValidatedPropertyAtIndex(ctx, js_prop_names, i); - - if (RJSIsValueArray(ctx, val)) { - prop_names[i] = RJSValidatedStringForValue(ctx, RJSValidatedPropertyAtIndex(ctx, (JSObjectRef)val, 0)); - ascending[i] = !JSValueToBoolean(ctx, RJSValidatedPropertyAtIndex(ctx, (JSObjectRef)val, 1)); - } - else { - prop_names[i] = RJSValidatedStringForValue(ctx, val); - ascending[i] = true; - } - } - } - else { - prop_count = 1; - prop_names.push_back(RJSValidatedStringForValue(ctx, arguments[0])); - ascending.push_back(argumentCount == 1 ? true : !JSValueToBoolean(ctx, arguments[1])); - } - - std::vector columns(prop_count); - size_t index = 0; - - for (std::string prop_name : prop_names) { - const Property *prop = results->get_object_schema().property_for_name(prop_name); - if (!prop) { - throw std::runtime_error("Property '" + prop_name + "' does not exist on object type '" + results->get_object_schema().name + "'"); - } - columns[index++] = prop->table_column; - } - - results = new Results(results->sort({std::move(columns), std::move(ascending)})); - return RJSWrapObject(ctx, RJSResultsClass(), results); + SharedRealm sharedRealm = *RJSGetInternal(thisObject); + return RJSResultsCreateSorted(ctx, sharedRealm, results->get_object_schema(), std::move(results->get_query()), argumentCount, arguments); } catch (std::exception &exp) { if (jsException) { @@ -153,14 +107,13 @@ JSValueRef ResultsSorted(JSContextRef ctx, JSObjectRef function, JSObjectRef thi return NULL; } - JSValueRef ResultsFiltered(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { try { Results *results = RJSGetInternal(thisObject); - RJSValidateArgumentCountIsAtLeast(argumentCount, 1); + SharedRealm sharedRealm = *RJSGetInternal(thisObject); - return RJSResultsCreate(ctx, sharedRealm, results->get_object_schema(), std::move(results->get_query()), argumentCount, arguments); + return RJSResultsCreateFiltered(ctx, sharedRealm, results->get_object_schema(), std::move(results->get_query()), argumentCount, arguments); } catch (std::exception &exp) { if (jsException) { @@ -194,7 +147,14 @@ 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, realm::SharedRealm realm, const realm::ObjectSchema &objectSchema, realm::Query query, size_t argumentCount, const JSValueRef arguments[]) { +JSObjectRef RJSResultsCreate(JSContextRef ctx, SharedRealm realm, const ObjectSchema &objectSchema, Query query, bool live) { + Results *results = new Results(realm, objectSchema, std::move(query)); + results->set_live(live); + + return RJSWrapObject(ctx, RJSResultsClass(), results); +} + +JSObjectRef RJSResultsCreateFiltered(JSContextRef ctx, SharedRealm realm, const ObjectSchema &objectSchema, Query query, size_t argumentCount, const JSValueRef arguments[]) { std::string queryString = RJSValidatedStringForValue(ctx, arguments[0], "predicate"); std::vector args(argumentCount - 1); for (size_t i = 1; i < argumentCount; i++) { @@ -208,10 +168,56 @@ JSObjectRef RJSResultsCreate(JSContextRef ctx, realm::SharedRealm realm, const r return RJSResultsCreate(ctx, realm, objectSchema, std::move(query)); } -JSObjectRef RJSResultsCreate(JSContextRef ctx, SharedRealm realm, const ObjectSchema &objectSchema, Query query, bool live) { - Results *results = new Results(realm, objectSchema, std::move(query)); - results->set_live(live); +JSObjectRef RJSResultsCreateSorted(JSContextRef ctx, SharedRealm realm, const ObjectSchema &objectSchema, Query query, size_t argumentCount, const JSValueRef arguments[]) { + size_t prop_count; + std::vector prop_names; + std::vector ascending; + if (RJSIsValueArray(ctx, arguments[0])) { + RJSValidateArgumentCount(argumentCount, 1, "Second argument is not allowed if passed an array of sort descriptors"); + + JSObjectRef js_prop_names = RJSValidatedValueToObject(ctx, arguments[0]); + prop_count = RJSValidatedListLength(ctx, js_prop_names); + if (!prop_count) { + throw std::invalid_argument("Sort descriptor array must not be empty"); + } + + prop_names.resize(prop_count); + ascending.resize(prop_count); + + for (unsigned int i = 0; i < prop_count; i++) { + JSValueRef val = RJSValidatedPropertyAtIndex(ctx, js_prop_names, i); + + if (RJSIsValueArray(ctx, val)) { + prop_names[i] = RJSValidatedStringForValue(ctx, RJSValidatedPropertyAtIndex(ctx, (JSObjectRef)val, 0)); + ascending[i] = !JSValueToBoolean(ctx, RJSValidatedPropertyAtIndex(ctx, (JSObjectRef)val, 1)); + } + else { + prop_names[i] = RJSValidatedStringForValue(ctx, val); + ascending[i] = true; + } + } + } + else { + RJSValidateArgumentRange(argumentCount, 1, 2); + + prop_count = 1; + prop_names.push_back(RJSValidatedStringForValue(ctx, arguments[0])); + ascending.push_back(argumentCount == 1 ? true : !JSValueToBoolean(ctx, arguments[1])); + } + + std::vector columns(prop_count); + size_t index = 0; + + for (std::string prop_name : prop_names) { + const Property *prop = objectSchema.property_for_name(prop_name); + if (!prop) { + throw std::runtime_error("Property '" + prop_name + "' does not exist on object type '" + objectSchema.name + "'"); + } + columns[index++] = prop->table_column; + } + + Results *results = new Results(realm, objectSchema, std::move(query), {std::move(columns), std::move(ascending)}); return RJSWrapObject(ctx, RJSResultsClass(), results); } diff --git a/src/js_results.hpp b/src/js_results.hpp index 61ef0d07..a3994f07 100644 --- a/src/js_results.hpp +++ b/src/js_results.hpp @@ -16,6 +16,6 @@ namespace realm { 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, realm::Query query, size_t argumentCount, const JSValueRef arguments[]); JSObjectRef RJSResultsCreate(JSContextRef ctx, realm::SharedRealm realm, const realm::ObjectSchema &objectSchema, realm::Query query, bool live = true); - +JSObjectRef RJSResultsCreateFiltered(JSContextRef ctx, realm::SharedRealm realm, const realm::ObjectSchema &objectSchema, realm::Query query, size_t argumentCount, const JSValueRef arguments[]); +JSObjectRef RJSResultsCreateSorted(JSContextRef ctx, realm::SharedRealm realm, const realm::ObjectSchema &objectSchema, realm::Query query, size_t argumentCount, const JSValueRef arguments[]); diff --git a/tests/lib/list-tests.js b/tests/lib/list-tests.js index 6876cddd..ff78473a 100644 --- a/tests/lib/list-tests.js +++ b/tests/lib/list-tests.js @@ -445,29 +445,54 @@ module.exports = BaseTest.extend({ }, testListFiltered: function() { - var personListSchema = { - name: 'PersonList', - properties: { - list: {type: 'list', objectType: 'PersonObject'} - } - }; - var realm = new Realm({schema: [schemas.PersonObject, personListSchema]}); - var listObject; + var realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList]}); + var list; + realm.write(function() { - listObject = realm.create('PersonList', {list: [ + var object = realm.create('PersonList', {list: [ {name: 'Ari', age: 10}, {name: 'Tim', age: 11}, {name: 'Bjarne', age: 12}, {name: 'Alex', age: 12, married: true} ]}); realm.create('PersonObject', {name: 'NotInList', age: 10}); + + list = object.list; }); - var list = listObject.list; TestCase.assertEqual(list.filtered("truepredicate").length, 4); TestCase.assertEqual(list.filtered('age = 11')[0].name, 'Tim'); TestCase.assertEqual(list.filtered('age = 12').length, 2); TestCase.assertEqual(list.filtered('age > 10 && age < 13').length, 3); TestCase.assertEqual(list.filtered('age > 10').filtered('age < 13').length, 3); }, + + testListSorted: function() { + var realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList]}); + var list; + + realm.write(function() { + var object = realm.create('PersonList', {list: [ + {name: 'Ari', age: 10}, + {name: 'Tim', age: 11}, + {name: 'Bjarne', age: 12}, + {name: 'Alex', age: 12, married: true} + ]}); + realm.create('PersonObject', {name: 'NotInList', age: 10}); + + list = object.list; + }); + + var names = function(results, prop) { + return Array.prototype.map.call(results, function(object) { + return object.name; + }); + }; + + var objects = list.sorted('name', true); + TestCase.assertArraysEqual(names(objects), ['Tim', 'Bjarne', 'Ari', 'Alex']); + + objects = list.sorted(['age', 'name']); + TestCase.assertArraysEqual(names(objects), ['Ari', 'Tim', 'Alex', 'Bjarne']); + }, }); diff --git a/tests/lib/results-tests.js b/tests/lib/results-tests.js index fc222c54..a704a71b 100644 --- a/tests/lib/results-tests.js +++ b/tests/lib/results-tests.js @@ -135,7 +135,7 @@ module.exports = BaseTest.extend({ realm.objects('PersonObject').filtered("invalidQuery"); }); }, - testSort: function() { + testResultsSorted: function() { var realm = new Realm({schema: [schemas.IntPrimary]}); var objects = realm.objects('IntPrimaryObject'); diff --git a/tests/lib/schemas.js b/tests/lib/schemas.js index d4bac072..c3b414af 100644 --- a/tests/lib/schemas.js +++ b/tests/lib/schemas.js @@ -27,6 +27,13 @@ PersonObject.prototype.description = function() { }; exports.PersonObject = PersonObject; +exports.PersonList = { + name: 'PersonList', + properties: { + list: {type: 'list', objectType: 'PersonObject'}, + } +}; + exports.BasicTypes = { name: 'BasicTypesObject', properties: {