From 8a77099685a18e16c93fe97ae7e86312cb372fa6 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 11 Apr 2016 13:58:52 -0700 Subject: [PATCH 1/7] support for null queries --- src/object-store/src/parser/parser.cpp | 4 +- src/object-store/src/parser/parser.hpp | 2 +- src/object-store/src/parser/query_builder.cpp | 74 ++++++++++++++----- 3 files changed, 60 insertions(+), 20 deletions(-) diff --git a/src/object-store/src/parser/parser.cpp b/src/object-store/src/parser/parser.cpp index 6819a954..6b981b3e 100644 --- a/src/object-store/src/parser/parser.cpp +++ b/src/object-store/src/parser/parser.cpp @@ -56,6 +56,7 @@ struct number : seq< minus, sor< float_num, hex_num, int_num > > {}; struct true_value : pegtl_istring_t("true") {}; struct false_value : pegtl_istring_t("false") {}; +struct null_value : pegtl_istring_t("null") {}; // key paths 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 > > {}; // 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 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(true_value, Expression::Type::True) EXPRESSION_ACTION(false_value, Expression::Type::False) +EXPRESSION_ACTION(null_value, Expression::Type::Null) EXPRESSION_ACTION(argument_index, Expression::Type::Argument) template<> struct action< true_pred > diff --git a/src/object-store/src/parser/parser.hpp b/src/object-store/src/parser/parser.hpp index 2f2ebed2..1e8afef8 100644 --- a/src/object-store/src/parser/parser.hpp +++ b/src/object-store/src/parser/parser.hpp @@ -28,7 +28,7 @@ class Schema; namespace parser { 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; Expression(Type t = Type::None, std::string s = "") : type(t), s(s) {} }; diff --git a/src/object-store/src/parser/query_builder.cpp b/src/object-store/src/parser/query_builder.cpp index 69eb8e54..a93f7863 100644 --- a/src/object-store/src/parser/query_builder.cpp +++ b/src/object-store/src/parser/query_builder.cpp @@ -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(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) { return args.object_index_for_argument(stot(argExpr.s)); @@ -438,6 +422,50 @@ void do_add_comparison_to_query(Query &query, const Schema &schema, const Object } } } + +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 PropertyTypeObject: + precondition(expr.indexes.empty(), "KeyPath queries not supported for object comparisons."); + switch (cmp.op) { + case Predicate::Operator::NotEqual: + query.Not(); + case Predicate::Operator::Equal: + query.and_query(query.get_table()->column(expr.prop->table_column).is_null()); + break; + default: + throw std::runtime_error("Only 'equal' and 'not equal' operators supported for object comparison."); + } + break; + case PropertyTypeArray: + throw std::runtime_error((std::string)"Comparing Lists to 'null' is not supported"); + break; + default: { + switch (cmp.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 for object comparison."); + } + } + } +} + +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(expr.s)); + } + return false; +} void add_comparison_to_query(Query &query, const Predicate &pred, Arguments &args, const Schema &schema, const std::string &type) { @@ -446,11 +474,21 @@ void add_comparison_to_query(Query &query, const Predicate &pred, Arguments &arg 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, expr, expr, cmpr.expr[1], args); + 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); + } } 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, expr, cmpr.expr[0], expr, args); + 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); + } } else { throw std::runtime_error("Predicate expressions must compare a keypath and another keypath or a constant value"); From 3a174161d73ce8135b1fd0a781f3510e93360803 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 11 Apr 2016 15:03:47 -0700 Subject: [PATCH 2/7] support keypath comparisons for types that support it --- src/object-store/src/parser/query_builder.cpp | 70 ++++++++++++++++--- 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/src/object-store/src/parser/query_builder.cpp b/src/object-store/src/parser/query_builder.cpp index a93f7863..eea6b8d2 100644 --- a/src/object-store/src/parser/query_builder.cpp +++ b/src/object-store/src/parser/query_builder.cpp @@ -422,11 +422,68 @@ void do_add_comparison_to_query(Query &query, const Schema &schema, const Object } } } + +template +void do_add_null_comparison_to_query(Query &query, Predicate::Operator op, const PropertyExpression &expr, Arguments &args) +{ + static realm::null null = realm::null(); + Columns column = expr.table_getter()->template column(expr.prop->table_column); + + switch (op) { + case Predicate::Operator::NotEqual: + query.and_query(column != null); + case Predicate::Operator::Equal: + query.and_query(column == 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) +template<> +void do_add_null_comparison_to_query(Query &query, Predicate::Operator op, const PropertyExpression &expr, Arguments &args) +{ + static realm::null null = realm::null(); + Columns column = expr.table_getter()->template column(expr.prop->table_column); + + switch (op) { + case Predicate::Operator::NotEqual: + query.not_equal(expr.prop->table_column, null); + case Predicate::Operator::Equal: + query.equal(expr.prop->table_column, 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(query, cmp.op, expr, args); + break; + case PropertyTypeDate: + do_add_null_comparison_to_query(query, cmp.op, expr, args); + break; + case PropertyTypeDouble: + do_add_null_comparison_to_query(query, cmp.op, expr, args); + break; + case PropertyTypeFloat: + do_add_null_comparison_to_query(query, cmp.op, expr, args); + break; + case PropertyTypeInt: + do_add_null_comparison_to_query(query, cmp.op, expr, args); + break; + case PropertyTypeString: + do_add_null_comparison_to_query(query, cmp.op, expr, args); + break; + case PropertyTypeData: + precondition(expr.indexes.empty(), "KeyPath queries not supported for data comparisons."); + do_add_null_comparison_to_query(query, cmp.op, expr, args); + break; case PropertyTypeObject: precondition(expr.indexes.empty(), "KeyPath queries not supported for object comparisons."); switch (cmp.op) { @@ -443,16 +500,7 @@ void do_add_null_comparison_to_query(Query &query, const Schema &schema, const O throw std::runtime_error((std::string)"Comparing Lists to 'null' is not supported"); break; default: { - switch (cmp.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 for object comparison."); - } + throw std::runtime_error((std::string)"Object type " + string_for_property_type(type) + " not supported"); } } } From b542fe8c7b9e22423b61a49fb77b203ddd0444dd Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 11 Apr 2016 16:01:51 -0700 Subject: [PATCH 3/7] tests --- src/object-store/src/parser/query_builder.cpp | 43 +++++++++-------- src/object-store/tests/query.json | 46 +++++++++++++++++++ tests/js/query-tests.js | 3 ++ 3 files changed, 72 insertions(+), 20 deletions(-) diff --git a/src/object-store/src/parser/query_builder.cpp b/src/object-store/src/parser/query_builder.cpp index eea6b8d2..a0746f65 100644 --- a/src/object-store/src/parser/query_builder.cpp +++ b/src/object-store/src/parser/query_builder.cpp @@ -426,37 +426,50 @@ void do_add_comparison_to_query(Query &query, const Schema &schema, const Object template void do_add_null_comparison_to_query(Query &query, Predicate::Operator op, const PropertyExpression &expr, Arguments &args) { - static realm::null null = realm::null(); Columns column = expr.table_getter()->template column(expr.prop->table_column); - switch (op) { case Predicate::Operator::NotEqual: - query.and_query(column != null); + query.and_query(column != realm::null()); case Predicate::Operator::Equal: - query.and_query(column == null); + query.and_query(column == realm::null()); break; default: - throw std::runtime_error("Only 'equal' and 'not equal' operators supported for object comparison."); + throw std::runtime_error("Only 'equal' and 'not equal' operators supported when comapring against 'null'."); } } template<> void do_add_null_comparison_to_query(Query &query, Predicate::Operator op, const PropertyExpression &expr, Arguments &args) { - static realm::null null = realm::null(); + precondition(expr.indexes.empty(), "KeyPath queries not supported for data comparisons."); Columns column = expr.table_getter()->template column(expr.prop->table_column); - switch (op) { case Predicate::Operator::NotEqual: - query.not_equal(expr.prop->table_column, null); + query.not_equal(expr.prop->table_column, realm::null()); case Predicate::Operator::Equal: - query.equal(expr.prop->table_column, null); + query.equal(expr.prop->table_column, realm::null()); + break; + default: + throw std::runtime_error("Only 'equal' and 'not equal' operators supported when comapring against 'null'."); + } +} + +template<> +void do_add_null_comparison_to_query(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: + query.Not(); + case Predicate::Operator::Equal: + query.and_query(query.get_table()->column(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) { @@ -481,20 +494,10 @@ void do_add_null_comparison_to_query(Query &query, const Schema &schema, const O do_add_null_comparison_to_query(query, cmp.op, expr, args); break; case PropertyTypeData: - precondition(expr.indexes.empty(), "KeyPath queries not supported for data comparisons."); do_add_null_comparison_to_query(query, cmp.op, expr, args); break; case PropertyTypeObject: - precondition(expr.indexes.empty(), "KeyPath queries not supported for object comparisons."); - switch (cmp.op) { - case Predicate::Operator::NotEqual: - query.Not(); - case Predicate::Operator::Equal: - query.and_query(query.get_table()->column(expr.prop->table_column).is_null()); - break; - default: - throw std::runtime_error("Only 'equal' and 'not equal' operators supported for object comparison."); - } + do_add_null_comparison_to_query(query, cmp.op, expr, args); break; case PropertyTypeArray: throw std::runtime_error((std::string)"Comparing Lists to 'null' is not supported"); diff --git a/src/object-store/tests/query.json b/src/object-store/tests/query.json index e4edd4de..22ef1c72 100644 --- a/src/object-store/tests/query.json +++ b/src/object-store/tests/query.json @@ -245,6 +245,9 @@ ["QueryCount", 1, "LinkObject", "linkCol == $0", [0, "linkCol"]], ["QueryCount", 1, "LinkObject", "$0 == linkCol", [1, "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", "intCol = $0", [0, "linkCol"]] @@ -317,6 +320,49 @@ ["ObjectSet", [1], "LinkTypesObject", "linkLink.basicLink.floatCol == 0.1"], ["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", [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"] + ] } } diff --git a/tests/js/query-tests.js b/tests/js/query-tests.js index c4cc4d57..3f5f2ace 100644 --- a/tests/js/query-tests.js +++ b/tests/js/query-tests.js @@ -146,6 +146,9 @@ module.exports = BaseTest.extend({ }, testKeyPathQueries: function() { runQuerySuite(testCases.keyPathTests); + }, + testOptionalQueries: function() { + runQuerySuite(testCases.optionalTests); } }); From 321ddc39e170564cc3e487d4b2408c725aea6cb1 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 11 Apr 2016 16:03:36 -0700 Subject: [PATCH 4/7] bug fix --- src/object-store/src/parser/query_builder.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/object-store/src/parser/query_builder.cpp b/src/object-store/src/parser/query_builder.cpp index a0746f65..877fa167 100644 --- a/src/object-store/src/parser/query_builder.cpp +++ b/src/object-store/src/parser/query_builder.cpp @@ -430,6 +430,7 @@ void do_add_null_comparison_to_query(Query &query, Predicate::Operator op, const switch (op) { case Predicate::Operator::NotEqual: query.and_query(column != realm::null()); + break; case Predicate::Operator::Equal: query.and_query(column == realm::null()); break; @@ -446,6 +447,7 @@ void do_add_null_comparison_to_query(Query &query, Predicate::Operator o 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; From 9f4b9410a5e2a4ff7d2fe8c2a6309f28bb8906fe Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 11 Apr 2016 16:37:18 -0700 Subject: [PATCH 5/7] typo --- src/object-store/src/parser/query_builder.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/object-store/src/parser/query_builder.cpp b/src/object-store/src/parser/query_builder.cpp index 877fa167..5cd7ca49 100644 --- a/src/object-store/src/parser/query_builder.cpp +++ b/src/object-store/src/parser/query_builder.cpp @@ -435,7 +435,7 @@ void do_add_null_comparison_to_query(Query &query, Predicate::Operator op, const query.and_query(column == realm::null()); break; default: - throw std::runtime_error("Only 'equal' and 'not equal' operators supported when comapring against 'null'."); + throw std::runtime_error("Only 'equal' and 'not equal' operators supported when comparing against 'null'."); } } @@ -452,7 +452,7 @@ void do_add_null_comparison_to_query(Query &query, Predicate::Operator o query.equal(expr.prop->table_column, realm::null()); break; default: - throw std::runtime_error("Only 'equal' and 'not equal' operators supported when comapring against 'null'."); + throw std::runtime_error("Only 'equal' and 'not equal' operators supported when comparing against 'null'."); } } From 1afa844aa43652b289a1459eea0b35854f3167c8 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 12 Apr 2016 08:13:29 -0700 Subject: [PATCH 6/7] pr feedback, changelog --- CHANGELOG.md | 11 +++++++++++ src/object-store/src/parser/query_builder.cpp | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec690625..79745316 100644 --- a/CHANGELOG.md +++ b/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) ============================================================= ### Bugfixes diff --git a/src/object-store/src/parser/query_builder.cpp b/src/object-store/src/parser/query_builder.cpp index 5cd7ca49..6984af85 100644 --- a/src/object-store/src/parser/query_builder.cpp +++ b/src/object-store/src/parser/query_builder.cpp @@ -462,6 +462,7 @@ void do_add_null_comparison_to_query(Query &query, Predicate::Operator op, 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(expr.prop->table_column).is_null()); @@ -471,7 +472,6 @@ void do_add_null_comparison_to_query(Query &query, Predicate::Operator op, } } - void do_add_null_comparison_to_query(Query &query, const Schema &schema, const ObjectSchema &object_schema, Predicate::Comparison cmp, const PropertyExpression &expr, Arguments &args) { From 4aed9135b8000e2ce0b507f8707c3d8f0e0d0445 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 12 Apr 2016 11:01:33 -0700 Subject: [PATCH 7/7] add keypath tests --- src/object-store/tests/query.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/object-store/tests/query.json b/src/object-store/tests/query.json index 22ef1c72..6cbbee55 100644 --- a/src/object-store/tests/query.json +++ b/src/object-store/tests/query.json @@ -351,6 +351,7 @@ ], "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"], @@ -361,7 +362,19 @@ ["ObjectSet", [1], "OptionalTypesObject", "dateCol == null"], ["ObjectSet", [0], "OptionalTypesObject", "dateCol != null"], ["ObjectSet", [1], "OptionalTypesObject", "dataCol == null"], - ["ObjectSet", [0], "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"] ] }