Merge pull request #386 from realm/al-null-query
Support for querying against null for optionals
This commit is contained in:
commit
e368bcaf79
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -1,3 +1,14 @@
|
||||||
|
x.x.x Release notes (yyyy-MM-dd)
|
||||||
|
=============================================================
|
||||||
|
### Breaking changes
|
||||||
|
* None
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
* Support for queries comparing optional properties to `null`
|
||||||
|
|
||||||
|
### Bugfixes
|
||||||
|
* None
|
||||||
|
|
||||||
0.11.1 Release notes (2016-3-29)
|
0.11.1 Release notes (2016-3-29)
|
||||||
=============================================================
|
=============================================================
|
||||||
### Bugfixes
|
### Bugfixes
|
||||||
|
|
|
@ -56,6 +56,7 @@ struct number : seq< minus, sor< float_num, hex_num, int_num > > {};
|
||||||
|
|
||||||
struct true_value : pegtl_istring_t("true") {};
|
struct true_value : pegtl_istring_t("true") {};
|
||||||
struct false_value : pegtl_istring_t("false") {};
|
struct false_value : pegtl_istring_t("false") {};
|
||||||
|
struct null_value : pegtl_istring_t("null") {};
|
||||||
|
|
||||||
// key paths
|
// key paths
|
||||||
struct key_path : list< seq< sor< alpha, one< '_' > >, star< sor< alnum, one< '_', '-' > > > >, one< '.' > > {};
|
struct key_path : list< seq< sor< alpha, one< '_' > >, star< sor< alnum, one< '_', '-' > > > >, one< '.' > > {};
|
||||||
|
@ -65,7 +66,7 @@ struct argument_index : plus< digit > {};
|
||||||
struct argument : seq< one< '$' >, must< argument_index > > {};
|
struct argument : seq< one< '$' >, must< argument_index > > {};
|
||||||
|
|
||||||
// expressions and operators
|
// expressions and operators
|
||||||
struct expr : sor< dq_string, sq_string, number, argument, true_value, false_value, key_path > {};
|
struct expr : sor< dq_string, sq_string, number, argument, true_value, false_value, null_value, key_path > {};
|
||||||
struct case_insensitive : pegtl_istring_t("[c]"){};
|
struct case_insensitive : pegtl_istring_t("[c]"){};
|
||||||
|
|
||||||
struct eq : seq< sor< two< '=' >, one< '=' > >, opt< case_insensitive > >{};
|
struct eq : seq< sor< two< '=' >, one< '=' > >, opt< case_insensitive > >{};
|
||||||
|
@ -236,6 +237,7 @@ EXPRESSION_ACTION(key_path, Expression::Type::KeyPath)
|
||||||
EXPRESSION_ACTION(number, Expression::Type::Number)
|
EXPRESSION_ACTION(number, Expression::Type::Number)
|
||||||
EXPRESSION_ACTION(true_value, Expression::Type::True)
|
EXPRESSION_ACTION(true_value, Expression::Type::True)
|
||||||
EXPRESSION_ACTION(false_value, Expression::Type::False)
|
EXPRESSION_ACTION(false_value, Expression::Type::False)
|
||||||
|
EXPRESSION_ACTION(null_value, Expression::Type::Null)
|
||||||
EXPRESSION_ACTION(argument_index, Expression::Type::Argument)
|
EXPRESSION_ACTION(argument_index, Expression::Type::Argument)
|
||||||
|
|
||||||
template<> struct action< true_pred >
|
template<> struct action< true_pred >
|
||||||
|
|
|
@ -28,7 +28,7 @@ class Schema;
|
||||||
namespace parser {
|
namespace parser {
|
||||||
struct Expression
|
struct Expression
|
||||||
{
|
{
|
||||||
enum class Type { None, Number, String, KeyPath, Argument, True, False } type;
|
enum class Type { None, Number, String, KeyPath, Argument, True, False, Null } type;
|
||||||
std::string s;
|
std::string s;
|
||||||
Expression(Type t = Type::None, std::string s = "") : type(t), s(s) {}
|
Expression(Type t = Type::None, std::string s = "") : type(t), s(s) {}
|
||||||
};
|
};
|
||||||
|
|
|
@ -265,22 +265,6 @@ void add_link_constraint_to_query(realm::Query &query,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(const PropertyExpression &propExpr, const parser::Expression &argExpr, Arguments &args)
|
auto link_argument(const PropertyExpression &propExpr, const parser::Expression &argExpr, Arguments &args)
|
||||||
{
|
{
|
||||||
return args.object_index_for_argument(stot<int>(argExpr.s));
|
return args.object_index_for_argument(stot<int>(argExpr.s));
|
||||||
|
@ -439,6 +423,103 @@ void do_add_comparison_to_query(Query &query, const Schema &schema, const Object
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void do_add_null_comparison_to_query(Query &query, Predicate::Operator op, const PropertyExpression &expr, Arguments &args)
|
||||||
|
{
|
||||||
|
Columns<T> column = expr.table_getter()->template column<T>(expr.prop->table_column);
|
||||||
|
switch (op) {
|
||||||
|
case Predicate::Operator::NotEqual:
|
||||||
|
query.and_query(column != realm::null());
|
||||||
|
break;
|
||||||
|
case Predicate::Operator::Equal:
|
||||||
|
query.and_query(column == realm::null());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("Only 'equal' and 'not equal' operators supported when comparing against 'null'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
void do_add_null_comparison_to_query<Binary>(Query &query, Predicate::Operator op, const PropertyExpression &expr, Arguments &args)
|
||||||
|
{
|
||||||
|
precondition(expr.indexes.empty(), "KeyPath queries not supported for data comparisons.");
|
||||||
|
Columns<Binary> column = expr.table_getter()->template column<Binary>(expr.prop->table_column);
|
||||||
|
switch (op) {
|
||||||
|
case Predicate::Operator::NotEqual:
|
||||||
|
query.not_equal(expr.prop->table_column, realm::null());
|
||||||
|
break;
|
||||||
|
case Predicate::Operator::Equal:
|
||||||
|
query.equal(expr.prop->table_column, realm::null());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("Only 'equal' and 'not equal' operators supported when comparing against 'null'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
void do_add_null_comparison_to_query<Link>(Query &query, Predicate::Operator op, const PropertyExpression &expr, Arguments &args)
|
||||||
|
{
|
||||||
|
precondition(expr.indexes.empty(), "KeyPath queries not supported for object comparisons.");
|
||||||
|
switch (op) {
|
||||||
|
case Predicate::Operator::NotEqual:
|
||||||
|
// for not equal we negate the query and then fallthrough
|
||||||
|
query.Not();
|
||||||
|
case Predicate::Operator::Equal:
|
||||||
|
query.and_query(query.get_table()->column<Link>(expr.prop->table_column).is_null());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("Only 'equal' and 'not equal' operators supported for object comparison.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void do_add_null_comparison_to_query(Query &query, const Schema &schema, const ObjectSchema &object_schema, Predicate::Comparison cmp,
|
||||||
|
const PropertyExpression &expr, Arguments &args)
|
||||||
|
{
|
||||||
|
auto type = expr.prop->type;
|
||||||
|
switch (type) {
|
||||||
|
case PropertyTypeBool:
|
||||||
|
do_add_null_comparison_to_query<bool>(query, cmp.op, expr, args);
|
||||||
|
break;
|
||||||
|
case PropertyTypeDate:
|
||||||
|
do_add_null_comparison_to_query<DateTime>(query, cmp.op, expr, args);
|
||||||
|
break;
|
||||||
|
case PropertyTypeDouble:
|
||||||
|
do_add_null_comparison_to_query<Double>(query, cmp.op, expr, args);
|
||||||
|
break;
|
||||||
|
case PropertyTypeFloat:
|
||||||
|
do_add_null_comparison_to_query<Float>(query, cmp.op, expr, args);
|
||||||
|
break;
|
||||||
|
case PropertyTypeInt:
|
||||||
|
do_add_null_comparison_to_query<Int>(query, cmp.op, expr, args);
|
||||||
|
break;
|
||||||
|
case PropertyTypeString:
|
||||||
|
do_add_null_comparison_to_query<String>(query, cmp.op, expr, args);
|
||||||
|
break;
|
||||||
|
case PropertyTypeData:
|
||||||
|
do_add_null_comparison_to_query<Binary>(query, cmp.op, expr, args);
|
||||||
|
break;
|
||||||
|
case PropertyTypeObject:
|
||||||
|
do_add_null_comparison_to_query<Link>(query, cmp.op, expr, args);
|
||||||
|
break;
|
||||||
|
case PropertyTypeArray:
|
||||||
|
throw std::runtime_error((std::string)"Comparing Lists to 'null' is not supported");
|
||||||
|
break;
|
||||||
|
default: {
|
||||||
|
throw std::runtime_error((std::string)"Object type " + string_for_property_type(type) + " not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool expression_is_null(const parser::Expression &expr, Arguments &args) {
|
||||||
|
if (expr.type == parser::Expression::Type::Null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (expr.type == parser::Expression::Type::Argument) {
|
||||||
|
return args.is_argument_null(stot<int>(expr.s));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void add_comparison_to_query(Query &query, const Predicate &pred, Arguments &args, const Schema &schema, const std::string &type)
|
void add_comparison_to_query(Query &query, const Predicate &pred, Arguments &args, const Schema &schema, const std::string &type)
|
||||||
{
|
{
|
||||||
const Predicate::Comparison &cmpr = pred.cmpr;
|
const Predicate::Comparison &cmpr = pred.cmpr;
|
||||||
|
@ -446,12 +527,22 @@ void add_comparison_to_query(Query &query, const Predicate &pred, Arguments &arg
|
||||||
auto object_schema = schema.find(type);
|
auto object_schema = schema.find(type);
|
||||||
if (t0 == parser::Expression::Type::KeyPath && t1 != parser::Expression::Type::KeyPath) {
|
if (t0 == parser::Expression::Type::KeyPath && t1 != parser::Expression::Type::KeyPath) {
|
||||||
PropertyExpression expr(query, schema, object_schema, cmpr.expr[0].s);
|
PropertyExpression expr(query, schema, object_schema, cmpr.expr[0].s);
|
||||||
|
if (expression_is_null(cmpr.expr[1], args)) {
|
||||||
|
do_add_null_comparison_to_query(query, schema, *object_schema, cmpr, expr, args);
|
||||||
|
}
|
||||||
|
else {
|
||||||
do_add_comparison_to_query(query, schema, *object_schema, cmpr, expr, expr, cmpr.expr[1], args);
|
do_add_comparison_to_query(query, schema, *object_schema, cmpr, expr, expr, cmpr.expr[1], args);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else if (t0 != parser::Expression::Type::KeyPath && t1 == parser::Expression::Type::KeyPath) {
|
else if (t0 != parser::Expression::Type::KeyPath && t1 == parser::Expression::Type::KeyPath) {
|
||||||
PropertyExpression expr(query, schema, object_schema, cmpr.expr[1].s);
|
PropertyExpression expr(query, schema, object_schema, cmpr.expr[1].s);
|
||||||
|
if (expression_is_null(cmpr.expr[0], args)) {
|
||||||
|
do_add_null_comparison_to_query(query, schema, *object_schema, cmpr, expr, args);
|
||||||
|
}
|
||||||
|
else {
|
||||||
do_add_comparison_to_query(query, schema, *object_schema, cmpr, expr, cmpr.expr[0], expr, args);
|
do_add_comparison_to_query(query, schema, *object_schema, cmpr, expr, cmpr.expr[0], expr, args);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
throw std::runtime_error("Predicate expressions must compare a keypath and another keypath or a constant value");
|
throw std::runtime_error("Predicate expressions must compare a keypath and another keypath or a constant value");
|
||||||
}
|
}
|
||||||
|
|
|
@ -245,6 +245,9 @@
|
||||||
["QueryCount", 1, "LinkObject", "linkCol == $0", [0, "linkCol"]],
|
["QueryCount", 1, "LinkObject", "linkCol == $0", [0, "linkCol"]],
|
||||||
["QueryCount", 1, "LinkObject", "$0 == linkCol", [1, "linkCol"]],
|
["QueryCount", 1, "LinkObject", "$0 == linkCol", [1, "linkCol"]],
|
||||||
["QueryCount", 2, "LinkObject", "linkCol != $0", [0, "linkCol"]],
|
["QueryCount", 2, "LinkObject", "linkCol != $0", [0, "linkCol"]],
|
||||||
|
["QueryCount", 1, "LinkObject", "linkCol = null"],
|
||||||
|
["QueryCount", 2, "LinkObject", "linkCol != NULL"],
|
||||||
|
["QueryCount", 1, "LinkObject", "linkCol = $0", null],
|
||||||
|
|
||||||
["QueryThrows", "LinkObject", "linkCol > $0", [0, "linkCol"]],
|
["QueryThrows", "LinkObject", "linkCol > $0", [0, "linkCol"]],
|
||||||
["QueryThrows", "LinkObject", "intCol = $0", [0, "linkCol"]]
|
["QueryThrows", "LinkObject", "intCol = $0", [0, "linkCol"]]
|
||||||
|
@ -317,6 +320,62 @@
|
||||||
["ObjectSet", [1], "LinkTypesObject", "linkLink.basicLink.floatCol == 0.1"],
|
["ObjectSet", [1], "LinkTypesObject", "linkLink.basicLink.floatCol == 0.1"],
|
||||||
["ObjectSet", [1, 3], "LinkTypesObject", "linkLink.basicLink.floatCol > 0"]
|
["ObjectSet", [1, 3], "LinkTypesObject", "linkLink.basicLink.floatCol > 0"]
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"optionalTests" : {
|
||||||
|
"schema" : [
|
||||||
|
{
|
||||||
|
"name": "OptionalTypesObject",
|
||||||
|
"primaryKey": "primaryKey",
|
||||||
|
"properties": [
|
||||||
|
{ "name": "primaryKey", "type": "int" },
|
||||||
|
{ "name": "intCol", "type": "int", "optional": true },
|
||||||
|
{ "name": "floatCol", "type": "float", "optional": true },
|
||||||
|
{ "name": "doubleCol", "type": "double", "optional": true },
|
||||||
|
{ "name": "stringCol", "type": "string", "optional": true },
|
||||||
|
{ "name": "dateCol", "type": "date", "optional": true },
|
||||||
|
{ "name": "dataCol", "type": "data", "optional": true }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LinkTypesObject",
|
||||||
|
"primaryKey": "primaryKey",
|
||||||
|
"properties": [
|
||||||
|
{ "name": "primaryKey", "type": "int" },
|
||||||
|
{ "name": "basicLink", "type": "object", "objectType": "OptionalTypesObject" }
|
||||||
|
]
|
||||||
|
}],
|
||||||
|
"objects": [
|
||||||
|
{ "type": "LinkTypesObject", "value": [0, [0, 1, 0.1, 0.001, "1", 1, [1, 10, 100]]] },
|
||||||
|
{ "type": "LinkTypesObject", "value": [1, [1, null, null, null, null, null, null]] }
|
||||||
|
],
|
||||||
|
"tests": [
|
||||||
|
["ObjectSet", [1], "OptionalTypesObject", "intCol == null"],
|
||||||
|
["ObjectSet", [1], "OptionalTypesObject", "null == intCol"],
|
||||||
|
["ObjectSet", [0], "OptionalTypesObject", "intCol != null"],
|
||||||
|
["ObjectSet", [1], "OptionalTypesObject", "floatCol == null"],
|
||||||
|
["ObjectSet", [0], "OptionalTypesObject", "floatCol != null"],
|
||||||
|
["ObjectSet", [1], "OptionalTypesObject", "doubleCol == null"],
|
||||||
|
["ObjectSet", [0], "OptionalTypesObject", "doubleCol != null"],
|
||||||
|
["ObjectSet", [1], "OptionalTypesObject", "stringCol == null"],
|
||||||
|
["ObjectSet", [0], "OptionalTypesObject", "stringCol != null"],
|
||||||
|
["ObjectSet", [1], "OptionalTypesObject", "dateCol == null"],
|
||||||
|
["ObjectSet", [0], "OptionalTypesObject", "dateCol != null"],
|
||||||
|
["ObjectSet", [1], "OptionalTypesObject", "dataCol == null"],
|
||||||
|
["ObjectSet", [0], "OptionalTypesObject", "dataCol != null"],
|
||||||
|
|
||||||
|
["ObjectSet", [1], "LinkTypesObject", "basicLink.intCol == null"],
|
||||||
|
["ObjectSet", [0], "LinkTypesObject", "basicLink.intCol != null"],
|
||||||
|
["ObjectSet", [1], "LinkTypesObject", "basicLink.floatCol == null"],
|
||||||
|
["ObjectSet", [0], "LinkTypesObject", "basicLink.floatCol != null"],
|
||||||
|
["ObjectSet", [1], "LinkTypesObject", "basicLink.doubleCol == null"],
|
||||||
|
["ObjectSet", [0], "LinkTypesObject", "basicLink.doubleCol != null"],
|
||||||
|
["ObjectSet", [1], "LinkTypesObject", "basicLink.stringCol == null"],
|
||||||
|
["ObjectSet", [0], "LinkTypesObject", "basicLink.stringCol != null"],
|
||||||
|
["ObjectSet", [1], "LinkTypesObject", "basicLink.dateCol == null"],
|
||||||
|
["ObjectSet", [0], "LinkTypesObject", "basicLink.dateCol != null"],
|
||||||
|
["QueryThrows", "LinkTypesObject", "basicLink.dataCol == null"]
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -146,6 +146,9 @@ module.exports = BaseTest.extend({
|
||||||
},
|
},
|
||||||
testKeyPathQueries: function() {
|
testKeyPathQueries: function() {
|
||||||
runQuerySuite(testCases.keyPathTests);
|
runQuerySuite(testCases.keyPathTests);
|
||||||
|
},
|
||||||
|
testOptionalQueries: function() {
|
||||||
|
runQuerySuite(testCases.optionalTests);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue