Merge pull request #1303 from realm/tg/keypath-sort

Use the objectstore keypath sorting functionality
This commit is contained in:
Thomas Goyne 2017-09-21 11:02:50 -07:00 committed by GitHub
commit e44b9126a8
5 changed files with 65 additions and 72 deletions

View File

@ -1,3 +1,14 @@
vNext (TBD)
=============================================================
### Breaking changes
* None
### Enhancements
* Add support for sorting Lists and Results on values from linked objects.
### Bug fixes
* None
2.0.0 Release notes (2017-9-19) 2.0.0 Release notes (2017-9-19)
============================================================= =============================================================
### Breaking changes ### Breaking changes

View File

@ -248,10 +248,8 @@ void ListClass<T>::filtered(ContextType ctx, FunctionType, ObjectType this_objec
template<typename T> template<typename T>
void ListClass<T>::sorted(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { void ListClass<T>::sorted(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1, 2);
auto list = get_internal<T, ListClass<T>>(this_object); auto list = get_internal<T, ListClass<T>>(this_object);
return_value.set(ResultsClass<T>::create_sorted(ctx, *list, argc, arguments)); return_value.set(ResultsClass<T>::create_instance(ctx, list->sort(ResultsClass<T>::get_keypaths(ctx, argc, arguments))));
} }
template<typename T> template<typename T>

View File

@ -63,8 +63,7 @@ struct ResultsClass : ClassDefinition<T, realm::js::Results<T>, CollectionClass<
template<typename U> template<typename U>
static ObjectType create_filtered(ContextType, const U &, size_t, const ValueType[]); static ObjectType create_filtered(ContextType, const U &, size_t, const ValueType[]);
template<typename U> static std::vector<std::pair<std::string, bool>> get_keypaths(ContextType, size_t, const ValueType[]);
static ObjectType create_sorted(ContextType, const U &, size_t, const ValueType[]);
static void get_length(ContextType, ObjectType, ReturnValue &); static void get_length(ContextType, ObjectType, ReturnValue &);
static void get_index(ContextType, ObjectType, uint32_t, ReturnValue &); static void get_index(ContextType, ObjectType, uint32_t, ReturnValue &);
@ -132,64 +131,37 @@ typename T::Object ResultsClass<T>::create_filtered(ContextType ctx, const U &co
} }
template<typename T> template<typename T>
template<typename U> std::vector<std::pair<std::string, bool>>
typename T::Object ResultsClass<T>::create_sorted(ContextType ctx, const U &collection, size_t argc, const ValueType arguments[]) { ResultsClass<T>::get_keypaths(ContextType ctx, size_t argc, const ValueType arguments[]) {
auto const &realm = collection.get_realm(); validate_argument_count(argc, 1, 2);
auto const &object_schema = collection.get_object_schema();
std::vector<std::string> prop_names;
std::vector<bool> ascending;
size_t prop_count;
if (Value::is_array(ctx, arguments[0])) { std::vector<std::pair<std::string, bool>> sort_order;
if (argc > 0 && Value::is_array(ctx, arguments[0])) {
validate_argument_count(argc, 1, "Second argument is not allowed if passed an array of sort descriptors"); validate_argument_count(argc, 1, "Second argument is not allowed if passed an array of sort descriptors");
ObjectType js_prop_names = Value::validated_to_object(ctx, arguments[0]); ObjectType js_prop_names = Value::validated_to_object(ctx, arguments[0]);
prop_count = Object::validated_get_length(ctx, js_prop_names); size_t prop_count = Object::validated_get_length(ctx, js_prop_names);
if (!prop_count) { sort_order.reserve(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++) { for (unsigned int i = 0; i < prop_count; i++) {
ValueType value = Object::validated_get_property(ctx, js_prop_names, i); ValueType value = Object::validated_get_property(ctx, js_prop_names, i);
if (Value::is_array(ctx, value)) { if (Value::is_array(ctx, value)) {
ObjectType array = Value::to_array(ctx, value); ObjectType array = Value::to_array(ctx, value);
prop_names[i] = Object::validated_get_string(ctx, array, 0); sort_order.emplace_back(Object::validated_get_string(ctx, array, 0),
ascending[i] = !Object::validated_get_boolean(ctx, array, 1); !Object::validated_get_boolean(ctx, array, 1));
} }
else { else {
prop_names[i] = Value::validated_to_string(ctx, value); sort_order.emplace_back(Value::validated_to_string(ctx, value), true);
ascending[i] = true;
} }
} }
} }
else { else {
validate_argument_count(argc, 1, 2); sort_order.emplace_back(Value::validated_to_string(ctx, arguments[0]),
argc == 1 || !Value::to_boolean(ctx, arguments[1]));
prop_count = 1;
prop_names.push_back(Value::validated_to_string(ctx, arguments[0]));
ascending.push_back(argc == 1 ? true : !Value::to_boolean(ctx, arguments[1]));
} }
return sort_order;
std::vector<std::vector<size_t>> columns;
columns.reserve(prop_count);
for (std::string &prop_name : prop_names) {
const Property *prop = object_schema.property_for_name(prop_name);
if (!prop) {
throw std::runtime_error("Property '" + prop_name + "' does not exist on object type '" + object_schema.name + "'");
}
columns.push_back({prop->table_column});
}
auto table = realm::ObjectStore::table_for_object_type(realm->read_group(), object_schema.name);
DescriptorOrdering ordering;
ordering.append_sort({*table, std::move(columns), std::move(ascending)});
auto results = new realm::js::Results<T>(realm, collection.get_query(), std::move(ordering));
return create_object<T, ResultsClass<T>>(ctx, results);
} }
template<typename T> template<typename T>
@ -231,10 +203,8 @@ void ResultsClass<T>::filtered(ContextType ctx, FunctionType, ObjectType this_ob
template<typename T> template<typename T>
void ResultsClass<T>::sorted(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { void ResultsClass<T>::sorted(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1, 2);
auto results = get_internal<T, ResultsClass<T>>(this_object); auto results = get_internal<T, ResultsClass<T>>(this_object);
return_value.set(create_sorted(ctx, *results, argc, arguments)); return_value.set(ResultsClass<T>::create_instance(ctx, results->sort(ResultsClass<T>::get_keypaths(ctx, argc, arguments))));
} }
template<typename T> template<typename T>

View File

@ -511,32 +511,46 @@ module.exports = {
}, },
testListSorted: function() { testListSorted: function() {
var realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList]}); const schema = [
var list; {name: 'Target', properties: {value: 'int'}},
{name: 'Mid', properties: {value: 'int', link: 'Target'}},
{name: 'List', properties: {list: {type: 'list', objectType: 'Mid'}}},
];
const realm = new Realm({schema: schema});
realm.write(function() { let list;
var object = realm.create('PersonList', {list: [ realm.write(() => {
{name: 'Ari', age: 10}, list = realm.create('List', {list: [
{name: 'Tim', age: 11}, {value: 3, link: {value: 1}},
{name: 'Bjarne', age: 12}, {value: 1, link: {value: 3}},
{name: 'Alex', age: 12, married: true} {value: 2, link: {value: 2}},
]}).list;
realm.create('List', {list: [
{value: 4, link: {value: 4}},
]}); ]});
realm.create('PersonObject', {name: 'NotInList', age: 10});
list = object.list;
}); });
var names = function(results) { const values = (results) => results.map((o) => o.value);
return results.map(function(object) {
return object.name;
});
};
var objects = list.sorted('name', true); TestCase.assertThrows(() => list.sorted());
TestCase.assertArraysEqual(names(objects), ['Tim', 'Bjarne', 'Ari', 'Alex']); TestCase.assertThrows(() => list.sorted('nonexistent property'));
TestCase.assertThrows(() => list.sorted('link'));
objects = list.sorted(['age', 'name']); TestCase.assertArraysEqual(values(list.sorted([])), [3, 1, 2]);
TestCase.assertArraysEqual(names(objects), ['Ari', 'Tim', 'Alex', 'Bjarne']);
TestCase.assertArraysEqual(values(list.sorted('value')), [1, 2, 3]);
TestCase.assertArraysEqual(values(list.sorted('value', false)), [1, 2, 3]);
TestCase.assertArraysEqual(values(list.sorted('value', true)), [3, 2, 1]);
TestCase.assertArraysEqual(values(list.sorted(['value'])), [1, 2, 3]);
TestCase.assertArraysEqual(values(list.sorted([['value', false]])), [1, 2, 3]);
TestCase.assertArraysEqual(values(list.sorted([['value', true]])), [3, 2, 1]);
TestCase.assertArraysEqual(values(list.sorted('link.value')), [3, 2, 1]);
TestCase.assertArraysEqual(values(list.sorted('link.value', false)), [3, 2, 1]);
TestCase.assertArraysEqual(values(list.sorted('link.value', true)), [1, 2, 3]);
TestCase.assertArraysEqual(values(list.sorted(['link.value'])), [3, 2, 1]);
TestCase.assertArraysEqual(values(list.sorted([['link.value', false]])), [3, 2, 1]);
TestCase.assertArraysEqual(values(list.sorted([['link.value', true]])), [1, 2, 3]);
}, },
testArrayMethods: function() { testArrayMethods: function() {

View File

@ -206,6 +206,9 @@ module.exports = {
}); });
}; };
objects = objects.sorted([]);
TestCase.assertArraysEqual(primaries(objects), [2, 3, 1, 4, 0]);
objects = objects.sorted('primaryCol'); objects = objects.sorted('primaryCol');
TestCase.assertArraysEqual(primaries(objects), [0, 1, 2, 3, 4]); TestCase.assertArraysEqual(primaries(objects), [0, 1, 2, 3, 4]);
@ -242,9 +245,6 @@ module.exports = {
TestCase.assertThrows(function() { TestCase.assertThrows(function() {
objects.sorted([1]); objects.sorted([1]);
}); });
TestCase.assertThrows(function() {
objects.sorted([]);
});
TestCase.assertThrows(function() { TestCase.assertThrows(function() {
objects.sorted('fish'); objects.sorted('fish');
}); });