object tests
This commit is contained in:
parent
7b993d2f09
commit
494fbd3a06
|
@ -109,8 +109,8 @@ JSObjectRef RJSResultsCreate(JSContextRef ctx, SharedRealm realm, std::string cl
|
|||
TableRef table = ObjectStore::table_for_object_type(realm->read_group(), className);
|
||||
Query query = table->where();
|
||||
Schema &schema = *realm->config().schema;
|
||||
auto object_schema = realm->config().schema->find(className);
|
||||
if (object_schema == realm->config().schema->end()) {
|
||||
auto object_schema = schema.find(className);
|
||||
if (object_schema == schema.end()) {
|
||||
throw std::runtime_error("Object type '" + className + "' not present in Realm.");
|
||||
}
|
||||
parser::Predicate predicate = parser::parse(queryString);
|
||||
|
|
|
@ -53,6 +53,51 @@ struct FalseExpression : realm::Expression {
|
|||
const Table* get_table() const override { return nullptr; }
|
||||
};
|
||||
|
||||
using KeyPath = std::vector<std::string>;
|
||||
KeyPath key_path_from_string(const std::string &s) {
|
||||
std::stringstream ss(s);
|
||||
std::string item;
|
||||
KeyPath key_path;
|
||||
while (std::getline(ss, item, '.')) {
|
||||
key_path.push_back(item);
|
||||
}
|
||||
return key_path;
|
||||
}
|
||||
|
||||
struct PropertyExpression
|
||||
{
|
||||
Property *prop = nullptr;
|
||||
std::vector<size_t> indexes;
|
||||
std::function<Table *()> table_getter;
|
||||
|
||||
PropertyExpression(Query &query, Schema &schema, Schema::iterator desc, const std::string &key_path_string)
|
||||
{
|
||||
KeyPath key_path = key_path_from_string(key_path_string);
|
||||
for (size_t index = 0; index < key_path.size(); index++) {
|
||||
if (prop) {
|
||||
precondition(prop->type == PropertyTypeObject || prop->type == PropertyTypeArray,
|
||||
(std::string)"Property '" + key_path[index] + "' is not a link in object of type '" + desc->name + "'");
|
||||
indexes.push_back(prop->table_column);
|
||||
|
||||
}
|
||||
prop = desc->property_for_name(key_path[index]);
|
||||
precondition(prop != nullptr, "No property '" + key_path[index] + "' on object of type '" + desc->name + "'");
|
||||
|
||||
if (prop->object_type.size()) {
|
||||
desc = schema.find(prop->object_type);
|
||||
}
|
||||
}
|
||||
|
||||
table_getter = [&] {
|
||||
TableRef& tbl = query.get_table();
|
||||
for (size_t col : indexes) {
|
||||
tbl->link(col); // mutates m_link_chain on table
|
||||
}
|
||||
return tbl.get();
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// add a clause for numeric constraints based on operator type
|
||||
template <typename A, typename B>
|
||||
|
@ -183,51 +228,48 @@ void add_binary_constraint_to_query(realm::Query &query,
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
using KeyPath = std::vector<std::string>;
|
||||
KeyPath key_path_from_string(const std::string &s) {
|
||||
std::stringstream ss(s);
|
||||
std::string item;
|
||||
KeyPath key_path;
|
||||
while (std::getline(ss, item, '.')) {
|
||||
key_path.push_back(item);
|
||||
void add_link_constraint_to_query(realm::Query &query,
|
||||
Predicate::Operator op,
|
||||
PropertyExpression &prop_expr,
|
||||
size_t row_index) {
|
||||
precondition(prop_expr.indexes.empty(), "KeyPath queries not supported for object comparisons.");
|
||||
switch (op) {
|
||||
case Predicate::Operator::NotEqual:
|
||||
query.Not();
|
||||
case Predicate::Operator::Equal:
|
||||
query.links_to(prop_expr.prop->table_column, row_index);
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("Only 'equal' and 'not equal' operators supported for object comparison.");
|
||||
}
|
||||
return key_path;
|
||||
}
|
||||
|
||||
struct PropertyExpression
|
||||
{
|
||||
Property *prop = nullptr;
|
||||
std::vector<size_t> indexes;
|
||||
std::function<Table *()> table_getter;
|
||||
|
||||
PropertyExpression(Query &query, Schema &schema, ObjectSchema &desc, const std::string &key_path_string)
|
||||
{
|
||||
KeyPath key_path = key_path_from_string(key_path_string);
|
||||
for (size_t index = 0; index < key_path.size(); index++) {
|
||||
if (prop) {
|
||||
precondition(prop->type == PropertyTypeObject || prop->type == PropertyTypeArray,
|
||||
(std::string)"Property '" + key_path[index] + "' is not a link in object of type '" + desc.name + "'");
|
||||
indexes.push_back(prop->table_column);
|
||||
|
||||
}
|
||||
prop = desc.property_for_name(key_path[index]);
|
||||
precondition(prop != nullptr, "No property '" + key_path[index] + "' on object of type '" + desc.name + "'");
|
||||
|
||||
if (prop->object_type.size()) {
|
||||
desc = *schema.find(prop->object_type);
|
||||
}
|
||||
}
|
||||
|
||||
table_getter = [&] {
|
||||
TableRef& tbl = query.get_table();
|
||||
for (size_t col : indexes) {
|
||||
tbl->link(col); // mutates m_link_chain on table
|
||||
}
|
||||
return tbl.get();
|
||||
};
|
||||
void add_link_constraint_to_query(realm::Query &query,
|
||||
Predicate::Operator op,
|
||||
PropertyExpression &prop_expr,
|
||||
realm::null) {
|
||||
precondition(prop_expr.indexes.empty(), "KeyPath queries not supported for object comparisons.");
|
||||
switch (op) {
|
||||
case Predicate::Operator::NotEqual:
|
||||
query.Not();
|
||||
case Predicate::Operator::Equal:
|
||||
query.and_query(query.get_table()->column<Link>(prop_expr.prop->table_column).is_null());
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("Only 'equal' and 'not equal' operators supported for object comparison.");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
auto link_argument(PropertyExpression &propExpr, parser::Expression &argExpr, Arguments &args)
|
||||
{
|
||||
return args.object_index_for_argument(std::stoi(argExpr.s));
|
||||
}
|
||||
|
||||
auto link_argument(parser::Expression &argExpr, PropertyExpression &propExpr, Arguments &args)
|
||||
{
|
||||
return args.object_index_for_argument(std::stoi(argExpr.s));
|
||||
}
|
||||
|
||||
|
||||
template <typename RetType, typename TableGetter>
|
||||
struct ColumnGetter {
|
||||
|
@ -366,30 +408,35 @@ void do_add_comparison_to_query(Query &query, Schema &schema, ObjectSchema &obje
|
|||
add_binary_constraint_to_query(query, op, value_of_type_for_query<Binary>(expr.table_getter, lhs, args),
|
||||
value_of_type_for_query<Binary>(expr.table_getter, rhs, args));
|
||||
break;
|
||||
case PropertyTypeObject:
|
||||
case PropertyTypeArray:
|
||||
add_link_constraint_to_query(query, op, expr, link_argument(lhs, rhs, args));
|
||||
break;
|
||||
default: {
|
||||
throw std::runtime_error((std::string)"Object type " + string_for_property_type(type) + " not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void add_comparison_to_query(Query &query, Predicate &pred, Arguments &args, Schema &schema, ObjectSchema &object_schema)
|
||||
void add_comparison_to_query(Query &query, Predicate &pred, Arguments &args, Schema &schema, const std::string &type)
|
||||
{
|
||||
Predicate::Comparison &cmpr = pred.cmpr;
|
||||
auto t0 = cmpr.expr[0].type, t1 = cmpr.expr[1].type;
|
||||
auto object_schema = schema.find(type);
|
||||
if (t0 == parser::Expression::Type::KeyPath && t1 != parser::Expression::Type::KeyPath) {
|
||||
PropertyExpression expr(query, schema, object_schema, cmpr.expr[0].s);
|
||||
do_add_comparison_to_query(query, schema, object_schema, cmpr.op, expr, expr, cmpr.expr[1], args);
|
||||
do_add_comparison_to_query(query, schema, *object_schema, cmpr.op, expr, expr, cmpr.expr[1], args);
|
||||
}
|
||||
else if (t0 != parser::Expression::Type::KeyPath && t1 == parser::Expression::Type::KeyPath) {
|
||||
PropertyExpression expr(query, schema, object_schema, cmpr.expr[1].s);
|
||||
do_add_comparison_to_query(query, schema, object_schema, cmpr.op, expr, cmpr.expr[0], expr, args);
|
||||
do_add_comparison_to_query(query, schema, *object_schema, cmpr.op, expr, cmpr.expr[0], expr, args);
|
||||
}
|
||||
else {
|
||||
throw std::runtime_error("Predicate expressions must compare a keypath and another keypath or a constant value");
|
||||
}
|
||||
}
|
||||
|
||||
void update_query_with_predicate(Query &query, Predicate &pred, Arguments &arguments, Schema &schema, ObjectSchema &object_schema)
|
||||
void update_query_with_predicate(Query &query, Predicate &pred, Arguments &arguments, Schema &schema, const std::string &type)
|
||||
{
|
||||
if (pred.negate) {
|
||||
query.Not();
|
||||
|
@ -399,7 +446,7 @@ void update_query_with_predicate(Query &query, Predicate &pred, Arguments &argum
|
|||
case Predicate::Type::And:
|
||||
query.group();
|
||||
for (auto &sub : pred.cpnd.sub_predicates) {
|
||||
update_query_with_predicate(query, sub, arguments, schema, object_schema);
|
||||
update_query_with_predicate(query, sub, arguments, schema, type);
|
||||
}
|
||||
if (!pred.cpnd.sub_predicates.size()) {
|
||||
query.and_query(new TrueExpression);
|
||||
|
@ -411,7 +458,7 @@ void update_query_with_predicate(Query &query, Predicate &pred, Arguments &argum
|
|||
query.group();
|
||||
for (auto &sub : pred.cpnd.sub_predicates) {
|
||||
query.Or();
|
||||
update_query_with_predicate(query, sub, arguments, schema, object_schema);
|
||||
update_query_with_predicate(query, sub, arguments, schema, type);
|
||||
}
|
||||
if (!pred.cpnd.sub_predicates.size()) {
|
||||
query.and_query(new FalseExpression);
|
||||
|
@ -420,7 +467,7 @@ void update_query_with_predicate(Query &query, Predicate &pred, Arguments &argum
|
|||
break;
|
||||
|
||||
case Predicate::Type::Comparison: {
|
||||
add_comparison_to_query(query, pred, arguments, schema, object_schema);
|
||||
add_comparison_to_query(query, pred, arguments, schema, type);
|
||||
break;
|
||||
}
|
||||
case Predicate::Type::True:
|
||||
|
@ -439,7 +486,7 @@ void update_query_with_predicate(Query &query, Predicate &pred, Arguments &argum
|
|||
|
||||
void apply_predicate(Query &query, Predicate &predicate, Arguments &arguments, Schema &schema, std::string objectType)
|
||||
{
|
||||
update_query_with_predicate(query, predicate, arguments, schema, *schema.find(objectType));
|
||||
update_query_with_predicate(query, predicate, arguments, schema, objectType);
|
||||
|
||||
// Test the constructed query in core
|
||||
std::string validateMessage = query.validate();
|
||||
|
|
|
@ -29,11 +29,11 @@ function runQuerySuite(suite) {
|
|||
});
|
||||
|
||||
return { type: obj.type, value: converted };
|
||||
});
|
||||
});
|
||||
|
||||
realm.write(function() {
|
||||
for (var obj of objects) {
|
||||
realm.create(obj.type, obj.value);
|
||||
for (var i = 0; i < objects.length; i++) {
|
||||
objects[i] = realm.create(objects[i].type, objects[i].value);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -43,7 +43,8 @@ function runQuerySuite(suite) {
|
|||
for (var i = 4; i < test.length; i++) {
|
||||
var arg = test[i];
|
||||
if (Array.isArray(arg)) {
|
||||
args.push(objects[arg[0]].value[arg[1]]);
|
||||
// aray arguments correspond to [objectAtIndex, propertyName]
|
||||
args.push(objects[arg[0]][arg[1]]);
|
||||
}
|
||||
else {
|
||||
args.push(arg);
|
||||
|
@ -88,6 +89,9 @@ module.exports = BaseTest.extend({
|
|||
testBinaryQueries: function() {
|
||||
runQuerySuite(testCases.binaryTests);
|
||||
},
|
||||
testObjectQueries: function() {
|
||||
runQuerySuite(testCases.objectTests);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
@ -540,34 +544,6 @@ module.exports = BaseTest.extend({
|
|||
@"Property type mismatch between double and string");
|
||||
}
|
||||
|
||||
- (void)testBinaryComparisonInPredicate
|
||||
{
|
||||
NSExpression *binary = [NSExpression expressionForConstantValue:[[NSData alloc] init]];
|
||||
|
||||
NSUInteger (^count)(NSPredicateOperatorType) = ^(NSPredicateOperatorType type) {
|
||||
NSPredicate *pred = [RLMPredicateUtil comparisonWithKeyPath: @"binaryCol"
|
||||
expression: binary
|
||||
operatorType: type];
|
||||
return [BinaryObject objectsWithPredicate: pred].count;
|
||||
};
|
||||
|
||||
XCTAssertEqual(count(NSBeginsWithPredicateOperatorType), 0U,
|
||||
@"BEGINSWITH operator in binary comparison.");
|
||||
XCTAssertEqual(count(NSEndsWithPredicateOperatorType), 0U,
|
||||
@"ENDSWITH operator in binary comparison.");
|
||||
XCTAssertEqual(count(NSContainsPredicateOperatorType), 0U,
|
||||
@"CONTAINS operator in binary comparison.");
|
||||
XCTAssertEqual(count(NSEqualToPredicateOperatorType), 0U,
|
||||
@"= or == operator in binary comparison.");
|
||||
XCTAssertEqual(count(NSNotEqualToPredicateOperatorType), 0U,
|
||||
@"!= or <> operator in binary comparison.");
|
||||
|
||||
// Invalid operators.
|
||||
XCTAssertThrowsSpecificNamed(count(NSLessThanPredicateOperatorType), NSException,
|
||||
@"Invalid operator type",
|
||||
@"Invalid operator in binary comparison.");
|
||||
}
|
||||
|
||||
- (void)testKeyPathLocationInComparison
|
||||
{
|
||||
NSExpression *keyPath = [NSExpression expressionForKeyPath:@"intCol"];
|
||||
|
@ -946,55 +922,6 @@ module.exports = BaseTest.extend({
|
|||
XCTAssertThrows([CircleArrayObject objectsInRealm:realm where:@"NONE data.circles = '2'"]);
|
||||
}
|
||||
|
||||
- (void)testQueryWithObjects
|
||||
{
|
||||
RLMRealm *realm = [RLMRealm defaultRealm];
|
||||
|
||||
NSDate *date1 = [NSDate date];
|
||||
NSDate *date2 = [date1 dateByAddingTimeInterval:1];
|
||||
NSDate *date3 = [date2 dateByAddingTimeInterval:1];
|
||||
|
||||
[realm beginWriteTransaction];
|
||||
|
||||
StringObject *stringObj0 = [StringObject createInRealm:realm withValue:@[@"string0"]];
|
||||
StringObject *stringObj1 = [StringObject createInRealm:realm withValue:@[@"string1"]];
|
||||
StringObject *stringObj2 = [StringObject createInRealm:realm withValue:@[@"string2"]];
|
||||
|
||||
AllTypesObject *obj0 = [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], date1, @YES, @1LL, @1, stringObj0]];
|
||||
AllTypesObject *obj1 = [AllTypesObject createInRealm:realm withValue:@[@YES, @2, @2.0f, @2.0, @"b", [@"b" dataUsingEncoding:NSUTF8StringEncoding], date2, @YES, @2LL, @"mixed", stringObj1]];
|
||||
AllTypesObject *obj2 = [AllTypesObject createInRealm:realm withValue:@[@NO, @3, @3.0f, @3.0, @"c", [@"c" dataUsingEncoding:NSUTF8StringEncoding], date3, @YES, @3LL, @"mixed", stringObj0]];
|
||||
AllTypesObject *obj3 = [AllTypesObject createInRealm:realm withValue:@[@NO, @3, @3.0f, @3.0, @"c", [@"c" dataUsingEncoding:NSUTF8StringEncoding], date3, @YES, @3LL, @"mixed", stringObj2]];
|
||||
AllTypesObject *obj4 = [AllTypesObject createInRealm:realm withValue:@[@NO, @3, @3.0f, @3.0, @"c", [@"c" dataUsingEncoding:NSUTF8StringEncoding], date3, @YES, @34359738368LL, @"mixed", NSNull.null]];
|
||||
|
||||
[ArrayOfAllTypesObject createInDefaultRealmWithValue:@[@[obj0, obj1]]];
|
||||
[ArrayOfAllTypesObject createInDefaultRealmWithValue:@[@[obj1]]];
|
||||
[ArrayOfAllTypesObject createInDefaultRealmWithValue:@[@[obj0, obj2, obj3]]];
|
||||
[ArrayOfAllTypesObject createInDefaultRealmWithValue:@[@[obj4]]];
|
||||
|
||||
[realm commitWriteTransaction];
|
||||
|
||||
// simple queries
|
||||
XCTAssertEqual(2U, ([AllTypesObject objectsWhere:@"objectCol = %@", stringObj0].count));
|
||||
XCTAssertEqual(1U, ([AllTypesObject objectsWhere:@"objectCol = %@", stringObj1].count));
|
||||
XCTAssertEqual(1U, ([AllTypesObject objectsWhere:@"objectCol = nil"].count));
|
||||
XCTAssertEqual(4U, ([AllTypesObject objectsWhere:@"objectCol != nil"].count));
|
||||
XCTAssertEqual(3U, ([AllTypesObject objectsWhere:@"objectCol != %@", stringObj0].count));
|
||||
|
||||
NSPredicate *longPred = [NSPredicate predicateWithFormat:@"longCol = %lli", 34359738368];
|
||||
XCTAssertEqual([AllTypesObject objectsWithPredicate:longPred].count, 1U, @"Count should be 1");
|
||||
|
||||
NSPredicate *longBetweenPred = [NSPredicate predicateWithFormat:@"longCol BETWEEN %@", @[@34359738367LL, @34359738369LL]];
|
||||
XCTAssertEqual([AllTypesObject objectsWithPredicate:longBetweenPred].count, 1U, @"Count should be 1");
|
||||
|
||||
// check for ANY object in array
|
||||
XCTAssertEqual(2U, ([ArrayOfAllTypesObject objectsWhere:@"ANY array = %@", obj0].count));
|
||||
XCTAssertEqual(2U, ([ArrayOfAllTypesObject objectsWhere:@"ANY array != %@", obj1].count));
|
||||
XCTAssertEqual(2U, ([ArrayOfAllTypesObject objectsWhere:@"NONE array = %@", obj0].count));
|
||||
XCTAssertEqual(2U, ([ArrayOfAllTypesObject objectsWhere:@"NONE array != %@", obj1].count));
|
||||
XCTAssertThrows(([ArrayOfAllTypesObject objectsWhere:@"array = %@", obj0].count));
|
||||
XCTAssertThrows(([ArrayOfAllTypesObject objectsWhere:@"array != %@", obj0].count));
|
||||
}
|
||||
|
||||
- (void)testCompoundOrQuery {
|
||||
RLMRealm *realm = [RLMRealm defaultRealm];
|
||||
|
||||
|
|
|
@ -11,12 +11,12 @@
|
|||
{ "type": "DateObject", "value": [10002] }
|
||||
],
|
||||
"tests": [
|
||||
["QueryCount", 2, "DateObject", "date < $0", [2, 0]],
|
||||
["QueryCount", 3, "DateObject", "date <= $0", [2, 0]],
|
||||
["QueryCount", 2, "DateObject", "date > $0", [0, 0]],
|
||||
["QueryCount", 3, "DateObject", "date >= $0", [0, 0]],
|
||||
["QueryCount", 1, "DateObject", "date == $0", [0, 0]],
|
||||
["QueryCount", 2, "DateObject", "date != $0", [0, 0]],
|
||||
["QueryCount", 2, "DateObject", "date < $0", [2, "date"]],
|
||||
["QueryCount", 3, "DateObject", "date <= $0", [2, "date"]],
|
||||
["QueryCount", 2, "DateObject", "date > $0", [0, "date"]],
|
||||
["QueryCount", 3, "DateObject", "date >= $0", [0, "date"]],
|
||||
["QueryCount", 1, "DateObject", "date == $0", [0, "date"]],
|
||||
["QueryCount", 2, "DateObject", "date != $0", [0, "date"]],
|
||||
|
||||
["QueryThrows", "DateObject", "date == 'not a date'"],
|
||||
["QueryThrows", "DateObject", "date == 1"],
|
||||
|
@ -217,13 +217,33 @@
|
|||
{ "type": "BinaryObject", "value": [[255, 0]] }
|
||||
],
|
||||
"tests": [
|
||||
["QueryCount", 1, "BinaryObject", "binaryCol == $0", [1, 0]],
|
||||
["QueryCount", 1, "BinaryObject", "$0 == binaryCol", [2, 0]],
|
||||
["QueryCount", 4, "BinaryObject", "binaryCol != $0", [0, 0]],
|
||||
["QueryCount", 1, "BinaryObject", "binaryCol BEGINSWITH $0", [0, 0]],
|
||||
["QueryCount", 2, "BinaryObject", "binaryCol BEGINSWITH $0", [1, 0]],
|
||||
["QueryCount", 2, "BinaryObject", "binaryCol ENDSWITH $0", [4, 0]],
|
||||
["QueryCount", 3, "BinaryObject", "binaryCol CONTAINS $0", [2, 0]]
|
||||
["QueryCount", 1, "BinaryObject", "binaryCol == $0", [1, "binaryCol"]],
|
||||
["QueryCount", 1, "BinaryObject", "$0 == binaryCol", [2, "binaryCol"]],
|
||||
["QueryCount", 4, "BinaryObject", "binaryCol != $0", [0, "binaryCol"]],
|
||||
["QueryCount", 1, "BinaryObject", "binaryCol BEGINSWITH $0", [0, "binaryCol"]],
|
||||
["QueryCount", 2, "BinaryObject", "binaryCol BEGINSWITH $0", [1, "binaryCol"]],
|
||||
["QueryCount", 2, "BinaryObject", "binaryCol ENDSWITH $0", [4, "binaryCol"]],
|
||||
["QueryCount", 3, "BinaryObject", "binaryCol CONTAINS $0", [2, "binaryCol"]]
|
||||
]
|
||||
},
|
||||
|
||||
"objectTests" : {
|
||||
"schema" : [
|
||||
{ "name": "IntObject", "properties": [{ "name": "intCol", "type": "int" }] },
|
||||
{ "name": "LinkObject", "properties": [{ "name": "linkCol", "type": "IntObject" }] }
|
||||
],
|
||||
"objects": [
|
||||
{ "type": "LinkObject", "value": [[1]] },
|
||||
{ "type": "LinkObject", "value": [[2]] },
|
||||
{ "type": "LinkObject", "value": [null] }
|
||||
],
|
||||
"tests": [
|
||||
["QueryCount", 1, "LinkObject", "linkCol == $0", [0, "linkCol"]],
|
||||
["QueryCount", 1, "LinkObject", "$0 == linkCol", [1, "linkCol"]],
|
||||
["QueryCount", 2, "LinkObject", "linkCol != $0", [0, "linkCol"]],
|
||||
|
||||
["QueryThrows", "LinkObject", "linkCol > $0", [0, "linkCol"]],
|
||||
["QueryThrows", "LinkObject", "intCol = $0", [0, "linkCol"]]
|
||||
]
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue