diff --git a/RealmJS.xcodeproj/project.pbxproj b/RealmJS.xcodeproj/project.pbxproj index 4b35b20a..1cb71a9c 100644 --- a/RealmJS.xcodeproj/project.pbxproj +++ b/RealmJS.xcodeproj/project.pbxproj @@ -53,6 +53,8 @@ 029445941BDEDF40006D1617 /* transact_log_handler.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 029445921BDEDF40006D1617 /* transact_log_handler.hpp */; }; 029445971BDEDF46006D1617 /* external_commit_helper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 029445951BDEDF46006D1617 /* external_commit_helper.cpp */; }; 029445981BDEDF46006D1617 /* external_commit_helper.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 029445961BDEDF46006D1617 /* external_commit_helper.hpp */; }; + 0294465E1BF27DE6006D1617 /* query_builder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0294465C1BF27DE6006D1617 /* query_builder.cpp */; }; + 0294465F1BF27DE6006D1617 /* query_builder.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 0294465D1BF27DE6006D1617 /* query_builder.hpp */; }; 02B29A311B7CF86D008A7E6B /* RealmJS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02B58CB11AE99CEC009B348C /* RealmJS.framework */; }; 02B58CCE1AE99D4D009B348C /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02B58CCD1AE99D4D009B348C /* JavaScriptCore.framework */; }; 02C0864E1BCDB27000942F9C /* list.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02C0864C1BCDB27000942F9C /* list.cpp */; }; @@ -208,7 +210,9 @@ 029445921BDEDF40006D1617 /* transact_log_handler.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = transact_log_handler.hpp; path = "src/object-store/impl/transact_log_handler.hpp"; sourceTree = ""; }; 029445951BDEDF46006D1617 /* external_commit_helper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = external_commit_helper.cpp; path = "src/object-store/impl/apple/external_commit_helper.cpp"; sourceTree = ""; }; 029445961BDEDF46006D1617 /* external_commit_helper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = external_commit_helper.hpp; path = "src/object-store/impl/apple/external_commit_helper.hpp"; sourceTree = ""; }; - 02A3C7841BC4317A00B1A7BE /* GCDWebServer.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = GCDWebServer.xcodeproj; path = GCDWebServer/GCDWebServer.xcodeproj; sourceTree = ""; }; + 0294465C1BF27DE6006D1617 /* query_builder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = query_builder.cpp; path = "src/object-store/parser/query_builder.cpp"; sourceTree = ""; }; + 0294465D1BF27DE6006D1617 /* query_builder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = query_builder.hpp; path = "src/object-store/parser/query_builder.hpp"; sourceTree = ""; }; + 02A3C7841BC4317A00B1A7BE /* GCDWebServer.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = GCDWebServer.xcodeproj; path = vendor/GCDWebServer/GCDWebServer.xcodeproj; sourceTree = ""; }; 02A3C7A41BC4341500B1A7BE /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; 02B29A161B7CF7C9008A7E6B /* RealmReact.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RealmReact.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 02B58CB11AE99CEC009B348C /* RealmJS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RealmJS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -285,6 +289,8 @@ 029445961BDEDF46006D1617 /* external_commit_helper.hpp */, 02786E311BF1065000937061 /* parser.cpp */, 02786E321BF1065000937061 /* parser.hpp */, + 0294465C1BF27DE6006D1617 /* query_builder.cpp */, + 0294465D1BF27DE6006D1617 /* query_builder.hpp */, 0270BC3E1B7CFC0D00010E03 /* RealmJS.h */, 0270BC3F1B7CFC0D00010E03 /* RealmJS.mm */, 02258FB11BC6E2D00075F13A /* RealmRPC.hpp */, @@ -412,6 +418,7 @@ 0270BC4C1B7CFC0D00010E03 /* RealmJS.h in Headers */, 02258FB31BC6E2D00075F13A /* RealmRPC.hpp in Headers */, 0270BC4F1B7CFC0D00010E03 /* RJSList.hpp in Headers */, + 0294465F1BF27DE6006D1617 /* query_builder.hpp in Headers */, 0270BC541B7CFC0D00010E03 /* RJSResults.hpp in Headers */, 02D0F23B1BF6C95200B4FC45 /* binding_context.hpp in Headers */, 0270BC581B7CFC0D00010E03 /* RJSUtil.hpp in Headers */, @@ -673,6 +680,7 @@ 0270BC551B7CFC0D00010E03 /* RJSResults.mm in Sources */, 02C0864E1BCDB27000942F9C /* list.cpp in Sources */, 0270BC6B1B7CFC1C00010E03 /* object_store.cpp in Sources */, + 0294465E1BF27DE6006D1617 /* query_builder.cpp in Sources */, 0270BC701B7CFC1C00010E03 /* shared_realm.cpp in Sources */, F6BB7DF11BF681BC00D0A69E /* base64.cpp in Sources */, 0270BC4E1B7CFC0D00010E03 /* RJSList.cpp in Sources */, diff --git a/src/RJSResults.mm b/src/RJSResults.mm index 9f19249e..b1c287d9 100644 --- a/src/RJSResults.mm +++ b/src/RJSResults.mm @@ -7,6 +7,7 @@ #import "object_accessor.hpp" #import "results.hpp" #import "parser.hpp" +#import "query_builder.hpp" using namespace realm; @@ -113,7 +114,7 @@ JSObjectRef RJSResultsCreate(JSContextRef ctx, SharedRealm realm, std::string cl throw std::runtime_error("Object type '" + className + "' not present in Realm."); } parser::Predicate predicate = parser::parse(queryString); - parser::apply_predicate(query, predicate, schema, object_schema->name); + query_builder::apply_predicate(query, predicate, schema, object_schema->name); return RJSWrapObject(ctx, RJSResultsClass(), new Results(realm, *object_schema, std::move(query))); } diff --git a/src/object-store/parser/parser.cpp b/src/object-store/parser/parser.cpp index be632cac..03dbd39d 100644 --- a/src/object-store/parser/parser.cpp +++ b/src/object-store/parser/parser.cpp @@ -24,10 +24,6 @@ #include #include -#include -#include "object_store.hpp" -#include "schema.hpp" - using namespace pegtl; namespace realm { @@ -296,355 +292,6 @@ Predicate parse(const std::string &query) return std::move(out_predicate); } - -// check a precondition and throw an exception if it is not met -// this should be used iff the condition being false indicates a bug in the caller -// of the function checking its preconditions -static void precondition(bool condition, const std::string message) { - if (__builtin_expect(condition, 1)) { - return; - } - throw std::runtime_error(message); -} - -// FIXME: TrueExpression and FalseExpression should be supported by core in some way -struct TrueExpression : realm::Expression { - size_t find_first(size_t start, size_t end) const override - { - if (start != end) - return start; - - return not_found; - } - void set_table() override {} - const Table* get_table() const override { return nullptr; } -}; - -struct FalseExpression : realm::Expression { - size_t find_first(size_t, size_t) const override { return not_found; } - void set_table() override {} - const Table* get_table() const override { return nullptr; } -}; - - -// add a clause for numeric constraints based on operator type -template -void add_numeric_constraint_to_query(Query& query, - PropertyType datatype, - Predicate::Operator operatorType, - A lhs, - B rhs) -{ - switch (operatorType) { - case Predicate::Operator::LessThan: - query.and_query(lhs < rhs); - break; - case Predicate::Operator::LessThanOrEqual: - query.and_query(lhs <= rhs); - break; - case Predicate::Operator::GreaterThan: - query.and_query(lhs > rhs); - break; - case Predicate::Operator::GreaterThanOrEqual: - query.and_query(lhs >= rhs); - break; - case Predicate::Operator::Equal: - query.and_query(lhs == rhs); - break; - case Predicate::Operator::NotEqual: - query.and_query(lhs != rhs); - break; - default: - throw std::runtime_error("Unsupported operator for numeric queries."); - } -} - -template -void add_bool_constraint_to_query(Query &query, Predicate::Operator operatorType, A lhs, B rhs) { - switch (operatorType) { - case Predicate::Operator::Equal: - query.and_query(lhs == rhs); - break; - case Predicate::Operator::NotEqual: - query.and_query(lhs != rhs); - break; - default: - throw std::runtime_error("Unsupported operator for numeric queries."); - } -} - -void add_string_constraint_to_query(Query &query, - Predicate::Operator op, - Columns &&column, - StringData value) { - bool case_sensitive = true; - StringData sd = value; - switch (op) { - case Predicate::Operator::BeginsWith: - query.and_query(column.begins_with(sd, case_sensitive)); - break; - case Predicate::Operator::EndsWith: - query.and_query(column.ends_with(sd, case_sensitive)); - break; - case Predicate::Operator::Contains: - query.and_query(column.contains(sd, case_sensitive)); - break; - case Predicate::Operator::Equal: - query.and_query(column.equal(sd, case_sensitive)); - break; - case Predicate::Operator::NotEqual: - query.and_query(column.not_equal(sd, case_sensitive)); - break; - default: - throw std::runtime_error("Unsupported operator for string queries."); - } -} - -void add_string_constraint_to_query(realm::Query& query, - Predicate::Operator op, - StringData value, - Columns &&column) { - bool case_sensitive = true; - StringData sd = value; - switch (op) { - case Predicate::Operator::Equal: - query.and_query(column.equal(sd, case_sensitive)); - break; - case Predicate::Operator::NotEqual: - query.and_query(column.not_equal(sd, case_sensitive)); - break; - default: - throw std::runtime_error("Substring comparison not supported for keypath substrings."); - } -} - -template -struct ColumnOfTypeHelper { - static Columns convert(TableGetter&& table, unsigned int idx) - { - return table()->template column(idx); - } -}; - -template -struct ColumnOfTypeHelper { - static Columns convert(TableGetter&& table, unsigned int idx) - { - return table()->template column(idx); - } -}; - -template -struct ValueOfTypeHelper; - -template -struct ValueOfTypeHelper { - static Int convert(TableGetter&&, const std::string & value) - { - assert(0); - } -}; - -template -struct ValueOfTypeHelper { - static bool convert(TableGetter&&, const std::string & value) - { - assert(0); - } -}; - -template -struct ValueOfTypeHelper { - static Double convert(TableGetter&&, const std::string & value) - { - return std::stod(value); - } -}; - -template -struct ValueOfTypeHelper { - static Float convert(TableGetter&&, const std::string & value) - { - return std::stof(value); - } -}; - -template -struct ValueOfTypeHelper { - static Int convert(TableGetter&&, const std::string & value) - { - return std::stoll(value); - } -}; - -template -struct ValueOfTypeHelper { - static std::string convert(TableGetter&&, const std::string & value) - { - return value; - } -}; - -template -auto value_of_type_for_query(TableGetter&& tables, Value&& value) -{ - const bool isColumnIndex = std::is_same::type>::value; - using helper = std::conditional_t, - ValueOfTypeHelper>; - return helper::convert(std::forward(tables), std::forward(value)); -} - -std::vector &split(const std::string &s, char delim, std::vector &elems) { - std::stringstream ss(s); - std::string item; - while (std::getline(ss, item, delim)) { - elems.push_back(item); - } - return elems; -} - -std::vector split(const std::string &s, char delim) { - std::vector elems; - split(s, delim, elems); - return elems; -} - -Property *get_property_from_key_path(Schema &schema, ObjectSchema &desc, const std::string &key_path, std::vector &indexes) -{ - Property *prop = nullptr; - - auto paths = split(key_path, '.'); - for (size_t index = 0; index < paths.size(); index++) { - if (prop) { - precondition(prop->type == PropertyTypeObject || prop->type == PropertyTypeArray, - (std::string)"Property '" + paths[index] + "' is not a link in object of type '" + desc.name + "'"); - indexes.push_back(prop->table_column); - - } - prop = desc.property_for_name(paths[index]); - precondition(prop != nullptr, "No property '" + paths[index] + "' on object of type '" + desc.name + "'"); - - if (prop->object_type.size()) { - desc = *schema.find(prop->object_type); - } - } - return prop; -} - -template - void do_add_comparison_to_query(Query &query, Schema &schema, ObjectSchema &object_schema, Property *prop, - Predicate::Operator op, const std::vector& indexes, T... values) -{ - auto table = [&] { - TableRef& tbl = query.get_table(); - for (size_t col : indexes) { - tbl->link(col); // mutates m_link_chain on table - } - return tbl.get(); - }; - - auto type = prop->type; - switch (type) { - case PropertyTypeBool: - add_bool_constraint_to_query(query, op, value_of_type_for_query(table, values)...); - break; - case PropertyTypeDate: - add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); - break; - case PropertyTypeDouble: - add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); - break; - case PropertyTypeFloat: - add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); - break; - case PropertyTypeInt: - add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); - break; - case PropertyTypeString: - case PropertyTypeData: - add_string_constraint_to_query(query, op, value_of_type_for_query(table, values)...); - 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, Schema &schema, ObjectSchema &object_schema) -{ - std::vector indexes; - Predicate::Comparison &cmpr = pred.cmpr; - auto t0 = cmpr.expr[0].type, t1 = cmpr.expr[1].type; - if (t0 == Expression::Type::KeyPath && t1 != Expression::Type::KeyPath) { - Property *prop = get_property_from_key_path(schema, object_schema, cmpr.expr[0].s, indexes); - do_add_comparison_to_query(query, schema, object_schema, prop, cmpr.op, indexes, prop->table_column, cmpr.expr[1].s); - } - else if (t0 != Expression::Type::KeyPath && t1 == Expression::Type::KeyPath) { - Property *prop = get_property_from_key_path(schema, object_schema, cmpr.expr[1].s, indexes); - do_add_comparison_to_query(query, schema, object_schema, prop, cmpr.op, indexes, cmpr.expr[0].s, prop->table_column); - } - 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, Schema &schema, ObjectSchema &object_schema) -{ - if (pred.negate) { - query.Not(); - } - - switch (pred.type) { - case Predicate::Type::And: - query.group(); - for (auto &sub : pred.cpnd.sub_predicates) { - update_query_with_predicate(query, sub, schema, object_schema); - } - if (!pred.cpnd.sub_predicates.size()) { - query.and_query(new TrueExpression); - } - query.end_group(); - break; - - case Predicate::Type::Or: - query.group(); - for (auto &sub : pred.cpnd.sub_predicates) { - query.Or(); - update_query_with_predicate(query, sub, schema, object_schema); - } - if (!pred.cpnd.sub_predicates.size()) { - query.and_query(new FalseExpression); - } - query.end_group(); - break; - - case Predicate::Type::Comparison: { - add_comparison_to_query(query, pred, schema, object_schema); - break; - } - case Predicate::Type::True: - query.and_query(new TrueExpression); - break; - - case Predicate::Type::False: - query.and_query(new FalseExpression); - break; - - default: - throw std::runtime_error("Invalid predicate type"); - break; - } -} - -void apply_predicate(Query &query, Predicate &predicate, Schema &schema, std::string objectType) { - update_query_with_predicate(query, predicate, schema, *schema.find(objectType)); - - // Test the constructed query in core - std::string validateMessage = query.validate(); - precondition(validateMessage.empty(), validateMessage.c_str()); -} - }} diff --git a/src/object-store/parser/parser.hpp b/src/object-store/parser/parser.hpp index 117ca833..568c446b 100644 --- a/src/object-store/parser/parser.hpp +++ b/src/object-store/parser/parser.hpp @@ -16,14 +16,13 @@ // //////////////////////////////////////////////////////////////////////////// -#ifndef REALM_EXTERNAL_COMMIT_HELPER_HPP -#define REALM_EXTERNAL_COMMIT_HELPER_HPP +#ifndef REALM_PARSER_HPP +#define REALM_PARSER_HPP #include #include namespace realm { - class Query; class Schema; namespace parser { @@ -77,9 +76,7 @@ namespace realm { }; Predicate parse(const std::string &query); - - void apply_predicate(Query &query, Predicate &predicate, Schema &schema, std::string objectType); } } -#endif // REALM_EXTERNAL_COMMIT_HELPER_HPP +#endif // REALM_PARSER_HPP diff --git a/src/object-store/parser/query_builder.cpp b/src/object-store/parser/query_builder.cpp new file mode 100644 index 00000000..004b821e --- /dev/null +++ b/src/object-store/parser/query_builder.cpp @@ -0,0 +1,381 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "query_builder.hpp" +#include "parser.hpp" + +#include +#include "object_store.hpp" +#include "schema.hpp" + +#include + +namespace realm { +namespace query_builder { +using namespace parser; + +// check a precondition and throw an exception if it is not met +// this should be used iff the condition being false indicates a bug in the caller +// of the function checking its preconditions +static void precondition(bool condition, const std::string message) { + if (__builtin_expect(condition, 1)) { + return; + } + throw std::runtime_error(message); +} + +// FIXME: TrueExpression and FalseExpression should be supported by core in some way +struct TrueExpression : realm::Expression { + size_t find_first(size_t start, size_t end) const override + { + if (start != end) + return start; + + return not_found; + } + void set_table() override {} + const Table* get_table() const override { return nullptr; } +}; + +struct FalseExpression : realm::Expression { + size_t find_first(size_t, size_t) const override { return not_found; } + void set_table() override {} + const Table* get_table() const override { return nullptr; } +}; + + +// add a clause for numeric constraints based on operator type +template +void add_numeric_constraint_to_query(Query& query, + PropertyType datatype, + Predicate::Operator operatorType, + A lhs, + B rhs) +{ + switch (operatorType) { + case Predicate::Operator::LessThan: + query.and_query(lhs < rhs); + break; + case Predicate::Operator::LessThanOrEqual: + query.and_query(lhs <= rhs); + break; + case Predicate::Operator::GreaterThan: + query.and_query(lhs > rhs); + break; + case Predicate::Operator::GreaterThanOrEqual: + query.and_query(lhs >= rhs); + break; + case Predicate::Operator::Equal: + query.and_query(lhs == rhs); + break; + case Predicate::Operator::NotEqual: + query.and_query(lhs != rhs); + break; + default: + throw std::runtime_error("Unsupported operator for numeric queries."); + } +} + +template +void add_bool_constraint_to_query(Query &query, Predicate::Operator operatorType, A lhs, B rhs) { + switch (operatorType) { + case Predicate::Operator::Equal: + query.and_query(lhs == rhs); + break; + case Predicate::Operator::NotEqual: + query.and_query(lhs != rhs); + break; + default: + throw std::runtime_error("Unsupported operator for numeric queries."); + } +} + +void add_string_constraint_to_query(Query &query, + Predicate::Operator op, + Columns &&column, + StringData value) { + bool case_sensitive = true; + StringData sd = value; + switch (op) { + case Predicate::Operator::BeginsWith: + query.and_query(column.begins_with(sd, case_sensitive)); + break; + case Predicate::Operator::EndsWith: + query.and_query(column.ends_with(sd, case_sensitive)); + break; + case Predicate::Operator::Contains: + query.and_query(column.contains(sd, case_sensitive)); + break; + case Predicate::Operator::Equal: + query.and_query(column.equal(sd, case_sensitive)); + break; + case Predicate::Operator::NotEqual: + query.and_query(column.not_equal(sd, case_sensitive)); + break; + default: + throw std::runtime_error("Unsupported operator for string queries."); + } +} + +void add_string_constraint_to_query(realm::Query& query, + Predicate::Operator op, + StringData value, + Columns &&column) { + bool case_sensitive = true; + StringData sd = value; + switch (op) { + case Predicate::Operator::Equal: + query.and_query(column.equal(sd, case_sensitive)); + break; + case Predicate::Operator::NotEqual: + query.and_query(column.not_equal(sd, case_sensitive)); + break; + default: + throw std::runtime_error("Substring comparison not supported for keypath substrings."); + } +} + +template +struct ColumnOfTypeHelper { + static Columns convert(TableGetter&& table, unsigned int idx) + { + return table()->template column(idx); + } +}; + +template +struct ColumnOfTypeHelper { + static Columns convert(TableGetter&& table, unsigned int idx) + { + return table()->template column(idx); + } +}; + +template +struct ValueOfTypeHelper; + +template +struct ValueOfTypeHelper { + static Int convert(TableGetter&&, const std::string & value) + { + assert(0); + } +}; + +template +struct ValueOfTypeHelper { + static bool convert(TableGetter&&, const std::string & value) + { + assert(0); + } +}; + +template +struct ValueOfTypeHelper { + static Double convert(TableGetter&&, const std::string & value) + { + return std::stod(value); + } +}; + +template +struct ValueOfTypeHelper { + static Float convert(TableGetter&&, const std::string & value) + { + return std::stof(value); + } +}; + +template +struct ValueOfTypeHelper { + static Int convert(TableGetter&&, const std::string & value) + { + return std::stoll(value); + } +}; + +template +struct ValueOfTypeHelper { + static std::string convert(TableGetter&&, const std::string & value) + { + return value; + } +}; + +template +auto value_of_type_for_query(TableGetter&& tables, Value&& value) +{ + const bool isColumnIndex = std::is_same::type>::value; + using helper = std::conditional_t, + ValueOfTypeHelper>; + return helper::convert(std::forward(tables), std::forward(value)); +} + +std::vector &split(const std::string &s, char delim, std::vector &elems) { + std::stringstream ss(s); + std::string item; + while (std::getline(ss, item, delim)) { + elems.push_back(item); + } + return elems; +} + +std::vector split(const std::string &s, char delim) { + std::vector elems; + split(s, delim, elems); + return elems; +} + +Property *get_property_from_key_path(Schema &schema, ObjectSchema &desc, const std::string &key_path, std::vector &indexes) +{ + Property *prop = nullptr; + + auto paths = split(key_path, '.'); + for (size_t index = 0; index < paths.size(); index++) { + if (prop) { + precondition(prop->type == PropertyTypeObject || prop->type == PropertyTypeArray, + (std::string)"Property '" + paths[index] + "' is not a link in object of type '" + desc.name + "'"); + indexes.push_back(prop->table_column); + + } + prop = desc.property_for_name(paths[index]); + precondition(prop != nullptr, "No property '" + paths[index] + "' on object of type '" + desc.name + "'"); + + if (prop->object_type.size()) { + desc = *schema.find(prop->object_type); + } + } + return prop; +} + +template +void do_add_comparison_to_query(Query &query, Schema &schema, ObjectSchema &object_schema, Property *prop, + Predicate::Operator op, const std::vector& indexes, T... values) +{ + auto table = [&] { + TableRef& tbl = query.get_table(); + for (size_t col : indexes) { + tbl->link(col); // mutates m_link_chain on table + } + return tbl.get(); + }; + + auto type = prop->type; + switch (type) { + case PropertyTypeBool: + add_bool_constraint_to_query(query, op, value_of_type_for_query(table, values)...); + break; + case PropertyTypeDate: + add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); + break; + case PropertyTypeDouble: + add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); + break; + case PropertyTypeFloat: + add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); + break; + case PropertyTypeInt: + add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); + break; + case PropertyTypeString: + case PropertyTypeData: + add_string_constraint_to_query(query, op, value_of_type_for_query(table, values)...); + 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, Schema &schema, ObjectSchema &object_schema) +{ + std::vector indexes; + Predicate::Comparison &cmpr = pred.cmpr; + auto t0 = cmpr.expr[0].type, t1 = cmpr.expr[1].type; + if (t0 == parser::Expression::Type::KeyPath && t1 != parser::Expression::Type::KeyPath) { + Property *prop = get_property_from_key_path(schema, object_schema, cmpr.expr[0].s, indexes); + do_add_comparison_to_query(query, schema, object_schema, prop, cmpr.op, indexes, prop->table_column, cmpr.expr[1].s); + } + else if (t0 != parser::Expression::Type::KeyPath && t1 == parser::Expression::Type::KeyPath) { + Property *prop = get_property_from_key_path(schema, object_schema, cmpr.expr[1].s, indexes); + do_add_comparison_to_query(query, schema, object_schema, prop, cmpr.op, indexes, cmpr.expr[0].s, prop->table_column); + } + 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, Schema &schema, ObjectSchema &object_schema) +{ + if (pred.negate) { + query.Not(); + } + + switch (pred.type) { + case Predicate::Type::And: + query.group(); + for (auto &sub : pred.cpnd.sub_predicates) { + update_query_with_predicate(query, sub, schema, object_schema); + } + if (!pred.cpnd.sub_predicates.size()) { + query.and_query(new TrueExpression); + } + query.end_group(); + break; + + case Predicate::Type::Or: + query.group(); + for (auto &sub : pred.cpnd.sub_predicates) { + query.Or(); + update_query_with_predicate(query, sub, schema, object_schema); + } + if (!pred.cpnd.sub_predicates.size()) { + query.and_query(new FalseExpression); + } + query.end_group(); + break; + + case Predicate::Type::Comparison: { + add_comparison_to_query(query, pred, schema, object_schema); + break; + } + case Predicate::Type::True: + query.and_query(new TrueExpression); + break; + + case Predicate::Type::False: + query.and_query(new FalseExpression); + break; + + default: + throw std::runtime_error("Invalid predicate type"); + break; + } +} + +void apply_predicate(Query &query, Predicate &predicate, Schema &schema, std::string objectType) +{ + update_query_with_predicate(query, predicate, schema, *schema.find(objectType)); + + // Test the constructed query in core + std::string validateMessage = query.validate(); + precondition(validateMessage.empty(), validateMessage.c_str()); +} + +}} diff --git a/src/object-store/parser/query_builder.hpp b/src/object-store/parser/query_builder.hpp new file mode 100644 index 00000000..8a408ec8 --- /dev/null +++ b/src/object-store/parser/query_builder.hpp @@ -0,0 +1,34 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef REALM_QUERY_BUILDER_HPP +#define REALM_QUERY_BUILDER_HPP + +#include +#include "parser.hpp" + +namespace realm { + class Query; + class Schema; + + namespace query_builder { + void apply_predicate(Query &query, parser::Predicate &predicate, Schema &schema, std::string objectType); + } +} + +#endif // REALM_QUERY_BUILDER_HPP