From c7c0698ad0067b10daf8d5a2003aca9b8fa59904 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 9 Nov 2015 21:12:18 -0800 Subject: [PATCH] hook it up --- src/RJSResults.mm | 12 +- src/RJSUtil.mm | 539 ----------------------------- src/object-store/parser/parser.cpp | 351 +++++++++++++++++++ src/object-store/parser/parser.hpp | 5 + src/object-store/parser/query.abnf | 8 +- 5 files changed, 364 insertions(+), 551 deletions(-) diff --git a/src/RJSResults.mm b/src/RJSResults.mm index a00d0e84..9f19249e 100644 --- a/src/RJSResults.mm +++ b/src/RJSResults.mm @@ -6,6 +6,7 @@ #import "RJSObject.hpp" #import "object_accessor.hpp" #import "results.hpp" +#import "parser.hpp" using namespace realm; @@ -103,8 +104,6 @@ JSObjectRef RJSResultsCreate(JSContextRef ctx, SharedRealm realm, std::string cl } -void RLMUpdateQueryWithPredicate(realm::Query *query, NSPredicate *predicate, realm::Schema &schema, realm::ObjectSchema &objectSchema); - JSObjectRef RJSResultsCreate(JSContextRef ctx, SharedRealm realm, std::string className, std::string queryString) { TableRef table = ObjectStore::table_for_object_type(realm->read_group(), className); Query query = table->where(); @@ -113,12 +112,9 @@ JSObjectRef RJSResultsCreate(JSContextRef ctx, SharedRealm realm, std::string cl if (object_schema == realm->config().schema->end()) { throw std::runtime_error("Object type '" + className + "' not present in Realm."); } - @try { - RLMUpdateQueryWithPredicate(&query, [NSPredicate predicateWithFormat:@(queryString.c_str())], schema, *object_schema); - } - @catch(NSException *ex) { - throw std::runtime_error(ex.description.UTF8String); - } + parser::Predicate predicate = parser::parse(queryString); + parser::apply_predicate(query, predicate, schema, object_schema->name); + return RJSWrapObject(ctx, RJSResultsClass(), new Results(realm, *object_schema, std::move(query))); } diff --git a/src/RJSUtil.mm b/src/RJSUtil.mm index 20993469..d1ab0193 100644 --- a/src/RJSUtil.mm +++ b/src/RJSUtil.mm @@ -98,542 +98,3 @@ bool RJSIsValueDate(JSContextRef ctx, JSValueRef value) { return RJSIsValueObjectOfType(ctx, value, dateString); } - -#include -#include "object_store.hpp" - -// 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 RLMPrecondition(bool condition, NSString *name, NSString *format, ...) { - if (__builtin_expect(condition, 1)) { - return; - } - - va_list args; - va_start(args, format); - NSString *reason = [[NSString alloc] initWithFormat:format arguments:args]; - va_end(args); - - throw std::runtime_error(reason.UTF8String); -} - -// 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 realm::not_found; - } - void set_table() override {} - const realm::Table* get_table() const override { return nullptr; } -}; - -struct FalseExpression : realm::Expression { - size_t find_first(size_t, size_t) const override { return realm::not_found; } - void set_table() override {} - const realm::Table* get_table() const override { return nullptr; } -}; - -NSString *operatorName(NSPredicateOperatorType operatorType) -{ - switch (operatorType) { - case NSLessThanPredicateOperatorType: - return @"<"; - case NSLessThanOrEqualToPredicateOperatorType: - return @"<="; - case NSGreaterThanPredicateOperatorType: - return @">"; - case NSGreaterThanOrEqualToPredicateOperatorType: - return @">="; - case NSEqualToPredicateOperatorType: - return @"=="; - case NSNotEqualToPredicateOperatorType: - return @"!="; - case NSMatchesPredicateOperatorType: - return @"MATCHES"; - case NSLikePredicateOperatorType: - return @"LIKE"; - case NSBeginsWithPredicateOperatorType: - return @"BEGINSWITH"; - case NSEndsWithPredicateOperatorType: - return @"ENDSWITH"; - case NSInPredicateOperatorType: - return @"IN"; - case NSContainsPredicateOperatorType: - return @"CONTAINS"; - case NSBetweenPredicateOperatorType: - return @"BETWEENS"; - case NSCustomSelectorPredicateOperatorType: - return @"custom selector"; - } - - return [NSString stringWithFormat:@"unknown operator %lu", (unsigned long)operatorType]; -} - -// add a clause for numeric constraints based on operator type -template -void add_numeric_constraint_to_query(realm::Query& query, - realm::PropertyType datatype, - NSPredicateOperatorType operatorType, - A lhs, - B rhs) -{ - switch (operatorType) { - case NSLessThanPredicateOperatorType: - query.and_query(lhs < rhs); - break; - case NSLessThanOrEqualToPredicateOperatorType: - query.and_query(lhs <= rhs); - break; - case NSGreaterThanPredicateOperatorType: - query.and_query(lhs > rhs); - break; - case NSGreaterThanOrEqualToPredicateOperatorType: - query.and_query(lhs >= rhs); - break; - case NSEqualToPredicateOperatorType: - query.and_query(lhs == rhs); - break; - case NSNotEqualToPredicateOperatorType: - query.and_query(lhs != rhs); - break; - default: { - NSString *error = [NSString stringWithFormat:@"Operator '%@' not supported for type %s", operatorName(operatorType), realm::string_for_property_type(datatype)]; - throw std::runtime_error(error.UTF8String); - } - } -} - -template -void add_bool_constraint_to_query(realm::Query &query, NSPredicateOperatorType operatorType, A lhs, B rhs) { - switch (operatorType) { - case NSEqualToPredicateOperatorType: - query.and_query(lhs == rhs); - break; - case NSNotEqualToPredicateOperatorType: - query.and_query(lhs != rhs); - break; - default: { - NSString *error = [NSString stringWithFormat:@"Operator '%@' not supported for bool type", operatorName(operatorType)]; - throw std::runtime_error(error.UTF8String); - } - } -} - -void add_string_constraint_to_query(realm::Query &query, - NSPredicateOperatorType operatorType, - NSComparisonPredicateOptions predicateOptions, - realm::Columns &&column, - NSString *value) { - bool caseSensitive = !(predicateOptions & NSCaseInsensitivePredicateOption); - bool diacriticInsensitive = (predicateOptions & NSDiacriticInsensitivePredicateOption); - RLMPrecondition(!diacriticInsensitive, @"Invalid predicate option", - @"NSDiacriticInsensitivePredicateOption not supported for string type"); - - realm::StringData sd = value.UTF8String; - switch (operatorType) { - case NSBeginsWithPredicateOperatorType: - query.and_query(column.begins_with(sd, caseSensitive)); - break; - case NSEndsWithPredicateOperatorType: - query.and_query(column.ends_with(sd, caseSensitive)); - break; - case NSContainsPredicateOperatorType: - query.and_query(column.contains(sd, caseSensitive)); - break; - case NSEqualToPredicateOperatorType: - query.and_query(column.equal(sd, caseSensitive)); - break; - case NSNotEqualToPredicateOperatorType: - query.and_query(column.not_equal(sd, caseSensitive)); - break; - default: { - NSString *error = [NSString stringWithFormat:@"Operator '%@' not supported for string type", operatorName(operatorType)]; - throw std::runtime_error(error.UTF8String); - } - } -} - -void add_string_constraint_to_query(realm::Query& query, - NSPredicateOperatorType operatorType, - NSComparisonPredicateOptions predicateOptions, - NSString *value, - realm::Columns&& column) { - switch (operatorType) { - case NSEqualToPredicateOperatorType: - case NSNotEqualToPredicateOperatorType: - add_string_constraint_to_query(query, operatorType, predicateOptions, std::move(column), value); - break; - default: { - NSString *error = [NSString stringWithFormat:@"Operator '%@' is not supported for string type with key path on right side of operator", - operatorName(operatorType)]; - throw std::runtime_error(error.UTF8String); - } - } -} - -template -static inline T *RLMDynamicCast(__unsafe_unretained id obj) { - if ([obj isKindOfClass:[T class]]) { - return obj; - } - return nil; -} - -id value_from_constant_expression_or_value(id value) { - if (NSExpression *exp = RLMDynamicCast(value)) { - RLMPrecondition(exp.expressionType == NSConstantValueExpressionType, - @"Invalid value", - @"Expressions within predicate aggregates must be constant values"); - return exp.constantValue; - } - return value; -} - -// iterate over an array of subpredicates, using @func to build a query from each -// one and ORing them together -template -void process_or_group(realm::Query &query, id array, Func&& func) { - RLMPrecondition([array conformsToProtocol:@protocol(NSFastEnumeration)], - @"Invalid value", @"IN clause requires an array of items"); - - query.group(); - - bool first = true; - for (id item in array) { - if (!first) { - query.Or(); - } - first = false; - - func(item); - } - - if (first) { - // Queries can't be empty, so if there's zero things in the OR group - // validation will fail. Work around this by adding an expression which - // will never find any rows in a table. - query.and_query(new FalseExpression); - } - - query.end_group(); -} - -template -struct ColumnOfTypeHelper { - static realm::Columns convert(TableGetter&& table, NSUInteger idx) - { - return table()->template column(idx); - } -}; - -template -struct ColumnOfTypeHelper { - static realm::Columns convert(TableGetter&& table, NSUInteger idx) - { - return table()->template column(idx); - } -}; - -template -struct ValueOfTypeHelper; - -template -struct ValueOfTypeHelper { - static realm::Int convert(TableGetter&&, id value) - { - return [value timeIntervalSince1970]; - } -}; - -template -struct ValueOfTypeHelper { - static bool convert(TableGetter&&, id value) - { - return [value boolValue]; - } -}; - -template -struct ValueOfTypeHelper { - static realm::Double convert(TableGetter&&, id value) - { - return [value doubleValue]; - } -}; - -template -struct ValueOfTypeHelper { - static realm::Float convert(TableGetter&&, id value) - { - return [value floatValue]; - } -}; - -template -struct ValueOfTypeHelper { - static realm::Int convert(TableGetter&&, id value) - { - return [value longLongValue]; - } -}; - -template -struct ValueOfTypeHelper { - static id convert(TableGetter&&, id 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)); -} - -template -void add_constraint_to_query(realm::Query &query, realm::PropertyType type, - NSPredicateOperatorType operatorType, - NSComparisonPredicateOptions predicateOptions, - std::vector linkColumns, T... values) -{ - static_assert(sizeof...(T) == 2, "add_constraint_to_query accepts only two values as arguments"); - - auto table = [&] { - realm::TableRef& tbl = query.get_table(); - for (NSUInteger col : linkColumns) { - tbl->link(col); // mutates m_link_chain on table - } - return tbl.get(); - }; - - switch (type) { - case realm::PropertyTypeBool: - add_bool_constraint_to_query(query, operatorType, value_of_type_for_query(table, values)...); - break; - case realm::PropertyTypeDate: - add_numeric_constraint_to_query(query, type, operatorType, value_of_type_for_query(table, values)...); - break; - case realm::PropertyTypeDouble: - add_numeric_constraint_to_query(query, type, operatorType, value_of_type_for_query(table, values)...); - break; - case realm::PropertyTypeFloat: - add_numeric_constraint_to_query(query, type, operatorType, value_of_type_for_query(table, values)...); - break; - case realm::PropertyTypeInt: - add_numeric_constraint_to_query(query, type, operatorType, value_of_type_for_query(table, values)...); - break; - case realm::PropertyTypeString: - case realm::PropertyTypeData: - add_string_constraint_to_query(query, operatorType, predicateOptions, value_of_type_for_query(table, values)...); - break; - default: { - NSString *error = [NSString stringWithFormat:@"Object type %s not supported", realm::string_for_property_type(type)]; - throw std::runtime_error(error.UTF8String); - } - } -} - - -realm::Property *get_property_from_key_path(realm::Schema &schema, realm::ObjectSchema &desc, - NSString *keyPath, std::vector &indexes, bool isAny) -{ - realm::Property *prop = nullptr; - - NSString *prevPath = nil; - NSUInteger start = 0, length = keyPath.length, end = NSNotFound; - do { - end = [keyPath rangeOfString:@"." options:0 range:{start, length - start}].location; - NSString *path = [keyPath substringWithRange:{start, end == NSNotFound ? length - start : end - start}]; - if (prop) { - RLMPrecondition(prop->type == realm::PropertyTypeObject || prop->type == realm::PropertyTypeArray, - @"Invalid value", @"Property '%@' is not a link in object of type '%s'", prevPath, desc.name.c_str()); - indexes.push_back(prop->table_column); - prop = desc.property_for_name(path.UTF8String); - RLMPrecondition(prop, @"Invalid property name", - @"Property '%@' not found in object of type '%s'", path, desc.name.c_str()); - } - else { - prop = desc.property_for_name(path.UTF8String); - RLMPrecondition(prop, @"Invalid property name", - @"Property '%@' not found in object of type '%s'", path, desc.name.c_str()); - - if (isAny) { - RLMPrecondition(prop->type == realm::PropertyTypeArray, - @"Invalid predicate", - @"ANY modifier can only be used for RLMArray properties"); - } - else { - RLMPrecondition(prop->type != realm::PropertyTypeArray, - @"Invalid predicate", - @"RLMArray predicates must contain the ANY modifier"); - } - } - - if (prop->object_type.length()) { - desc = *schema.find(prop->object_type); - } - prevPath = path; - start = end + 1; - } while (end != NSNotFound); - - return prop; -} - -void update_query_with_value_expression(realm::Schema &schema, - realm::ObjectSchema &desc, - realm::Query &query, - NSString *keyPath, - id value, - NSComparisonPredicate *pred) -{ - bool isAny = pred.comparisonPredicateModifier == NSAnyPredicateModifier; - std::vector indexes; - realm::Property *prop = get_property_from_key_path(schema, desc, keyPath, indexes, isAny); - - NSUInteger index = prop->table_column; - - // check to see if this is a between query - if (pred.predicateOperatorType == NSBetweenPredicateOperatorType) { - throw std::runtime_error("BETWEEN queries not supported"); - } - - // turn IN into ored together == - if (pred.predicateOperatorType == NSInPredicateOperatorType) { - process_or_group(query, value, [&](id item) { - id normalized = value_from_constant_expression_or_value(item); - add_constraint_to_query(query, prop->type, NSEqualToPredicateOperatorType, - pred.options, indexes, index, normalized); - }); - return; - } - - if (pred.leftExpression.expressionType == NSKeyPathExpressionType) { - add_constraint_to_query(query, prop->type, pred.predicateOperatorType, - pred.options, indexes, index, value); - } else { - add_constraint_to_query(query, prop->type, pred.predicateOperatorType, - pred.options, indexes, value, index); - } -} - - -void update_query_with_predicate(NSPredicate *predicate, realm::Schema &schema, realm::ObjectSchema &objectSchema, realm::Query &query) -{ - // Compound predicates. - if ([predicate isMemberOfClass:[NSCompoundPredicate class]]) { - NSCompoundPredicate *comp = (NSCompoundPredicate *)predicate; - - switch ([comp compoundPredicateType]) { - case NSAndPredicateType: - if (comp.subpredicates.count) { - // Add all of the subpredicates. - query.group(); - for (NSPredicate *subp in comp.subpredicates) { - update_query_with_predicate(subp, schema, objectSchema, query); - } - query.end_group(); - } else { - // NSCompoundPredicate's documentation states that an AND predicate with no subpredicates evaluates to TRUE. - query.and_query(new TrueExpression); - } - break; - - case NSOrPredicateType: { - // Add all of the subpredicates with ors inbetween. - process_or_group(query, comp.subpredicates, [&](__unsafe_unretained NSPredicate *const subp) { - update_query_with_predicate(subp, schema, objectSchema, query); - }); - break; - } - - case NSNotPredicateType: - // Add the negated subpredicate - query.Not(); - update_query_with_predicate(comp.subpredicates.firstObject, schema, objectSchema, query); - break; - - default: - throw std::runtime_error("Invalid compound predicate type - Only support AND, OR and NOT predicate types"); - } - } - else if ([predicate isMemberOfClass:[NSComparisonPredicate class]]) { - NSComparisonPredicate *compp = (NSComparisonPredicate *)predicate; - - // check modifier - RLMPrecondition(compp.comparisonPredicateModifier != NSAllPredicateModifier, - @"Invalid predicate", @"ALL modifier not supported"); - - NSExpressionType exp1Type = compp.leftExpression.expressionType; - NSExpressionType exp2Type = compp.rightExpression.expressionType; - - if (compp.comparisonPredicateModifier == NSAnyPredicateModifier) { - // for ANY queries - RLMPrecondition(exp1Type == NSKeyPathExpressionType && exp2Type == NSConstantValueExpressionType, - @"Invalid predicate", - @"Predicate with ANY modifier must compare a KeyPath with RLMArray with a value"); - } - - if (compp.predicateOperatorType == NSBetweenPredicateOperatorType || compp.predicateOperatorType == NSInPredicateOperatorType) { - // Inserting an array via %@ gives NSConstantValueExpressionType, but - // including it directly gives NSAggregateExpressionType - if (exp1Type != NSKeyPathExpressionType || (exp2Type != NSAggregateExpressionType && exp2Type != NSConstantValueExpressionType)) { - NSString * error = [NSString stringWithFormat:@"Predicate with %s operator must compare a KeyPath with an aggregate with two values", compp.predicateOperatorType == NSBetweenPredicateOperatorType ? "BETWEEN" : "IN"]; - throw std::runtime_error(error.UTF8String); - } - exp2Type = NSConstantValueExpressionType; - } - - if (exp1Type == NSKeyPathExpressionType && exp2Type == NSConstantValueExpressionType) { - // comparing keypath to value - update_query_with_value_expression(schema, objectSchema, query, compp.leftExpression.keyPath, - compp.rightExpression.constantValue, compp); - } - else if (exp1Type == NSConstantValueExpressionType && exp2Type == NSKeyPathExpressionType) { - // comparing value to keypath - update_query_with_value_expression(schema, objectSchema, query, compp.rightExpression.keyPath, - compp.leftExpression.constantValue, compp); - } - //if (exp1Type == NSKeyPathExpressionType && exp2Type == NSKeyPathExpressionType) { - // both expression are KeyPaths - // update_query_with_column_expression(objectSchema, query, compp.leftExpression.keyPath, - // compp.rightExpression.keyPath, compp); - //} - else { - throw std::runtime_error("Predicate expressions must compare a keypath and another keypath or a constant value"); - } - } - else if ([predicate isEqual:[NSPredicate predicateWithValue:YES]]) { - query.and_query(new TrueExpression); - } else if ([predicate isEqual:[NSPredicate predicateWithValue:NO]]) { - query.and_query(new FalseExpression); - } - else { - // invalid predicate type - throw std::runtime_error("Only support compound, comparison, and constant predicates"); - } -} - -void RLMUpdateQueryWithPredicate(realm::Query *query, NSPredicate *predicate, realm::Schema &schema, realm::ObjectSchema &objectSchema) -{ - // passing a nil predicate is a no-op - if (!predicate) { - return; - } - - RLMPrecondition([predicate isKindOfClass:NSPredicate.class], @"Invalid argument", @"predicate must be an NSPredicate object"); - - update_query_with_predicate(predicate, schema, objectSchema, *query); - - // Test the constructed query in core - std::string validateMessage = query->validate(); - RLMPrecondition(validateMessage.empty(), @"Invalid query", @"%.*s", (int)validateMessage.size(), validateMessage.c_str()); -} diff --git a/src/object-store/parser/parser.cpp b/src/object-store/parser/parser.cpp index 3b504ee9..723332b1 100644 --- a/src/object-store/parser/parser.cpp +++ b/src/object-store/parser/parser.cpp @@ -24,6 +24,10 @@ #include #include +#include +#include "object_store.hpp" +#include "schema.hpp" + using namespace pegtl; namespace realm { @@ -291,9 +295,356 @@ Predicate parse(const std::string &query) state.predicate_stack.push_back(&out_predicate); pegtl::parse< must< pred, eof >, action >(query, source, state); + if (out_predicate.type == Predicate::Type::And && out_predicate.sub_predicates.size() == 1) { + return out_predicate.sub_predicates.back(); + } return 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++) { + prop = desc.property_for_name(paths[index]); + precondition(prop != nullptr, "No property '" + paths[index] + "' on object of type '" + desc.name + "'"); + precondition(index == paths.size() - 1 || 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); + 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; + auto t0 = pred.sub_expressions[0].type, t1 = pred.sub_expressions[1].type; + if (t0 == Expression::Type::KeyPath && t1 != Expression::Type::KeyPath) { + Property *prop = get_property_from_key_path(schema, object_schema, pred.sub_expressions[0].s, indexes); + do_add_comparison_to_query(query, schema, object_schema, prop, pred.op, indexes, prop->table_column, pred.sub_expressions[1].s); + } + else if (t0 != Expression::Type::KeyPath && t1 == Expression::Type::KeyPath) { + Property *prop = get_property_from_key_path(schema, object_schema, pred.sub_expressions[1].s, indexes); + do_add_comparison_to_query(query, schema, object_schema, prop, pred.op, indexes, pred.sub_expressions[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.sub_predicates) { + update_query_with_predicate(query, sub, schema, object_schema); + } + if (!pred.sub_predicates.size()) { + query.and_query(new TrueExpression); + } + query.end_group(); + break; + + case Predicate::Type::Or: + query.group(); + for (auto &sub : pred.sub_predicates) { + query.Or(); + update_query_with_predicate(query, sub, schema, object_schema); + } + if (!pred.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 149f12ff..99dda99d 100644 --- a/src/object-store/parser/parser.hpp +++ b/src/object-store/parser/parser.hpp @@ -23,6 +23,9 @@ #include namespace realm { + class Query; + class Schema; + namespace parser { struct Expression { @@ -74,6 +77,8 @@ namespace realm { }; Predicate parse(const std::string &query); + + void apply_predicate(Query &query, Predicate &predicate, Schema &schema, std::string objectType); } } diff --git a/src/object-store/parser/query.abnf b/src/object-store/parser/query.abnf index d70416d3..d315fd35 100644 --- a/src/object-store/parser/query.abnf +++ b/src/object-store/parser/query.abnf @@ -17,7 +17,7 @@ string = dq-string / sq-string sq-string = "'" *(%x20-ffffffff) "'" dq-string = DQUOTE *("\\" / %x20-21 / %x23-ffffffff) DQUOTE -num = float-num / int-num / hex-num -float-num = ["-"] (*DIGIT "." 1*DIGIT / "." 1*DIGIT) -int-num = ["-"] 1*DIGIT -hex-num = ["-"] ["0"] "x" 1*HEXDIG +num = ["-"] (float-num / int-num / hex-num) +float-num = (*DIGIT "." 1*DIGIT / "." 1*DIGIT) +int-num = 1*DIGIT +hex-num = ("0x" / "0X") 1*HEXDIG