X-Git-Url: https://git.mdrn.pl/wl-app.git/blobdiff_plain/53b27422d140022594fc241cca91c3183be57bca..48b2fe9f7c2dc3d9aeaaa6dbfb27c7da4f3235ff:/iOS/Pods/Realm/Realm/RLMQueryUtil.mm diff --git a/iOS/Pods/Realm/Realm/RLMQueryUtil.mm b/iOS/Pods/Realm/Realm/RLMQueryUtil.mm new file mode 100644 index 0000000..82d8ccd --- /dev/null +++ b/iOS/Pods/Realm/Realm/RLMQueryUtil.mm @@ -0,0 +1,1489 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 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. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMQueryUtil.hpp" + +#import "RLMArray.h" +#import "RLMObjectSchema_Private.h" +#import "RLMObject_Private.hpp" +#import "RLMPredicateUtil.hpp" +#import "RLMProperty_Private.h" +#import "RLMSchema.h" +#import "RLMUtil.hpp" + +#import "object_store.hpp" +#import "results.hpp" + +#include +#include +#include +#include + +using namespace realm; + +NSString * const RLMPropertiesComparisonTypeMismatchException = @"RLMPropertiesComparisonTypeMismatchException"; +NSString * const RLMUnsupportedTypesFoundInPropertyComparisonException = @"RLMUnsupportedTypesFoundInPropertyComparisonException"; + +NSString * const RLMPropertiesComparisonTypeMismatchReason = @"Property type mismatch between %@ and %@"; +NSString * const RLMUnsupportedTypesFoundInPropertyComparisonReason = @"Comparison between %@ and %@"; + +// small helper to create the many exceptions thrown when parsing predicates +static NSException *RLMPredicateException(NSString *name, NSString *format, ...) { + va_list args; + va_start(args, format); + NSString *reason = [[NSString alloc] initWithFormat:format arguments:args]; + va_end(args); + + return [NSException exceptionWithName:name reason:reason userInfo:nil]; +} + +// 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 [NSException exceptionWithName:name reason:reason userInfo:nil]; +} + +// return the property for a validated column name +RLMProperty *RLMValidatedProperty(RLMObjectSchema *desc, NSString *columnName) { + RLMProperty *prop = desc[columnName]; + RLMPrecondition(prop, @"Invalid property name", + @"Property '%@' not found in object of type '%@'", columnName, desc.className); + return prop; +} + +namespace { +BOOL RLMPropertyTypeIsNumeric(RLMPropertyType propertyType) { + switch (propertyType) { + case RLMPropertyTypeInt: + case RLMPropertyTypeFloat: + case RLMPropertyTypeDouble: + return YES; + default: + return NO; + } +} + +// Equal and ContainsSubstring are used by QueryBuilder::add_string_constraint as the comparator +// for performing diacritic-insensitive comparisons. + +bool equal(CFStringCompareFlags options, StringData v1, StringData v2) +{ + if (v1.is_null() || v2.is_null()) { + return v1.is_null() == v2.is_null(); + } + + auto s1 = util::adoptCF(CFStringCreateWithBytesNoCopy(kCFAllocatorSystemDefault, (const UInt8*)v1.data(), v1.size(), + kCFStringEncodingUTF8, false, kCFAllocatorNull)); + auto s2 = util::adoptCF(CFStringCreateWithBytesNoCopy(kCFAllocatorSystemDefault, (const UInt8*)v2.data(), v2.size(), + kCFStringEncodingUTF8, false, kCFAllocatorNull)); + + return CFStringCompare(s1.get(), s2.get(), options) == kCFCompareEqualTo; +} + +template +struct Equal { + using CaseSensitive = Equal; + using CaseInsensitive = Equal; + + bool operator()(StringData v1, StringData v2, bool v1_null, bool v2_null) const + { + REALM_ASSERT_DEBUG(v1_null == v1.is_null()); + REALM_ASSERT_DEBUG(v2_null == v2.is_null()); + + return equal(options, v1, v2); + } + + // FIXME: Consider the options. + static const char* description() { return "equal"; } +}; + +bool contains_substring(CFStringCompareFlags options, StringData v1, StringData v2) +{ + if (v2.is_null()) { + // Everything contains NULL + return true; + } + + if (v1.is_null()) { + // NULL contains nothing (except NULL, handled above) + return false; + } + + if (v2.size() == 0) { + // Everything (except NULL, handled above) contains the empty string + return true; + } + + auto s1 = util::adoptCF(CFStringCreateWithBytesNoCopy(kCFAllocatorSystemDefault, (const UInt8*)v1.data(), v1.size(), + kCFStringEncodingUTF8, false, kCFAllocatorNull)); + auto s2 = util::adoptCF(CFStringCreateWithBytesNoCopy(kCFAllocatorSystemDefault, (const UInt8*)v2.data(), v2.size(), + kCFStringEncodingUTF8, false, kCFAllocatorNull)); + + return CFStringFind(s1.get(), s2.get(), options).location != kCFNotFound; +} + +template +struct ContainsSubstring { + using CaseSensitive = ContainsSubstring; + using CaseInsensitive = ContainsSubstring; + + bool operator()(StringData v1, StringData v2, bool v1_null, bool v2_null) const + { + REALM_ASSERT_DEBUG(v1_null == v1.is_null()); + REALM_ASSERT_DEBUG(v2_null == v2.is_null()); + + return contains_substring(options, v1, v2); + } + + // FIXME: Consider the options. + static const char* description() { return "contains"; } +}; + + +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 @"BETWEEN"; + case NSCustomSelectorPredicateOperatorType: + return @"custom selector"; + } + + return [NSString stringWithFormat:@"unknown operator %lu", (unsigned long)operatorType]; +} + +Table& get_table(Group& group, RLMObjectSchema *objectSchema) +{ + return *ObjectStore::table_for_object_type(group, objectSchema.objectName.UTF8String); +} + +// A reference to a column within a query. Can be resolved to a Columns for use in query expressions. +class ColumnReference { +public: + ColumnReference(Query& query, Group& group, RLMSchema *schema, RLMProperty* property, const std::vector& links = {}) + : m_links(links), m_property(property), m_schema(schema), m_group(&group), m_query(&query), m_table(query.get_table().get()) + { + auto& table = walk_link_chain([](Table&, size_t, RLMPropertyType) { }); + m_index = table.get_column_index(m_property.name.UTF8String); + } + + template + auto resolve(SubQuery&&... subquery) const + { + static_assert(sizeof...(SubQuery) < 2, "resolve() takes at most one subquery"); + set_link_chain_on_table(); + if (type() != RLMPropertyTypeLinkingObjects) { + return m_table->template column(index(), std::forward(subquery)...); + } + else { + return resolve_backlink(std::forward(subquery)...); + } + } + + RLMProperty *property() const { return m_property; } + size_t index() const { return m_index; } + RLMPropertyType type() const { return property().type; } + Group& group() const { return *m_group; } + + RLMObjectSchema *link_target_object_schema() const + { + switch (type()) { + case RLMPropertyTypeObject: + case RLMPropertyTypeLinkingObjects: + return m_schema[property().objectClassName]; + default: + REALM_UNREACHABLE(); + } + } + + bool has_links() const { return m_links.size(); } + + bool has_any_to_many_links() const { + return std::any_of(begin(m_links), end(m_links), + [](RLMProperty *property) { return property.array; }); + } + + ColumnReference last_link_column() const { + REALM_ASSERT(!m_links.empty()); + return {*m_query, *m_group, m_schema, m_links.back(), {m_links.begin(), m_links.end() - 1}}; + } + + ColumnReference column_ignoring_links(Query& query) const { + return {query, *m_group, m_schema, m_property}; + } + +private: + template + auto resolve_backlink(SubQuery&&... subquery) const + { + // We actually just want `if constexpr (std::is_same::value) { ... }`, + // so fake it by tag-dispatching on the conditional + return do_resolve_backlink(std::is_same(), std::forward(subquery)...); + } + + template + auto do_resolve_backlink(std::true_type, SubQuery&&... subquery) const + { + return with_link_origin(m_property, [&](Table& table, size_t col) { + return m_table->template column(table, col, std::forward(subquery)...); + }); + } + + template + Columns do_resolve_backlink(std::false_type, SubQuery&&...) const + { + // This can't actually happen as we only call resolve_backlink() if + // it's RLMPropertyTypeLinkingObjects + __builtin_unreachable(); + } + + template + Table& walk_link_chain(Func&& func) const + { + auto table = m_query->get_table().get(); + for (const auto& link : m_links) { + if (link.type != RLMPropertyTypeLinkingObjects) { + auto index = table->get_column_index(link.name.UTF8String); + func(*table, index, link.type); + table = table->get_link_target(index).get(); + } + else { + with_link_origin(link, [&](Table& link_origin_table, size_t link_origin_column) { + func(link_origin_table, link_origin_column, link.type); + table = &link_origin_table; + }); + } + } + return *table; + } + + template + auto with_link_origin(RLMProperty *prop, Func&& func) const + { + RLMObjectSchema *link_origin_schema = m_schema[prop.objectClassName]; + Table& link_origin_table = get_table(*m_group, link_origin_schema); + size_t link_origin_column = link_origin_table.get_column_index(prop.linkOriginPropertyName.UTF8String); + return func(link_origin_table, link_origin_column); + } + + void set_link_chain_on_table() const + { + walk_link_chain([&](Table& current_table, size_t column, RLMPropertyType type) { + if (type == RLMPropertyTypeLinkingObjects) { + m_table->backlink(current_table, column); + } + else { + m_table->link(column); + } + }); + } + + std::vector m_links; + RLMProperty *m_property; + RLMSchema *m_schema; + Group *m_group; + Query *m_query; + Table *m_table; + size_t m_index; +}; + +class CollectionOperation { +public: + enum Type { + Count, + Minimum, + Maximum, + Sum, + Average, + }; + + CollectionOperation(Type type, ColumnReference link_column, util::Optional column) + : m_type(type) + , m_link_column(std::move(link_column)) + , m_column(std::move(column)) + { + RLMPrecondition(m_link_column.property().array, + @"Invalid predicate", @"Collection operation can only be applied to a property of type RLMArray."); + + switch (m_type) { + case Count: + RLMPrecondition(!m_column, @"Invalid predicate", @"Result of @count does not have any properties."); + break; + case Minimum: + case Maximum: + case Sum: + case Average: + RLMPrecondition(m_column && RLMPropertyTypeIsNumeric(m_column->type()), @"Invalid predicate", + @"%@ can only be applied to a numeric property.", name_for_type(m_type)); + break; + } + } + + CollectionOperation(NSString *operationName, ColumnReference link_column, util::Optional column = util::none) + : CollectionOperation(type_for_name(operationName), std::move(link_column), std::move(column)) + { + } + + Type type() const { return m_type; } + const ColumnReference& link_column() const { return m_link_column; } + const ColumnReference& column() const { return *m_column; } + + void validate_comparison(id value) const { + switch (m_type) { + case Count: + case Average: + RLMPrecondition([value isKindOfClass:[NSNumber class]], @"Invalid operand", + @"%@ can only be compared with a numeric value.", name_for_type(m_type)); + break; + case Minimum: + case Maximum: + case Sum: + RLMPrecondition(RLMIsObjectValidForProperty(value, m_column->property()), @"Invalid operand", + @"%@ on a property of type %@ cannot be compared with '%@'", + name_for_type(m_type), RLMTypeToString(m_column->type()), value); + break; + } + } + + void validate_comparison(const ColumnReference& column) const { + switch (m_type) { + case Count: + RLMPrecondition(RLMPropertyTypeIsNumeric(column.type()), @"Invalid operand", + @"%@ can only be compared with a numeric value.", name_for_type(m_type)); + break; + case Average: + case Minimum: + case Maximum: + case Sum: + RLMPrecondition(RLMPropertyTypeIsNumeric(column.type()), @"Invalid operand", + @"%@ on a property of type %@ cannot be compared with property of type '%@'", + name_for_type(m_type), RLMTypeToString(m_column->type()), RLMTypeToString(column.type())); + break; + } + } + +private: + static Type type_for_name(NSString *name) { + if ([name isEqualToString:@"@count"]) { + return Count; + } + if ([name isEqualToString:@"@min"]) { + return Minimum; + } + if ([name isEqualToString:@"@max"]) { + return Maximum; + } + if ([name isEqualToString:@"@sum"]) { + return Sum; + } + if ([name isEqualToString:@"@avg"]) { + return Average; + } + @throw RLMPredicateException(@"Invalid predicate", @"Unsupported collection operation '%@'", name); + } + + static NSString *name_for_type(Type type) { + switch (type) { + case Count: return @"@count"; + case Minimum: return @"@min"; + case Maximum: return @"@max"; + case Sum: return @"@sum"; + case Average: return @"@avg"; + } + } + + Type m_type; + ColumnReference m_link_column; + util::Optional m_column; +}; + +class QueryBuilder { +public: + QueryBuilder(Query& query, Group& group, RLMSchema *schema) + : m_query(query), m_group(group), m_schema(schema) { } + + void apply_predicate(NSPredicate *predicate, RLMObjectSchema *objectSchema); + + + void apply_collection_operator_expression(RLMObjectSchema *desc, NSString *keyPath, id value, NSComparisonPredicate *pred); + void apply_value_expression(RLMObjectSchema *desc, NSString *keyPath, id value, NSComparisonPredicate *pred); + void apply_column_expression(RLMObjectSchema *desc, NSString *leftKeyPath, NSString *rightKeyPath, NSComparisonPredicate *predicate); + void apply_subquery_count_expression(RLMObjectSchema *objectSchema, NSExpression *subqueryExpression, + NSPredicateOperatorType operatorType, NSExpression *right); + void apply_function_subquery_expression(RLMObjectSchema *objectSchema, NSExpression *functionExpression, + NSPredicateOperatorType operatorType, NSExpression *right); + void apply_function_expression(RLMObjectSchema *objectSchema, NSExpression *functionExpression, + NSPredicateOperatorType operatorType, NSExpression *right); + + + template + void add_numeric_constraint(RLMPropertyType datatype, + NSPredicateOperatorType operatorType, + A&& lhs, B&& rhs); + + template + void add_bool_constraint(NSPredicateOperatorType operatorType, A lhs, B rhs); + + void add_substring_constraint(null, Query condition); + template + void add_substring_constraint(const T& value, Query condition); + template + void add_substring_constraint(const Columns& value, Query condition); + + template + void add_string_constraint(NSPredicateOperatorType operatorType, + NSComparisonPredicateOptions predicateOptions, + Columns &&column, + T value); + + void add_string_constraint(NSPredicateOperatorType operatorType, + NSComparisonPredicateOptions predicateOptions, + StringData value, + Columns&& column); + + template + void add_constraint(RLMPropertyType type, + NSPredicateOperatorType operatorType, + NSComparisonPredicateOptions predicateOptions, + L lhs, R rhs); + template + void do_add_constraint(RLMPropertyType type, NSPredicateOperatorType operatorType, + NSComparisonPredicateOptions predicateOptions, T... values); + void do_add_constraint(RLMPropertyType, NSPredicateOperatorType, NSComparisonPredicateOptions, id, realm::null); + + void add_between_constraint(const ColumnReference& column, id value); + + void add_binary_constraint(NSPredicateOperatorType operatorType, const ColumnReference& column, BinaryData value); + void add_binary_constraint(NSPredicateOperatorType operatorType, const ColumnReference& column, id value); + void add_binary_constraint(NSPredicateOperatorType operatorType, const ColumnReference& column, null); + void add_binary_constraint(NSPredicateOperatorType operatorType, id value, const ColumnReference& column); + void add_binary_constraint(NSPredicateOperatorType, const ColumnReference&, const ColumnReference&); + + void add_link_constraint(NSPredicateOperatorType operatorType, const ColumnReference& column, RLMObject *obj); + void add_link_constraint(NSPredicateOperatorType operatorType, const ColumnReference& column, realm::null); + template + void add_link_constraint(NSPredicateOperatorType operatorType, T obj, const ColumnReference& column); + void add_link_constraint(NSPredicateOperatorType, const ColumnReference&, const ColumnReference&); + + template + void add_collection_operation_constraint(RLMPropertyType propertyType, NSPredicateOperatorType operatorType, T... values); + template + void add_collection_operation_constraint(NSPredicateOperatorType operatorType, + CollectionOperation collectionOperation, T... values); + + + CollectionOperation collection_operation_from_key_path(RLMObjectSchema *desc, NSString *keyPath); + ColumnReference column_reference_from_key_path(RLMObjectSchema *objectSchema, NSString *keyPath, bool isAggregate); + +private: + Query& m_query; + Group& m_group; + RLMSchema *m_schema; +}; + +// add a clause for numeric constraints based on operator type +template +void QueryBuilder::add_numeric_constraint(RLMPropertyType datatype, + NSPredicateOperatorType operatorType, + A&& lhs, B&& rhs) +{ + switch (operatorType) { + case NSLessThanPredicateOperatorType: + m_query.and_query(lhs < rhs); + break; + case NSLessThanOrEqualToPredicateOperatorType: + m_query.and_query(lhs <= rhs); + break; + case NSGreaterThanPredicateOperatorType: + m_query.and_query(lhs > rhs); + break; + case NSGreaterThanOrEqualToPredicateOperatorType: + m_query.and_query(lhs >= rhs); + break; + case NSEqualToPredicateOperatorType: + m_query.and_query(lhs == rhs); + break; + case NSNotEqualToPredicateOperatorType: + m_query.and_query(lhs != rhs); + break; + default: + @throw RLMPredicateException(@"Invalid operator type", + @"Operator '%@' not supported for type %@", + operatorName(operatorType), RLMTypeToString(datatype)); + } +} + +template +void QueryBuilder::add_bool_constraint(NSPredicateOperatorType operatorType, A lhs, B rhs) { + switch (operatorType) { + case NSEqualToPredicateOperatorType: + m_query.and_query(lhs == rhs); + break; + case NSNotEqualToPredicateOperatorType: + m_query.and_query(lhs != rhs); + break; + default: + @throw RLMPredicateException(@"Invalid operator type", + @"Operator '%@' not supported for bool type", operatorName(operatorType)); + } +} + +void QueryBuilder::add_substring_constraint(null, Query) { + // Foundation always returns false for substring operations with a RHS of null or "". + m_query.and_query(std::unique_ptr(new FalseExpression)); +} + +template +void QueryBuilder::add_substring_constraint(const T& value, Query condition) { + // Foundation always returns false for substring operations with a RHS of null or "". + m_query.and_query(value.size() + ? std::move(condition) + : std::unique_ptr(new FalseExpression)); +} + +template +void QueryBuilder::add_substring_constraint(const Columns& value, Query condition) { + // Foundation always returns false for substring operations with a RHS of null or "". + // We don't need to concern ourselves with the possibility of value traversing a link list + // and producing multiple values per row as such expressions will have been rejected. + m_query.and_query(const_cast&>(value).size() != 0 && std::move(condition)); +} + +template +void QueryBuilder::add_string_constraint(NSPredicateOperatorType operatorType, + NSComparisonPredicateOptions predicateOptions, + Columns &&column, + T value) { + bool caseSensitive = !(predicateOptions & NSCaseInsensitivePredicateOption); + bool diacriticSensitive = !(predicateOptions & NSDiacriticInsensitivePredicateOption); + + if (diacriticSensitive) { + switch (operatorType) { + case NSBeginsWithPredicateOperatorType: + add_substring_constraint(value, column.begins_with(value, caseSensitive)); + break; + case NSEndsWithPredicateOperatorType: + add_substring_constraint(value, column.ends_with(value, caseSensitive)); + break; + case NSContainsPredicateOperatorType: + add_substring_constraint(value, column.contains(value, caseSensitive)); + break; + case NSEqualToPredicateOperatorType: + m_query.and_query(column.equal(value, caseSensitive)); + break; + case NSNotEqualToPredicateOperatorType: + m_query.and_query(column.not_equal(value, caseSensitive)); + break; + case NSLikePredicateOperatorType: + m_query.and_query(column.like(value, caseSensitive)); + break; + default: + @throw RLMPredicateException(@"Invalid operator type", + @"Operator '%@' not supported for string type", + operatorName(operatorType)); + } + return; + } + + auto as_subexpr = util::overload([](StringData value) { return make_subexpr(value); }, + [](const Columns& c) { return c.clone(); }); + auto left = as_subexpr(column); + auto right = as_subexpr(value); + + auto make_constraint = [&](auto comparator) { + using Comparator = decltype(comparator); + using CompareCS = Compare; + using CompareCI = Compare; + if (caseSensitive) { + return make_expression(std::move(left), std::move(right)); + } + else { + return make_expression(std::move(left), std::move(right)); + } + }; + + switch (operatorType) { + case NSBeginsWithPredicateOperatorType: { + using C = ContainsSubstring; + add_substring_constraint(value, make_constraint(C{})); + break; + } + case NSEndsWithPredicateOperatorType: { + using C = ContainsSubstring; + add_substring_constraint(value, make_constraint(C{})); + break; + } + case NSContainsPredicateOperatorType: { + using C = ContainsSubstring; + add_substring_constraint(value, make_constraint(C{})); + break; + } + case NSNotEqualToPredicateOperatorType: + m_query.Not(); + REALM_FALLTHROUGH; + case NSEqualToPredicateOperatorType: + m_query.and_query(make_constraint(Equal{})); + break; + case NSLikePredicateOperatorType: + @throw RLMPredicateException(@"Invalid operator type", + @"Operator 'LIKE' not supported with diacritic-insensitive modifier."); + default: + @throw RLMPredicateException(@"Invalid operator type", + @"Operator '%@' not supported for string type", operatorName(operatorType)); + } +} + +void QueryBuilder::add_string_constraint(NSPredicateOperatorType operatorType, + NSComparisonPredicateOptions predicateOptions, + StringData value, + Columns&& column) { + switch (operatorType) { + case NSEqualToPredicateOperatorType: + case NSNotEqualToPredicateOperatorType: + add_string_constraint(operatorType, predicateOptions, std::move(column), value); + break; + default: + @throw RLMPredicateException(@"Invalid operator type", + @"Operator '%@' is not supported for string type with key path on right side of operator", + operatorName(operatorType)); + } +} + +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; +} + +void validate_and_extract_between_range(id value, RLMProperty *prop, id *from, id *to) { + NSArray *array = RLMDynamicCast(value); + RLMPrecondition(array, @"Invalid value", @"object must be of type NSArray for BETWEEN operations"); + RLMPrecondition(array.count == 2, @"Invalid value", @"NSArray object must contain exactly two objects for BETWEEN operations"); + + *from = value_from_constant_expression_or_value(array.firstObject); + *to = value_from_constant_expression_or_value(array.lastObject); + RLMPrecondition(RLMIsObjectValidForProperty(*from, prop) && RLMIsObjectValidForProperty(*to, prop), + @"Invalid value", + @"NSArray objects must be of type %@ for BETWEEN operations", RLMTypeToString(prop.type)); +} + +void QueryBuilder::add_between_constraint(const ColumnReference& column, id value) { + if (column.has_any_to_many_links()) { + auto link_column = column.last_link_column(); + Query subquery = get_table(m_group, link_column.link_target_object_schema()).where(); + QueryBuilder(subquery, m_group, m_schema).add_between_constraint(column.column_ignoring_links(subquery), value); + + m_query.and_query(link_column.resolve(std::move(subquery)).count() > 0); + return; + } + + id from, to; + validate_and_extract_between_range(value, column.property(), &from, &to); + + RLMPropertyType type = column.type(); + + m_query.group(); + add_constraint(type, NSGreaterThanOrEqualToPredicateOperatorType, 0, column, from); + add_constraint(type, NSLessThanOrEqualToPredicateOperatorType, 0, column, to); + m_query.end_group(); +} + +void QueryBuilder::add_binary_constraint(NSPredicateOperatorType operatorType, + const ColumnReference& column, + BinaryData value) { + RLMPrecondition(!column.has_links(), @"Unsupported operator", @"NSData properties cannot be queried over an object link."); + + size_t index = column.index(); + Query query = m_query.get_table()->where(); + + switch (operatorType) { + case NSBeginsWithPredicateOperatorType: + add_substring_constraint(value, query.begins_with(index, value)); + break; + case NSEndsWithPredicateOperatorType: + add_substring_constraint(value, query.ends_with(index, value)); + break; + case NSContainsPredicateOperatorType: + add_substring_constraint(value, query.contains(index, value)); + break; + case NSEqualToPredicateOperatorType: + m_query.equal(index, value); + break; + case NSNotEqualToPredicateOperatorType: + m_query.not_equal(index, value); + break; + default: + @throw RLMPredicateException(@"Invalid operator type", + @"Operator '%@' not supported for binary type", operatorName(operatorType)); + } +} + +void QueryBuilder::add_binary_constraint(NSPredicateOperatorType operatorType, const ColumnReference& column, id value) { + add_binary_constraint(operatorType, column, RLMBinaryDataForNSData(value)); +} + +void QueryBuilder::add_binary_constraint(NSPredicateOperatorType operatorType, const ColumnReference& column, null) { + add_binary_constraint(operatorType, column, BinaryData()); +} + +void QueryBuilder::add_binary_constraint(NSPredicateOperatorType operatorType, id value, const ColumnReference& column) { + switch (operatorType) { + case NSEqualToPredicateOperatorType: + case NSNotEqualToPredicateOperatorType: + add_binary_constraint(operatorType, column, value); + break; + default: + @throw RLMPredicateException(@"Invalid operator type", + @"Operator '%@' is not supported for binary type with key path on right side of operator", + operatorName(operatorType)); + } +} + +void QueryBuilder::add_binary_constraint(NSPredicateOperatorType, const ColumnReference&, const ColumnReference&) { + @throw RLMPredicateException(@"Invalid predicate", @"Comparisons between two NSData properties are not supported"); +} + +void QueryBuilder::add_link_constraint(NSPredicateOperatorType operatorType, + const ColumnReference& column, RLMObject *obj) { + RLMPrecondition(operatorType == NSEqualToPredicateOperatorType || operatorType == NSNotEqualToPredicateOperatorType, + @"Invalid operator type", @"Only 'Equal' and 'Not Equal' operators supported for object comparison"); + + if (operatorType == NSEqualToPredicateOperatorType) { + m_query.and_query(column.resolve() == obj->_row); + } + else { + m_query.and_query(column.resolve() != obj->_row); + } +} + +void QueryBuilder::add_link_constraint(NSPredicateOperatorType operatorType, + const ColumnReference& column, + realm::null) { + RLMPrecondition(operatorType == NSEqualToPredicateOperatorType || operatorType == NSNotEqualToPredicateOperatorType, + @"Invalid operator type", @"Only 'Equal' and 'Not Equal' operators supported for object comparison"); + + if (operatorType == NSEqualToPredicateOperatorType) { + m_query.and_query(column.resolve() == null()); + } + else { + m_query.and_query(column.resolve() != null()); + } +} + +template +void QueryBuilder::add_link_constraint(NSPredicateOperatorType operatorType, T obj, const ColumnReference& column) { + // Link constraints only support the equal-to and not-equal-to operators. The order of operands + // is not important for those comparisons so we can delegate to the other implementation. + add_link_constraint(operatorType, column, obj); +} + +void QueryBuilder::add_link_constraint(NSPredicateOperatorType, const ColumnReference&, const ColumnReference&) { + // This is not actually reachable as this case is caught earlier, but this + // overload is needed for the code to compile + @throw RLMPredicateException(@"Invalid predicate", @"Comparisons between two RLMArray properties are not supported"); +} + + +// iterate over an array of subpredicates, using @func to build a query from each +// one and ORing them together +template +void process_or_group(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(std::unique_ptr(new FalseExpression)); + } + + query.end_group(); +} + +template +RequestedType convert(id value); + +template <> +Timestamp convert(id value) { + return RLMTimestampForNSDate(value); +} + +template <> +bool convert(id value) { + return [value boolValue]; +} + +template <> +Double convert(id value) { + return [value doubleValue]; +} + +template <> +Float convert(id value) { + return [value floatValue]; +} + +template <> +Int convert(id value) { + return [value longLongValue]; +} + +template <> +String convert(id value) { + return RLMStringDataWithNSString(value); +} + +template +realm::null value_of_type(realm::null) { + return realm::null(); +} + +template +auto value_of_type(id value) { + return ::convert(value); +} + +template +auto value_of_type(const ColumnReference& column) { + return column.resolve(); +} + + +template +void QueryBuilder::do_add_constraint(RLMPropertyType type, NSPredicateOperatorType operatorType, + NSComparisonPredicateOptions predicateOptions, T... values) +{ + static_assert(sizeof...(T) == 2, "do_add_constraint accepts only two values as arguments"); + + switch (type) { + case RLMPropertyTypeBool: + add_bool_constraint(operatorType, value_of_type(values)...); + break; + case RLMPropertyTypeDate: + add_numeric_constraint(type, operatorType, value_of_type(values)...); + break; + case RLMPropertyTypeDouble: + add_numeric_constraint(type, operatorType, value_of_type(values)...); + break; + case RLMPropertyTypeFloat: + add_numeric_constraint(type, operatorType, value_of_type(values)...); + break; + case RLMPropertyTypeInt: + add_numeric_constraint(type, operatorType, value_of_type(values)...); + break; + case RLMPropertyTypeString: + add_string_constraint(operatorType, predicateOptions, value_of_type(values)...); + break; + case RLMPropertyTypeData: + add_binary_constraint(operatorType, values...); + break; + case RLMPropertyTypeObject: + case RLMPropertyTypeLinkingObjects: + add_link_constraint(operatorType, values...); + break; + default: + @throw RLMPredicateException(@"Unsupported predicate value type", + @"Object type %@ not supported", RLMTypeToString(type)); + } +} + +void QueryBuilder::do_add_constraint(RLMPropertyType, NSPredicateOperatorType, NSComparisonPredicateOptions, id, realm::null) +{ + // This is not actually reachable as this case is caught earlier, but this + // overload is needed for the code to compile + @throw RLMPredicateException(@"Invalid predicate expressions", + @"Predicate expressions must compare a keypath and another keypath or a constant value"); +} + +bool is_nsnull(id value) { + return !value || value == NSNull.null; +} + +template +bool is_nsnull(T) { + return false; +} + +template +void QueryBuilder::add_constraint(RLMPropertyType type, NSPredicateOperatorType operatorType, + NSComparisonPredicateOptions predicateOptions, L lhs, R rhs) +{ + // The expression operators are only overloaded for realm::null on the rhs + RLMPrecondition(!is_nsnull(lhs), @"Unsupported operator", + @"Nil is only supported on the right side of operators"); + + if (is_nsnull(rhs)) { + do_add_constraint(type, operatorType, predicateOptions, lhs, realm::null()); + } + else { + do_add_constraint(type, operatorType, predicateOptions, lhs, rhs); + } +} + +struct KeyPath { + std::vector links; + RLMProperty *property; + bool containsToManyRelationship; +}; + +KeyPath key_path_from_string(RLMSchema *schema, RLMObjectSchema *objectSchema, NSString *keyPath) +{ + RLMProperty *property; + std::vector links; + + bool keyPathContainsToManyRelationship = false; + + NSUInteger start = 0, length = keyPath.length, end = NSNotFound; + do { + end = [keyPath rangeOfString:@"." options:0 range:{start, length - start}].location; + NSString *propertyName = [keyPath substringWithRange:{start, end == NSNotFound ? length - start : end - start}]; + property = objectSchema[propertyName]; + RLMPrecondition(property, @"Invalid property name", + @"Property '%@' not found in object of type '%@'", + propertyName, objectSchema.className); + + if (property.array) + keyPathContainsToManyRelationship = true; + + if (end != NSNotFound) { + RLMPrecondition(property.type == RLMPropertyTypeObject || property.type == RLMPropertyTypeLinkingObjects, + @"Invalid value", @"Property '%@' is not a link in object of type '%@'", + propertyName, objectSchema.className); + + links.push_back(property); + REALM_ASSERT(property.objectClassName); + objectSchema = schema[property.objectClassName]; + } + + start = end + 1; + } while (end != NSNotFound); + + return {std::move(links), property, keyPathContainsToManyRelationship}; +} + +ColumnReference QueryBuilder::column_reference_from_key_path(RLMObjectSchema *objectSchema, + NSString *keyPathString, bool isAggregate) +{ + auto keyPath = key_path_from_string(m_schema, objectSchema, keyPathString); + + if (isAggregate && !keyPath.containsToManyRelationship) { + @throw RLMPredicateException(@"Invalid predicate", + @"Aggregate operations can only be used on key paths that include an array property"); + } else if (!isAggregate && keyPath.containsToManyRelationship) { + @throw RLMPredicateException(@"Invalid predicate", + @"Key paths that include an array property must use aggregate operations"); + } + + return ColumnReference(m_query, m_group, m_schema, keyPath.property, std::move(keyPath.links)); +} + +void validate_property_value(const ColumnReference& column, + __unsafe_unretained id const value, + __unsafe_unretained NSString *const err, + __unsafe_unretained RLMObjectSchema *const objectSchema, + __unsafe_unretained NSString *const keyPath) { + RLMProperty *prop = column.property(); + if (prop.array) { + RLMPrecondition([RLMObjectBaseObjectSchema(RLMDynamicCast(value)).className isEqualToString:prop.objectClassName], + @"Invalid value", err, prop.objectClassName, keyPath, objectSchema.className, value); + } + else { + RLMPrecondition(RLMIsObjectValidForProperty(value, prop), + @"Invalid value", err, RLMTypeToString(prop.type), keyPath, objectSchema.className, value); + } + if (RLMObjectBase *obj = RLMDynamicCast(value)) { + RLMPrecondition(!obj->_row.is_attached() || &column.group() == &obj->_realm.group, + @"Invalid value origin", @"Object must be from the Realm being queried"); + } +} + +template +struct ValueOfTypeWithCollectionOperationHelper; + +template <> +struct ValueOfTypeWithCollectionOperationHelper { + static auto convert(const CollectionOperation& operation) + { + assert(operation.type() == CollectionOperation::Count); + return operation.link_column().resolve().count(); + } +}; + +#define VALUE_OF_TYPE_WITH_COLLECTION_OPERATOR_HELPER(OperationType, function) \ +template \ +struct ValueOfTypeWithCollectionOperationHelper { \ + static auto convert(const CollectionOperation& operation) \ + { \ + REALM_ASSERT(operation.type() == OperationType); \ + auto targetColumn = operation.link_column().resolve().template column(operation.column().index()); \ + return targetColumn.function(); \ + } \ +} \ + +VALUE_OF_TYPE_WITH_COLLECTION_OPERATOR_HELPER(CollectionOperation::Minimum, min); +VALUE_OF_TYPE_WITH_COLLECTION_OPERATOR_HELPER(CollectionOperation::Maximum, max); +VALUE_OF_TYPE_WITH_COLLECTION_OPERATOR_HELPER(CollectionOperation::Sum, sum); +VALUE_OF_TYPE_WITH_COLLECTION_OPERATOR_HELPER(CollectionOperation::Average, average); +#undef VALUE_OF_TYPE_WITH_COLLECTION_OPERATOR_HELPER + +template +auto value_of_type_with_collection_operation(T&& value) { + return value_of_type(std::forward(value)); +} + +template +auto value_of_type_with_collection_operation(CollectionOperation operation) { + using helper = ValueOfTypeWithCollectionOperationHelper; + return helper::convert(operation); +} + +template +void QueryBuilder::add_collection_operation_constraint(RLMPropertyType propertyType, NSPredicateOperatorType operatorType, T... values) +{ + switch (propertyType) { + case RLMPropertyTypeInt: + add_numeric_constraint(propertyType, operatorType, value_of_type_with_collection_operation(values)...); + break; + case RLMPropertyTypeFloat: + add_numeric_constraint(propertyType, operatorType, value_of_type_with_collection_operation(values)...); + break; + case RLMPropertyTypeDouble: + add_numeric_constraint(propertyType, operatorType, value_of_type_with_collection_operation(values)...); + break; + default: + REALM_ASSERT(false && "Only numeric property types should hit this path."); + } +} + +template +void QueryBuilder::add_collection_operation_constraint(NSPredicateOperatorType operatorType, + CollectionOperation collectionOperation, T... values) +{ + static_assert(sizeof...(T) == 2, "add_collection_operation_constraint accepts only two values as arguments"); + + switch (collectionOperation.type()) { + case CollectionOperation::Count: + add_numeric_constraint(RLMPropertyTypeInt, operatorType, + value_of_type_with_collection_operation(values)...); + break; + case CollectionOperation::Minimum: + add_collection_operation_constraint(collectionOperation.column().type(), operatorType, values...); + break; + case CollectionOperation::Maximum: + add_collection_operation_constraint(collectionOperation.column().type(), operatorType, values...); + break; + case CollectionOperation::Sum: + add_collection_operation_constraint(collectionOperation.column().type(), operatorType, values...); + break; + case CollectionOperation::Average: + add_collection_operation_constraint(collectionOperation.column().type(), operatorType, values...); + break; + } +} + +bool key_path_contains_collection_operator(NSString *keyPath) { + return [keyPath rangeOfString:@"@"].location != NSNotFound; +} + +NSString *get_collection_operation_name_from_key_path(NSString *keyPath, NSString **leadingKeyPath, + NSString **trailingKey) { + NSRange at = [keyPath rangeOfString:@"@"]; + if (at.location == NSNotFound || at.location >= keyPath.length - 1) { + @throw RLMPredicateException(@"Invalid key path", @"'%@' is not a valid key path'", keyPath); + } + + if (at.location == 0 || [keyPath characterAtIndex:at.location - 1] != '.') { + @throw RLMPredicateException(@"Invalid key path", @"'%@' is not a valid key path'", keyPath); + } + + NSRange trailingKeyRange = [keyPath rangeOfString:@"." options:0 range:{at.location, keyPath.length - at.location} locale:nil]; + + *leadingKeyPath = [keyPath substringToIndex:at.location - 1]; + if (trailingKeyRange.location == NSNotFound) { + *trailingKey = nil; + return [keyPath substringFromIndex:at.location]; + } else { + *trailingKey = [keyPath substringFromIndex:trailingKeyRange.location + 1]; + return [keyPath substringWithRange:{at.location, trailingKeyRange.location - at.location}]; + } +} + +CollectionOperation QueryBuilder::collection_operation_from_key_path(RLMObjectSchema *desc, NSString *keyPath) { + NSString *leadingKeyPath; + NSString *trailingKey; + NSString *collectionOperationName = get_collection_operation_name_from_key_path(keyPath, &leadingKeyPath, &trailingKey); + + ColumnReference linkColumn = column_reference_from_key_path(desc, leadingKeyPath, true); + util::Optional column; + if (trailingKey) { + RLMPrecondition([trailingKey rangeOfString:@"."].location == NSNotFound, @"Invalid key path", + @"Right side of collection operator may only have a single level key"); + NSString *fullKeyPath = [leadingKeyPath stringByAppendingFormat:@".%@", trailingKey]; + column = column_reference_from_key_path(desc, fullKeyPath, true); + } + + return {collectionOperationName, std::move(linkColumn), std::move(column)}; +} + +void QueryBuilder::apply_collection_operator_expression(RLMObjectSchema *desc, + NSString *keyPath, id value, + NSComparisonPredicate *pred) { + CollectionOperation operation = collection_operation_from_key_path(desc, keyPath); + operation.validate_comparison(value); + + if (pred.leftExpression.expressionType == NSKeyPathExpressionType) { + add_collection_operation_constraint(pred.predicateOperatorType, operation, operation, value); + } else { + add_collection_operation_constraint(pred.predicateOperatorType, operation, value, operation); + } +} + +void QueryBuilder::apply_value_expression(RLMObjectSchema *desc, + NSString *keyPath, id value, + NSComparisonPredicate *pred) +{ + if (key_path_contains_collection_operator(keyPath)) { + apply_collection_operator_expression(desc, keyPath, value, pred); + return; + } + + bool isAny = pred.comparisonPredicateModifier == NSAnyPredicateModifier; + ColumnReference column = column_reference_from_key_path(desc, keyPath, isAny); + + // check to see if this is a between query + if (pred.predicateOperatorType == NSBetweenPredicateOperatorType) { + add_between_constraint(std::move(column), value); + return; + } + + // turn "key.path IN collection" into ored together ==. "collection IN key.path" is handled elsewhere. + if (pred.predicateOperatorType == NSInPredicateOperatorType) { + process_or_group(m_query, value, [&](id item) { + id normalized = value_from_constant_expression_or_value(item); + validate_property_value(column, normalized, + @"Expected object of type %@ in IN clause for property '%@' on object of type '%@', but received: %@", desc, keyPath); + add_constraint(column.type(), NSEqualToPredicateOperatorType, pred.options, column, normalized); + }); + return; + } + + validate_property_value(column, value, @"Expected object of type %@ for property '%@' on object of type '%@', but received: %@", desc, keyPath); + if (pred.leftExpression.expressionType == NSKeyPathExpressionType) { + add_constraint(column.type(), pred.predicateOperatorType, pred.options, std::move(column), value); + } else { + add_constraint(column.type(), pred.predicateOperatorType, pred.options, value, std::move(column)); + } +} + +void QueryBuilder::apply_column_expression(RLMObjectSchema *desc, + NSString *leftKeyPath, NSString *rightKeyPath, + NSComparisonPredicate *predicate) +{ + bool left_key_path_contains_collection_operator = key_path_contains_collection_operator(leftKeyPath); + bool right_key_path_contains_collection_operator = key_path_contains_collection_operator(rightKeyPath); + if (left_key_path_contains_collection_operator && right_key_path_contains_collection_operator) { + @throw RLMPredicateException(@"Unsupported predicate", @"Key paths including aggregate operations cannot be compared with other aggregate operations."); + } + + if (left_key_path_contains_collection_operator) { + CollectionOperation left = collection_operation_from_key_path(desc, leftKeyPath); + ColumnReference right = column_reference_from_key_path(desc, rightKeyPath, false); + left.validate_comparison(right); + add_collection_operation_constraint(predicate.predicateOperatorType, left, left, std::move(right)); + return; + } + if (right_key_path_contains_collection_operator) { + ColumnReference left = column_reference_from_key_path(desc, leftKeyPath, false); + CollectionOperation right = collection_operation_from_key_path(desc, rightKeyPath); + right.validate_comparison(left); + add_collection_operation_constraint(predicate.predicateOperatorType, right, std::move(left), right); + return; + } + + bool isAny = false; + ColumnReference left = column_reference_from_key_path(desc, leftKeyPath, isAny); + ColumnReference right = column_reference_from_key_path(desc, rightKeyPath, isAny); + + // NOTE: It's assumed that column type must match and no automatic type conversion is supported. + RLMPrecondition(left.type() == right.type(), + RLMPropertiesComparisonTypeMismatchException, + RLMPropertiesComparisonTypeMismatchReason, + RLMTypeToString(left.type()), + RLMTypeToString(right.type())); + + // TODO: Should we handle special case where left row is the same as right row (tautology) + add_constraint(left.type(), predicate.predicateOperatorType, predicate.options, + std::move(left), std::move(right)); +} + +// Identify expressions of the form [SELF valueForKeyPath:] +bool is_self_value_for_key_path_function_expression(NSExpression *expression) +{ + if (expression.expressionType != NSFunctionExpressionType) + return false; + + if (expression.operand.expressionType != NSEvaluatedObjectExpressionType) + return false; + + return [expression.function isEqualToString:@"valueForKeyPath:"]; +} + +// -[NSPredicate predicateWithSubtitutionVariables:] results in function expressions of the form [SELF valueForKeyPath:] +// that apply_predicate cannot handle. Replace such expressions with equivalent NSKeyPathExpressionType expressions. +NSExpression *simplify_self_value_for_key_path_function_expression(NSExpression *expression) { + if (is_self_value_for_key_path_function_expression(expression)) { + if (NSString *keyPath = [expression.arguments.firstObject keyPath]) { + return [NSExpression expressionForKeyPath:keyPath]; + } + } + return expression; +} + +void QueryBuilder::apply_subquery_count_expression(RLMObjectSchema *objectSchema, + NSExpression *subqueryExpression, NSPredicateOperatorType operatorType, NSExpression *right) { + if (right.expressionType != NSConstantValueExpressionType || ![right.constantValue isKindOfClass:[NSNumber class]]) { + @throw RLMPredicateException(@"Invalid predicate expression", @"SUBQUERY(…).@count is only supported when compared with a constant number."); + } + int64_t value = [right.constantValue integerValue]; + + ColumnReference collectionColumn = column_reference_from_key_path(objectSchema, [subqueryExpression.collection keyPath], true); + RLMObjectSchema *collectionMemberObjectSchema = m_schema[collectionColumn.property().objectClassName]; + + // Eliminate references to the iteration variable in the subquery. + NSPredicate *subqueryPredicate = [subqueryExpression.predicate predicateWithSubstitutionVariables:@{ subqueryExpression.variable : [NSExpression expressionForEvaluatedObject] }]; + subqueryPredicate = transformPredicate(subqueryPredicate, simplify_self_value_for_key_path_function_expression); + + Query subquery = RLMPredicateToQuery(subqueryPredicate, collectionMemberObjectSchema, m_schema, m_group); + add_numeric_constraint(RLMPropertyTypeInt, operatorType, + collectionColumn.resolve(std::move(subquery)).count(), value); +} + +void QueryBuilder::apply_function_subquery_expression(RLMObjectSchema *objectSchema, NSExpression *functionExpression, + NSPredicateOperatorType operatorType, NSExpression *right) { + if (![functionExpression.function isEqualToString:@"valueForKeyPath:"] || functionExpression.arguments.count != 1) { + @throw RLMPredicateException(@"Invalid predicate", @"The '%@' function is not supported on the result of a SUBQUERY.", functionExpression.function); + } + + NSExpression *keyPathExpression = functionExpression.arguments.firstObject; + if ([keyPathExpression.keyPath isEqualToString:@"@count"]) { + apply_subquery_count_expression(objectSchema, functionExpression.operand, operatorType, right); + } else { + @throw RLMPredicateException(@"Invalid predicate", @"SUBQUERY is only supported when immediately followed by .@count that is compared with a constant number."); + } +} + +void QueryBuilder::apply_function_expression(RLMObjectSchema *objectSchema, NSExpression *functionExpression, + NSPredicateOperatorType operatorType, NSExpression *right) { + if (functionExpression.operand.expressionType == NSSubqueryExpressionType) { + apply_function_subquery_expression(objectSchema, functionExpression, operatorType, right); + } else { + @throw RLMPredicateException(@"Invalid predicate", @"The '%@' function is not supported.", functionExpression.function); + } +} + + +void QueryBuilder::apply_predicate(NSPredicate *predicate, RLMObjectSchema *objectSchema) +{ + // 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. + m_query.group(); + for (NSPredicate *subp in comp.subpredicates) { + apply_predicate(subp, objectSchema); + } + m_query.end_group(); + } else { + // NSCompoundPredicate's documentation states that an AND predicate with no subpredicates evaluates to TRUE. + m_query.and_query(std::unique_ptr(new TrueExpression)); + } + break; + + case NSOrPredicateType: { + // Add all of the subpredicates with ors inbetween. + process_or_group(m_query, comp.subpredicates, [&](__unsafe_unretained NSPredicate *const subp) { + apply_predicate(subp, objectSchema); + }); + break; + } + + case NSNotPredicateType: + // Add the negated subpredicate + m_query.Not(); + apply_predicate(comp.subpredicates.firstObject, objectSchema); + break; + + default: + @throw RLMPredicateException(@"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)) { + // "key.path IN %@", "key.path IN {…}", "key.path BETWEEN %@", or "key.path BETWEEN {…}". + exp2Type = NSConstantValueExpressionType; + } + else if (compp.predicateOperatorType == NSInPredicateOperatorType && exp1Type == NSConstantValueExpressionType && exp2Type == NSKeyPathExpressionType) { + // "%@ IN key.path" is equivalent to "ANY key.path IN %@". Rewrite the former into the latter. + compp = [NSComparisonPredicate predicateWithLeftExpression:compp.rightExpression rightExpression:compp.leftExpression + modifier:NSAnyPredicateModifier type:NSEqualToPredicateOperatorType options:0]; + exp1Type = NSKeyPathExpressionType; + exp2Type = NSConstantValueExpressionType; + } + else { + if (compp.predicateOperatorType == NSBetweenPredicateOperatorType) { + @throw RLMPredicateException(@"Invalid predicate", + @"Predicate with BETWEEN operator must compare a KeyPath with an aggregate with two values"); + } + else if (compp.predicateOperatorType == NSInPredicateOperatorType) { + @throw RLMPredicateException(@"Invalid predicate", + @"Predicate with IN operator must compare a KeyPath with an aggregate"); + } + } + } + + if (exp1Type == NSKeyPathExpressionType && exp2Type == NSKeyPathExpressionType) { + // both expression are KeyPaths + apply_column_expression(objectSchema, compp.leftExpression.keyPath, compp.rightExpression.keyPath, compp); + } + else if (exp1Type == NSKeyPathExpressionType && exp2Type == NSConstantValueExpressionType) { + // comparing keypath to value + apply_value_expression(objectSchema, compp.leftExpression.keyPath, compp.rightExpression.constantValue, compp); + } + else if (exp1Type == NSConstantValueExpressionType && exp2Type == NSKeyPathExpressionType) { + // comparing value to keypath + apply_value_expression(objectSchema, compp.rightExpression.keyPath, compp.leftExpression.constantValue, compp); + } + else if (exp1Type == NSFunctionExpressionType) { + apply_function_expression(objectSchema, compp.leftExpression, compp.predicateOperatorType, compp.rightExpression); + } + else if (exp1Type == NSSubqueryExpressionType) { + // The subquery expressions that we support are handled by the NSFunctionExpressionType case above. + @throw RLMPredicateException(@"Invalid predicate expression", @"SUBQUERY is only supported when immediately followed by .@count."); + } + else { + @throw RLMPredicateException(@"Invalid predicate expressions", + @"Predicate expressions must compare a keypath and another keypath or a constant value"); + } + } + else if ([predicate isEqual:[NSPredicate predicateWithValue:YES]]) { + m_query.and_query(std::unique_ptr(new TrueExpression)); + } else if ([predicate isEqual:[NSPredicate predicateWithValue:NO]]) { + m_query.and_query(std::unique_ptr(new FalseExpression)); + } + else { + // invalid predicate type + @throw RLMPredicateException(@"Invalid predicate", + @"Only support compound, comparison, and constant predicates"); + } +} +} // namespace + +realm::Query RLMPredicateToQuery(NSPredicate *predicate, RLMObjectSchema *objectSchema, + RLMSchema *schema, Group &group) +{ + auto query = get_table(group, objectSchema).where(); + + // passing a nil predicate is a no-op + if (!predicate) { + return query; + } + + @autoreleasepool { + QueryBuilder(query, group, schema).apply_predicate(predicate, objectSchema); + } + + // Test the constructed query in core + std::string validateMessage = query.validate(); + RLMPrecondition(validateMessage.empty(), @"Invalid query", @"%.*s", + (int)validateMessage.size(), validateMessage.c_str()); + return query; +}