hook it up

This commit is contained in:
Ari Lazier 2015-11-09 21:12:18 -08:00
parent 11b27dbdbf
commit c7c0698ad0
5 changed files with 364 additions and 551 deletions

View File

@ -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<Results *>(ctx, RJSResultsClass(), new Results(realm, *object_schema, std::move(query)));
}

View File

@ -98,542 +98,3 @@ bool RJSIsValueDate(JSContextRef ctx, JSValueRef value) {
return RJSIsValueObjectOfType(ctx, value, dateString);
}
#include <realm.hpp>
#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 <typename A, typename B>
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 <typename A, typename B>
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<realm::String> &&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<realm::String>&& 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<typename T>
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<NSExpression>(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<typename Func>
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 <typename RequestedType, typename TableGetter>
struct ColumnOfTypeHelper {
static realm::Columns<RequestedType> convert(TableGetter&& table, NSUInteger idx)
{
return table()->template column<RequestedType>(idx);
}
};
template <typename TableGetter>
struct ColumnOfTypeHelper<realm::DateTime, TableGetter> {
static realm::Columns<realm::Int> convert(TableGetter&& table, NSUInteger idx)
{
return table()->template column<realm::Int>(idx);
}
};
template <typename RequestedType, typename TableGetter>
struct ValueOfTypeHelper;
template <typename TableGetter>
struct ValueOfTypeHelper<realm::DateTime, TableGetter> {
static realm::Int convert(TableGetter&&, id value)
{
return [value timeIntervalSince1970];
}
};
template <typename TableGetter>
struct ValueOfTypeHelper<bool, TableGetter> {
static bool convert(TableGetter&&, id value)
{
return [value boolValue];
}
};
template <typename TableGetter>
struct ValueOfTypeHelper<realm::Double, TableGetter> {
static realm::Double convert(TableGetter&&, id value)
{
return [value doubleValue];
}
};
template <typename TableGetter>
struct ValueOfTypeHelper<realm::Float, TableGetter> {
static realm::Float convert(TableGetter&&, id value)
{
return [value floatValue];
}
};
template <typename TableGetter>
struct ValueOfTypeHelper<realm::Int, TableGetter> {
static realm::Int convert(TableGetter&&, id value)
{
return [value longLongValue];
}
};
template <typename TableGetter>
struct ValueOfTypeHelper<realm::String, TableGetter> {
static id convert(TableGetter&&, id value)
{
return value;
}
};
template <typename RequestedType, typename Value, typename TableGetter>
auto value_of_type_for_query(TableGetter&& tables, Value&& value)
{
const bool isColumnIndex = std::is_same<NSUInteger, typename std::remove_reference<Value>::type>::value;
using helper = std::conditional_t<isColumnIndex,
ColumnOfTypeHelper<RequestedType, TableGetter>,
ValueOfTypeHelper<RequestedType, TableGetter>>;
return helper::convert(std::forward<TableGetter>(tables), std::forward<Value>(value));
}
template <typename... T>
void add_constraint_to_query(realm::Query &query, realm::PropertyType type,
NSPredicateOperatorType operatorType,
NSComparisonPredicateOptions predicateOptions,
std::vector<NSUInteger> 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<bool>(table, values)...);
break;
case realm::PropertyTypeDate:
add_numeric_constraint_to_query(query, type, operatorType, value_of_type_for_query<realm::DateTime>(table, values)...);
break;
case realm::PropertyTypeDouble:
add_numeric_constraint_to_query(query, type, operatorType, value_of_type_for_query<realm::Double>(table, values)...);
break;
case realm::PropertyTypeFloat:
add_numeric_constraint_to_query(query, type, operatorType, value_of_type_for_query<realm::Float>(table, values)...);
break;
case realm::PropertyTypeInt:
add_numeric_constraint_to_query(query, type, operatorType, value_of_type_for_query<realm::Int>(table, values)...);
break;
case realm::PropertyTypeString:
case realm::PropertyTypeData:
add_string_constraint_to_query(query, operatorType, predicateOptions, value_of_type_for_query<realm::String>(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<NSUInteger> &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<NSUInteger> 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());
}

View File

@ -24,6 +24,10 @@
#include <pegtl/analyze.hh>
#include <pegtl/trace.hh>
#include <realm.hpp>
#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 <typename A, typename B>
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 <typename A, typename B>
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<String> &&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<String> &&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 <typename RequestedType, typename TableGetter>
struct ColumnOfTypeHelper {
static Columns<RequestedType> convert(TableGetter&& table, unsigned int idx)
{
return table()->template column<RequestedType>(idx);
}
};
template <typename TableGetter>
struct ColumnOfTypeHelper<DateTime, TableGetter> {
static Columns<Int> convert(TableGetter&& table, unsigned int idx)
{
return table()->template column<Int>(idx);
}
};
template <typename RequestedType, typename TableGetter>
struct ValueOfTypeHelper;
template <typename TableGetter>
struct ValueOfTypeHelper<DateTime, TableGetter> {
static Int convert(TableGetter&&, const std::string & value)
{
assert(0);
}
};
template <typename TableGetter>
struct ValueOfTypeHelper<bool, TableGetter> {
static bool convert(TableGetter&&, const std::string & value)
{
assert(0);
}
};
template <typename TableGetter>
struct ValueOfTypeHelper<Double, TableGetter> {
static Double convert(TableGetter&&, const std::string & value)
{
return std::stod(value);
}
};
template <typename TableGetter>
struct ValueOfTypeHelper<Float, TableGetter> {
static Float convert(TableGetter&&, const std::string & value)
{
return std::stof(value);
}
};
template <typename TableGetter>
struct ValueOfTypeHelper<Int, TableGetter> {
static Int convert(TableGetter&&, const std::string & value)
{
return std::stoll(value);
}
};
template <typename TableGetter>
struct ValueOfTypeHelper<String, TableGetter> {
static std::string convert(TableGetter&&, const std::string & value)
{
return value;
}
};
template <typename RequestedType, typename Value, typename TableGetter>
auto value_of_type_for_query(TableGetter&& tables, Value&& value)
{
const bool isColumnIndex = std::is_same<size_t, typename std::remove_reference<Value>::type>::value;
using helper = std::conditional_t<isColumnIndex,
ColumnOfTypeHelper<RequestedType, TableGetter>,
ValueOfTypeHelper<RequestedType, TableGetter>>;
return helper::convert(std::forward<TableGetter>(tables), std::forward<Value>(value));
}
std::vector<std::string> &split(const std::string &s, char delim, std::vector<std::string> &elems) {
std::stringstream ss(s);
std::string item;
while (std::getline(ss, item, delim)) {
elems.push_back(item);
}
return elems;
}
std::vector<std::string> split(const std::string &s, char delim) {
std::vector<std::string> elems;
split(s, delim, elems);
return elems;
}
Property *get_property_from_key_path(Schema &schema, ObjectSchema &desc, const std::string &key_path, std::vector<size_t> &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 <typename... T>
void do_add_comparison_to_query(Query &query, Schema &schema, ObjectSchema &object_schema, Property *prop,
Predicate::Operator op, const std::vector<size_t>& 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<bool>(table, values)...);
break;
case PropertyTypeDate:
add_numeric_constraint_to_query(query, type, op, value_of_type_for_query<DateTime>(table, values)...);
break;
case PropertyTypeDouble:
add_numeric_constraint_to_query(query, type, op, value_of_type_for_query<Double>(table, values)...);
break;
case PropertyTypeFloat:
add_numeric_constraint_to_query(query, type, op, value_of_type_for_query<Float>(table, values)...);
break;
case PropertyTypeInt:
add_numeric_constraint_to_query(query, type, op, value_of_type_for_query<Int>(table, values)...);
break;
case PropertyTypeString:
case PropertyTypeData:
add_string_constraint_to_query(query, op, value_of_type_for_query<String>(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<size_t> 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());
}
}}

View File

@ -23,6 +23,9 @@
#include <string>
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);
}
}

View File

@ -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