1 ////////////////////////////////////////////////////////////////////////////
3 // Copyright 2014 Realm Inc.
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
9 // http://www.apache.org/licenses/LICENSE-2.0
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
17 ////////////////////////////////////////////////////////////////////////////
19 #import "RLMQueryUtil.hpp"
22 #import "RLMObjectSchema_Private.h"
23 #import "RLMObject_Private.hpp"
24 #import "RLMPredicateUtil.hpp"
25 #import "RLMProperty_Private.h"
29 #import "object_store.hpp"
32 #include <realm/query_engine.hpp>
33 #include <realm/query_expression.hpp>
34 #include <realm/util/cf_ptr.hpp>
35 #include <realm/util/overload.hpp>
37 using namespace realm;
39 NSString * const RLMPropertiesComparisonTypeMismatchException = @"RLMPropertiesComparisonTypeMismatchException";
40 NSString * const RLMUnsupportedTypesFoundInPropertyComparisonException = @"RLMUnsupportedTypesFoundInPropertyComparisonException";
42 NSString * const RLMPropertiesComparisonTypeMismatchReason = @"Property type mismatch between %@ and %@";
43 NSString * const RLMUnsupportedTypesFoundInPropertyComparisonReason = @"Comparison between %@ and %@";
45 // small helper to create the many exceptions thrown when parsing predicates
46 static NSException *RLMPredicateException(NSString *name, NSString *format, ...) {
48 va_start(args, format);
49 NSString *reason = [[NSString alloc] initWithFormat:format arguments:args];
52 return [NSException exceptionWithName:name reason:reason userInfo:nil];
55 // check a precondition and throw an exception if it is not met
56 // this should be used iff the condition being false indicates a bug in the caller
57 // of the function checking its preconditions
58 static void RLMPrecondition(bool condition, NSString *name, NSString *format, ...) {
59 if (__builtin_expect(condition, 1)) {
64 va_start(args, format);
65 NSString *reason = [[NSString alloc] initWithFormat:format arguments:args];
68 @throw [NSException exceptionWithName:name reason:reason userInfo:nil];
71 // return the property for a validated column name
72 RLMProperty *RLMValidatedProperty(RLMObjectSchema *desc, NSString *columnName) {
73 RLMProperty *prop = desc[columnName];
74 RLMPrecondition(prop, @"Invalid property name",
75 @"Property '%@' not found in object of type '%@'", columnName, desc.className);
80 BOOL RLMPropertyTypeIsNumeric(RLMPropertyType propertyType) {
81 switch (propertyType) {
82 case RLMPropertyTypeInt:
83 case RLMPropertyTypeFloat:
84 case RLMPropertyTypeDouble:
91 // Equal and ContainsSubstring are used by QueryBuilder::add_string_constraint as the comparator
92 // for performing diacritic-insensitive comparisons.
94 bool equal(CFStringCompareFlags options, StringData v1, StringData v2)
96 if (v1.is_null() || v2.is_null()) {
97 return v1.is_null() == v2.is_null();
100 auto s1 = util::adoptCF(CFStringCreateWithBytesNoCopy(kCFAllocatorSystemDefault, (const UInt8*)v1.data(), v1.size(),
101 kCFStringEncodingUTF8, false, kCFAllocatorNull));
102 auto s2 = util::adoptCF(CFStringCreateWithBytesNoCopy(kCFAllocatorSystemDefault, (const UInt8*)v2.data(), v2.size(),
103 kCFStringEncodingUTF8, false, kCFAllocatorNull));
105 return CFStringCompare(s1.get(), s2.get(), options) == kCFCompareEqualTo;
108 template <CFStringCompareFlags options>
110 using CaseSensitive = Equal<options & ~kCFCompareCaseInsensitive>;
111 using CaseInsensitive = Equal<options | kCFCompareCaseInsensitive>;
113 bool operator()(StringData v1, StringData v2, bool v1_null, bool v2_null) const
115 REALM_ASSERT_DEBUG(v1_null == v1.is_null());
116 REALM_ASSERT_DEBUG(v2_null == v2.is_null());
118 return equal(options, v1, v2);
121 // FIXME: Consider the options.
122 static const char* description() { return "equal"; }
125 bool contains_substring(CFStringCompareFlags options, StringData v1, StringData v2)
128 // Everything contains NULL
133 // NULL contains nothing (except NULL, handled above)
137 if (v2.size() == 0) {
138 // Everything (except NULL, handled above) contains the empty string
142 auto s1 = util::adoptCF(CFStringCreateWithBytesNoCopy(kCFAllocatorSystemDefault, (const UInt8*)v1.data(), v1.size(),
143 kCFStringEncodingUTF8, false, kCFAllocatorNull));
144 auto s2 = util::adoptCF(CFStringCreateWithBytesNoCopy(kCFAllocatorSystemDefault, (const UInt8*)v2.data(), v2.size(),
145 kCFStringEncodingUTF8, false, kCFAllocatorNull));
147 return CFStringFind(s1.get(), s2.get(), options).location != kCFNotFound;
150 template <CFStringCompareFlags options>
151 struct ContainsSubstring {
152 using CaseSensitive = ContainsSubstring<options & ~kCFCompareCaseInsensitive>;
153 using CaseInsensitive = ContainsSubstring<options | kCFCompareCaseInsensitive>;
155 bool operator()(StringData v1, StringData v2, bool v1_null, bool v2_null) const
157 REALM_ASSERT_DEBUG(v1_null == v1.is_null());
158 REALM_ASSERT_DEBUG(v2_null == v2.is_null());
160 return contains_substring(options, v1, v2);
163 // FIXME: Consider the options.
164 static const char* description() { return "contains"; }
168 NSString *operatorName(NSPredicateOperatorType operatorType)
170 switch (operatorType) {
171 case NSLessThanPredicateOperatorType:
173 case NSLessThanOrEqualToPredicateOperatorType:
175 case NSGreaterThanPredicateOperatorType:
177 case NSGreaterThanOrEqualToPredicateOperatorType:
179 case NSEqualToPredicateOperatorType:
181 case NSNotEqualToPredicateOperatorType:
183 case NSMatchesPredicateOperatorType:
185 case NSLikePredicateOperatorType:
187 case NSBeginsWithPredicateOperatorType:
188 return @"BEGINSWITH";
189 case NSEndsWithPredicateOperatorType:
191 case NSInPredicateOperatorType:
193 case NSContainsPredicateOperatorType:
195 case NSBetweenPredicateOperatorType:
197 case NSCustomSelectorPredicateOperatorType:
198 return @"custom selector";
201 return [NSString stringWithFormat:@"unknown operator %lu", (unsigned long)operatorType];
204 Table& get_table(Group& group, RLMObjectSchema *objectSchema)
206 return *ObjectStore::table_for_object_type(group, objectSchema.objectName.UTF8String);
209 // A reference to a column within a query. Can be resolved to a Columns<T> for use in query expressions.
210 class ColumnReference {
212 ColumnReference(Query& query, Group& group, RLMSchema *schema, RLMProperty* property, const std::vector<RLMProperty*>& links = {})
213 : m_links(links), m_property(property), m_schema(schema), m_group(&group), m_query(&query), m_table(query.get_table().get())
215 auto& table = walk_link_chain([](Table&, size_t, RLMPropertyType) { });
216 m_index = table.get_column_index(m_property.name.UTF8String);
219 template <typename T, typename... SubQuery>
220 auto resolve(SubQuery&&... subquery) const
222 static_assert(sizeof...(SubQuery) < 2, "resolve() takes at most one subquery");
223 set_link_chain_on_table();
224 if (type() != RLMPropertyTypeLinkingObjects) {
225 return m_table->template column<T>(index(), std::forward<SubQuery>(subquery)...);
228 return resolve_backlink<T>(std::forward<SubQuery>(subquery)...);
232 RLMProperty *property() const { return m_property; }
233 size_t index() const { return m_index; }
234 RLMPropertyType type() const { return property().type; }
235 Group& group() const { return *m_group; }
237 RLMObjectSchema *link_target_object_schema() const
240 case RLMPropertyTypeObject:
241 case RLMPropertyTypeLinkingObjects:
242 return m_schema[property().objectClassName];
248 bool has_links() const { return m_links.size(); }
250 bool has_any_to_many_links() const {
251 return std::any_of(begin(m_links), end(m_links),
252 [](RLMProperty *property) { return property.array; });
255 ColumnReference last_link_column() const {
256 REALM_ASSERT(!m_links.empty());
257 return {*m_query, *m_group, m_schema, m_links.back(), {m_links.begin(), m_links.end() - 1}};
260 ColumnReference column_ignoring_links(Query& query) const {
261 return {query, *m_group, m_schema, m_property};
265 template <typename T, typename... SubQuery>
266 auto resolve_backlink(SubQuery&&... subquery) const
268 // We actually just want `if constexpr (std::is_same<T, Link>::value) { ... }`,
269 // so fake it by tag-dispatching on the conditional
270 return do_resolve_backlink<T>(std::is_same<T, Link>(), std::forward<SubQuery>(subquery)...);
273 template <typename T, typename... SubQuery>
274 auto do_resolve_backlink(std::true_type, SubQuery&&... subquery) const
276 return with_link_origin(m_property, [&](Table& table, size_t col) {
277 return m_table->template column<T>(table, col, std::forward<SubQuery>(subquery)...);
281 template <typename T, typename... SubQuery>
282 Columns<T> do_resolve_backlink(std::false_type, SubQuery&&...) const
284 // This can't actually happen as we only call resolve_backlink() if
285 // it's RLMPropertyTypeLinkingObjects
286 __builtin_unreachable();
289 template<typename Func>
290 Table& walk_link_chain(Func&& func) const
292 auto table = m_query->get_table().get();
293 for (const auto& link : m_links) {
294 if (link.type != RLMPropertyTypeLinkingObjects) {
295 auto index = table->get_column_index(link.name.UTF8String);
296 func(*table, index, link.type);
297 table = table->get_link_target(index).get();
300 with_link_origin(link, [&](Table& link_origin_table, size_t link_origin_column) {
301 func(link_origin_table, link_origin_column, link.type);
302 table = &link_origin_table;
309 template<typename Func>
310 auto with_link_origin(RLMProperty *prop, Func&& func) const
312 RLMObjectSchema *link_origin_schema = m_schema[prop.objectClassName];
313 Table& link_origin_table = get_table(*m_group, link_origin_schema);
314 size_t link_origin_column = link_origin_table.get_column_index(prop.linkOriginPropertyName.UTF8String);
315 return func(link_origin_table, link_origin_column);
318 void set_link_chain_on_table() const
320 walk_link_chain([&](Table& current_table, size_t column, RLMPropertyType type) {
321 if (type == RLMPropertyTypeLinkingObjects) {
322 m_table->backlink(current_table, column);
325 m_table->link(column);
330 std::vector<RLMProperty*> m_links;
331 RLMProperty *m_property;
339 class CollectionOperation {
349 CollectionOperation(Type type, ColumnReference link_column, util::Optional<ColumnReference> column)
351 , m_link_column(std::move(link_column))
352 , m_column(std::move(column))
354 RLMPrecondition(m_link_column.property().array,
355 @"Invalid predicate", @"Collection operation can only be applied to a property of type RLMArray.");
359 RLMPrecondition(!m_column, @"Invalid predicate", @"Result of @count does not have any properties.");
365 RLMPrecondition(m_column && RLMPropertyTypeIsNumeric(m_column->type()), @"Invalid predicate",
366 @"%@ can only be applied to a numeric property.", name_for_type(m_type));
371 CollectionOperation(NSString *operationName, ColumnReference link_column, util::Optional<ColumnReference> column = util::none)
372 : CollectionOperation(type_for_name(operationName), std::move(link_column), std::move(column))
376 Type type() const { return m_type; }
377 const ColumnReference& link_column() const { return m_link_column; }
378 const ColumnReference& column() const { return *m_column; }
380 void validate_comparison(id value) const {
384 RLMPrecondition([value isKindOfClass:[NSNumber class]], @"Invalid operand",
385 @"%@ can only be compared with a numeric value.", name_for_type(m_type));
390 RLMPrecondition(RLMIsObjectValidForProperty(value, m_column->property()), @"Invalid operand",
391 @"%@ on a property of type %@ cannot be compared with '%@'",
392 name_for_type(m_type), RLMTypeToString(m_column->type()), value);
397 void validate_comparison(const ColumnReference& column) const {
400 RLMPrecondition(RLMPropertyTypeIsNumeric(column.type()), @"Invalid operand",
401 @"%@ can only be compared with a numeric value.", name_for_type(m_type));
407 RLMPrecondition(RLMPropertyTypeIsNumeric(column.type()), @"Invalid operand",
408 @"%@ on a property of type %@ cannot be compared with property of type '%@'",
409 name_for_type(m_type), RLMTypeToString(m_column->type()), RLMTypeToString(column.type()));
415 static Type type_for_name(NSString *name) {
416 if ([name isEqualToString:@"@count"]) {
419 if ([name isEqualToString:@"@min"]) {
422 if ([name isEqualToString:@"@max"]) {
425 if ([name isEqualToString:@"@sum"]) {
428 if ([name isEqualToString:@"@avg"]) {
431 @throw RLMPredicateException(@"Invalid predicate", @"Unsupported collection operation '%@'", name);
434 static NSString *name_for_type(Type type) {
436 case Count: return @"@count";
437 case Minimum: return @"@min";
438 case Maximum: return @"@max";
439 case Sum: return @"@sum";
440 case Average: return @"@avg";
445 ColumnReference m_link_column;
446 util::Optional<ColumnReference> m_column;
451 QueryBuilder(Query& query, Group& group, RLMSchema *schema)
452 : m_query(query), m_group(group), m_schema(schema) { }
454 void apply_predicate(NSPredicate *predicate, RLMObjectSchema *objectSchema);
457 void apply_collection_operator_expression(RLMObjectSchema *desc, NSString *keyPath, id value, NSComparisonPredicate *pred);
458 void apply_value_expression(RLMObjectSchema *desc, NSString *keyPath, id value, NSComparisonPredicate *pred);
459 void apply_column_expression(RLMObjectSchema *desc, NSString *leftKeyPath, NSString *rightKeyPath, NSComparisonPredicate *predicate);
460 void apply_subquery_count_expression(RLMObjectSchema *objectSchema, NSExpression *subqueryExpression,
461 NSPredicateOperatorType operatorType, NSExpression *right);
462 void apply_function_subquery_expression(RLMObjectSchema *objectSchema, NSExpression *functionExpression,
463 NSPredicateOperatorType operatorType, NSExpression *right);
464 void apply_function_expression(RLMObjectSchema *objectSchema, NSExpression *functionExpression,
465 NSPredicateOperatorType operatorType, NSExpression *right);
468 template <typename A, typename B>
469 void add_numeric_constraint(RLMPropertyType datatype,
470 NSPredicateOperatorType operatorType,
473 template <typename A, typename B>
474 void add_bool_constraint(NSPredicateOperatorType operatorType, A lhs, B rhs);
476 void add_substring_constraint(null, Query condition);
478 void add_substring_constraint(const T& value, Query condition);
480 void add_substring_constraint(const Columns<T>& value, Query condition);
482 template <typename T>
483 void add_string_constraint(NSPredicateOperatorType operatorType,
484 NSComparisonPredicateOptions predicateOptions,
485 Columns<String> &&column,
488 void add_string_constraint(NSPredicateOperatorType operatorType,
489 NSComparisonPredicateOptions predicateOptions,
491 Columns<String>&& column);
493 template <typename L, typename R>
494 void add_constraint(RLMPropertyType type,
495 NSPredicateOperatorType operatorType,
496 NSComparisonPredicateOptions predicateOptions,
498 template <typename... T>
499 void do_add_constraint(RLMPropertyType type, NSPredicateOperatorType operatorType,
500 NSComparisonPredicateOptions predicateOptions, T... values);
501 void do_add_constraint(RLMPropertyType, NSPredicateOperatorType, NSComparisonPredicateOptions, id, realm::null);
503 void add_between_constraint(const ColumnReference& column, id value);
505 void add_binary_constraint(NSPredicateOperatorType operatorType, const ColumnReference& column, BinaryData value);
506 void add_binary_constraint(NSPredicateOperatorType operatorType, const ColumnReference& column, id value);
507 void add_binary_constraint(NSPredicateOperatorType operatorType, const ColumnReference& column, null);
508 void add_binary_constraint(NSPredicateOperatorType operatorType, id value, const ColumnReference& column);
509 void add_binary_constraint(NSPredicateOperatorType, const ColumnReference&, const ColumnReference&);
511 void add_link_constraint(NSPredicateOperatorType operatorType, const ColumnReference& column, RLMObject *obj);
512 void add_link_constraint(NSPredicateOperatorType operatorType, const ColumnReference& column, realm::null);
514 void add_link_constraint(NSPredicateOperatorType operatorType, T obj, const ColumnReference& column);
515 void add_link_constraint(NSPredicateOperatorType, const ColumnReference&, const ColumnReference&);
517 template <CollectionOperation::Type Operation, typename... T>
518 void add_collection_operation_constraint(RLMPropertyType propertyType, NSPredicateOperatorType operatorType, T... values);
519 template <typename... T>
520 void add_collection_operation_constraint(NSPredicateOperatorType operatorType,
521 CollectionOperation collectionOperation, T... values);
524 CollectionOperation collection_operation_from_key_path(RLMObjectSchema *desc, NSString *keyPath);
525 ColumnReference column_reference_from_key_path(RLMObjectSchema *objectSchema, NSString *keyPath, bool isAggregate);
533 // add a clause for numeric constraints based on operator type
534 template <typename A, typename B>
535 void QueryBuilder::add_numeric_constraint(RLMPropertyType datatype,
536 NSPredicateOperatorType operatorType,
539 switch (operatorType) {
540 case NSLessThanPredicateOperatorType:
541 m_query.and_query(lhs < rhs);
543 case NSLessThanOrEqualToPredicateOperatorType:
544 m_query.and_query(lhs <= rhs);
546 case NSGreaterThanPredicateOperatorType:
547 m_query.and_query(lhs > rhs);
549 case NSGreaterThanOrEqualToPredicateOperatorType:
550 m_query.and_query(lhs >= rhs);
552 case NSEqualToPredicateOperatorType:
553 m_query.and_query(lhs == rhs);
555 case NSNotEqualToPredicateOperatorType:
556 m_query.and_query(lhs != rhs);
559 @throw RLMPredicateException(@"Invalid operator type",
560 @"Operator '%@' not supported for type %@",
561 operatorName(operatorType), RLMTypeToString(datatype));
565 template <typename A, typename B>
566 void QueryBuilder::add_bool_constraint(NSPredicateOperatorType operatorType, A lhs, B rhs) {
567 switch (operatorType) {
568 case NSEqualToPredicateOperatorType:
569 m_query.and_query(lhs == rhs);
571 case NSNotEqualToPredicateOperatorType:
572 m_query.and_query(lhs != rhs);
575 @throw RLMPredicateException(@"Invalid operator type",
576 @"Operator '%@' not supported for bool type", operatorName(operatorType));
580 void QueryBuilder::add_substring_constraint(null, Query) {
581 // Foundation always returns false for substring operations with a RHS of null or "".
582 m_query.and_query(std::unique_ptr<Expression>(new FalseExpression));
586 void QueryBuilder::add_substring_constraint(const T& value, Query condition) {
587 // Foundation always returns false for substring operations with a RHS of null or "".
588 m_query.and_query(value.size()
589 ? std::move(condition)
590 : std::unique_ptr<Expression>(new FalseExpression));
594 void QueryBuilder::add_substring_constraint(const Columns<T>& value, Query condition) {
595 // Foundation always returns false for substring operations with a RHS of null or "".
596 // We don't need to concern ourselves with the possibility of value traversing a link list
597 // and producing multiple values per row as such expressions will have been rejected.
598 m_query.and_query(const_cast<Columns<String>&>(value).size() != 0 && std::move(condition));
601 template <typename T>
602 void QueryBuilder::add_string_constraint(NSPredicateOperatorType operatorType,
603 NSComparisonPredicateOptions predicateOptions,
604 Columns<String> &&column,
606 bool caseSensitive = !(predicateOptions & NSCaseInsensitivePredicateOption);
607 bool diacriticSensitive = !(predicateOptions & NSDiacriticInsensitivePredicateOption);
609 if (diacriticSensitive) {
610 switch (operatorType) {
611 case NSBeginsWithPredicateOperatorType:
612 add_substring_constraint(value, column.begins_with(value, caseSensitive));
614 case NSEndsWithPredicateOperatorType:
615 add_substring_constraint(value, column.ends_with(value, caseSensitive));
617 case NSContainsPredicateOperatorType:
618 add_substring_constraint(value, column.contains(value, caseSensitive));
620 case NSEqualToPredicateOperatorType:
621 m_query.and_query(column.equal(value, caseSensitive));
623 case NSNotEqualToPredicateOperatorType:
624 m_query.and_query(column.not_equal(value, caseSensitive));
626 case NSLikePredicateOperatorType:
627 m_query.and_query(column.like(value, caseSensitive));
630 @throw RLMPredicateException(@"Invalid operator type",
631 @"Operator '%@' not supported for string type",
632 operatorName(operatorType));
637 auto as_subexpr = util::overload([](StringData value) { return make_subexpr<ConstantStringValue>(value); },
638 [](const Columns<String>& c) { return c.clone(); });
639 auto left = as_subexpr(column);
640 auto right = as_subexpr(value);
642 auto make_constraint = [&](auto comparator) {
643 using Comparator = decltype(comparator);
644 using CompareCS = Compare<typename Comparator::CaseSensitive, StringData>;
645 using CompareCI = Compare<typename Comparator::CaseInsensitive, StringData>;
647 return make_expression<CompareCS>(std::move(left), std::move(right));
650 return make_expression<CompareCI>(std::move(left), std::move(right));
654 switch (operatorType) {
655 case NSBeginsWithPredicateOperatorType: {
656 using C = ContainsSubstring<kCFCompareDiacriticInsensitive | kCFCompareAnchored>;
657 add_substring_constraint(value, make_constraint(C{}));
660 case NSEndsWithPredicateOperatorType: {
661 using C = ContainsSubstring<kCFCompareDiacriticInsensitive | kCFCompareAnchored | kCFCompareBackwards>;
662 add_substring_constraint(value, make_constraint(C{}));
665 case NSContainsPredicateOperatorType: {
666 using C = ContainsSubstring<kCFCompareDiacriticInsensitive>;
667 add_substring_constraint(value, make_constraint(C{}));
670 case NSNotEqualToPredicateOperatorType:
673 case NSEqualToPredicateOperatorType:
674 m_query.and_query(make_constraint(Equal<kCFCompareDiacriticInsensitive>{}));
676 case NSLikePredicateOperatorType:
677 @throw RLMPredicateException(@"Invalid operator type",
678 @"Operator 'LIKE' not supported with diacritic-insensitive modifier.");
680 @throw RLMPredicateException(@"Invalid operator type",
681 @"Operator '%@' not supported for string type", operatorName(operatorType));
685 void QueryBuilder::add_string_constraint(NSPredicateOperatorType operatorType,
686 NSComparisonPredicateOptions predicateOptions,
688 Columns<String>&& column) {
689 switch (operatorType) {
690 case NSEqualToPredicateOperatorType:
691 case NSNotEqualToPredicateOperatorType:
692 add_string_constraint(operatorType, predicateOptions, std::move(column), value);
695 @throw RLMPredicateException(@"Invalid operator type",
696 @"Operator '%@' is not supported for string type with key path on right side of operator",
697 operatorName(operatorType));
701 id value_from_constant_expression_or_value(id value) {
702 if (NSExpression *exp = RLMDynamicCast<NSExpression>(value)) {
703 RLMPrecondition(exp.expressionType == NSConstantValueExpressionType,
705 @"Expressions within predicate aggregates must be constant values");
706 return exp.constantValue;
711 void validate_and_extract_between_range(id value, RLMProperty *prop, id *from, id *to) {
712 NSArray *array = RLMDynamicCast<NSArray>(value);
713 RLMPrecondition(array, @"Invalid value", @"object must be of type NSArray for BETWEEN operations");
714 RLMPrecondition(array.count == 2, @"Invalid value", @"NSArray object must contain exactly two objects for BETWEEN operations");
716 *from = value_from_constant_expression_or_value(array.firstObject);
717 *to = value_from_constant_expression_or_value(array.lastObject);
718 RLMPrecondition(RLMIsObjectValidForProperty(*from, prop) && RLMIsObjectValidForProperty(*to, prop),
720 @"NSArray objects must be of type %@ for BETWEEN operations", RLMTypeToString(prop.type));
723 void QueryBuilder::add_between_constraint(const ColumnReference& column, id value) {
724 if (column.has_any_to_many_links()) {
725 auto link_column = column.last_link_column();
726 Query subquery = get_table(m_group, link_column.link_target_object_schema()).where();
727 QueryBuilder(subquery, m_group, m_schema).add_between_constraint(column.column_ignoring_links(subquery), value);
729 m_query.and_query(link_column.resolve<Link>(std::move(subquery)).count() > 0);
734 validate_and_extract_between_range(value, column.property(), &from, &to);
736 RLMPropertyType type = column.type();
739 add_constraint(type, NSGreaterThanOrEqualToPredicateOperatorType, 0, column, from);
740 add_constraint(type, NSLessThanOrEqualToPredicateOperatorType, 0, column, to);
744 void QueryBuilder::add_binary_constraint(NSPredicateOperatorType operatorType,
745 const ColumnReference& column,
747 RLMPrecondition(!column.has_links(), @"Unsupported operator", @"NSData properties cannot be queried over an object link.");
749 size_t index = column.index();
750 Query query = m_query.get_table()->where();
752 switch (operatorType) {
753 case NSBeginsWithPredicateOperatorType:
754 add_substring_constraint(value, query.begins_with(index, value));
756 case NSEndsWithPredicateOperatorType:
757 add_substring_constraint(value, query.ends_with(index, value));
759 case NSContainsPredicateOperatorType:
760 add_substring_constraint(value, query.contains(index, value));
762 case NSEqualToPredicateOperatorType:
763 m_query.equal(index, value);
765 case NSNotEqualToPredicateOperatorType:
766 m_query.not_equal(index, value);
769 @throw RLMPredicateException(@"Invalid operator type",
770 @"Operator '%@' not supported for binary type", operatorName(operatorType));
774 void QueryBuilder::add_binary_constraint(NSPredicateOperatorType operatorType, const ColumnReference& column, id value) {
775 add_binary_constraint(operatorType, column, RLMBinaryDataForNSData(value));
778 void QueryBuilder::add_binary_constraint(NSPredicateOperatorType operatorType, const ColumnReference& column, null) {
779 add_binary_constraint(operatorType, column, BinaryData());
782 void QueryBuilder::add_binary_constraint(NSPredicateOperatorType operatorType, id value, const ColumnReference& column) {
783 switch (operatorType) {
784 case NSEqualToPredicateOperatorType:
785 case NSNotEqualToPredicateOperatorType:
786 add_binary_constraint(operatorType, column, value);
789 @throw RLMPredicateException(@"Invalid operator type",
790 @"Operator '%@' is not supported for binary type with key path on right side of operator",
791 operatorName(operatorType));
795 void QueryBuilder::add_binary_constraint(NSPredicateOperatorType, const ColumnReference&, const ColumnReference&) {
796 @throw RLMPredicateException(@"Invalid predicate", @"Comparisons between two NSData properties are not supported");
799 void QueryBuilder::add_link_constraint(NSPredicateOperatorType operatorType,
800 const ColumnReference& column, RLMObject *obj) {
801 RLMPrecondition(operatorType == NSEqualToPredicateOperatorType || operatorType == NSNotEqualToPredicateOperatorType,
802 @"Invalid operator type", @"Only 'Equal' and 'Not Equal' operators supported for object comparison");
804 if (operatorType == NSEqualToPredicateOperatorType) {
805 m_query.and_query(column.resolve<Link>() == obj->_row);
808 m_query.and_query(column.resolve<Link>() != obj->_row);
812 void QueryBuilder::add_link_constraint(NSPredicateOperatorType operatorType,
813 const ColumnReference& column,
815 RLMPrecondition(operatorType == NSEqualToPredicateOperatorType || operatorType == NSNotEqualToPredicateOperatorType,
816 @"Invalid operator type", @"Only 'Equal' and 'Not Equal' operators supported for object comparison");
818 if (operatorType == NSEqualToPredicateOperatorType) {
819 m_query.and_query(column.resolve<Link>() == null());
822 m_query.and_query(column.resolve<Link>() != null());
827 void QueryBuilder::add_link_constraint(NSPredicateOperatorType operatorType, T obj, const ColumnReference& column) {
828 // Link constraints only support the equal-to and not-equal-to operators. The order of operands
829 // is not important for those comparisons so we can delegate to the other implementation.
830 add_link_constraint(operatorType, column, obj);
833 void QueryBuilder::add_link_constraint(NSPredicateOperatorType, const ColumnReference&, const ColumnReference&) {
834 // This is not actually reachable as this case is caught earlier, but this
835 // overload is needed for the code to compile
836 @throw RLMPredicateException(@"Invalid predicate", @"Comparisons between two RLMArray properties are not supported");
840 // iterate over an array of subpredicates, using @func to build a query from each
841 // one and ORing them together
842 template<typename Func>
843 void process_or_group(Query &query, id array, Func&& func) {
844 RLMPrecondition([array conformsToProtocol:@protocol(NSFastEnumeration)],
845 @"Invalid value", @"IN clause requires an array of items");
850 for (id item in array) {
860 // Queries can't be empty, so if there's zero things in the OR group
861 // validation will fail. Work around this by adding an expression which
862 // will never find any rows in a table.
863 query.and_query(std::unique_ptr<Expression>(new FalseExpression));
869 template <typename RequestedType>
870 RequestedType convert(id value);
873 Timestamp convert<Timestamp>(id value) {
874 return RLMTimestampForNSDate(value);
878 bool convert<bool>(id value) {
879 return [value boolValue];
883 Double convert<Double>(id value) {
884 return [value doubleValue];
888 Float convert<Float>(id value) {
889 return [value floatValue];
893 Int convert<Int>(id value) {
894 return [value longLongValue];
898 String convert<String>(id value) {
899 return RLMStringDataWithNSString(value);
903 realm::null value_of_type(realm::null) {
904 return realm::null();
907 template <typename RequestedType>
908 auto value_of_type(id value) {
909 return ::convert<RequestedType>(value);
912 template <typename RequestedType>
913 auto value_of_type(const ColumnReference& column) {
914 return column.resolve<RequestedType>();
918 template <typename... T>
919 void QueryBuilder::do_add_constraint(RLMPropertyType type, NSPredicateOperatorType operatorType,
920 NSComparisonPredicateOptions predicateOptions, T... values)
922 static_assert(sizeof...(T) == 2, "do_add_constraint accepts only two values as arguments");
925 case RLMPropertyTypeBool:
926 add_bool_constraint(operatorType, value_of_type<bool>(values)...);
928 case RLMPropertyTypeDate:
929 add_numeric_constraint(type, operatorType, value_of_type<realm::Timestamp>(values)...);
931 case RLMPropertyTypeDouble:
932 add_numeric_constraint(type, operatorType, value_of_type<Double>(values)...);
934 case RLMPropertyTypeFloat:
935 add_numeric_constraint(type, operatorType, value_of_type<Float>(values)...);
937 case RLMPropertyTypeInt:
938 add_numeric_constraint(type, operatorType, value_of_type<Int>(values)...);
940 case RLMPropertyTypeString:
941 add_string_constraint(operatorType, predicateOptions, value_of_type<String>(values)...);
943 case RLMPropertyTypeData:
944 add_binary_constraint(operatorType, values...);
946 case RLMPropertyTypeObject:
947 case RLMPropertyTypeLinkingObjects:
948 add_link_constraint(operatorType, values...);
951 @throw RLMPredicateException(@"Unsupported predicate value type",
952 @"Object type %@ not supported", RLMTypeToString(type));
956 void QueryBuilder::do_add_constraint(RLMPropertyType, NSPredicateOperatorType, NSComparisonPredicateOptions, id, realm::null)
958 // This is not actually reachable as this case is caught earlier, but this
959 // overload is needed for the code to compile
960 @throw RLMPredicateException(@"Invalid predicate expressions",
961 @"Predicate expressions must compare a keypath and another keypath or a constant value");
964 bool is_nsnull(id value) {
965 return !value || value == NSNull.null;
973 template <typename L, typename R>
974 void QueryBuilder::add_constraint(RLMPropertyType type, NSPredicateOperatorType operatorType,
975 NSComparisonPredicateOptions predicateOptions, L lhs, R rhs)
977 // The expression operators are only overloaded for realm::null on the rhs
978 RLMPrecondition(!is_nsnull(lhs), @"Unsupported operator",
979 @"Nil is only supported on the right side of operators");
981 if (is_nsnull(rhs)) {
982 do_add_constraint(type, operatorType, predicateOptions, lhs, realm::null());
985 do_add_constraint(type, operatorType, predicateOptions, lhs, rhs);
990 std::vector<RLMProperty *> links;
991 RLMProperty *property;
992 bool containsToManyRelationship;
995 KeyPath key_path_from_string(RLMSchema *schema, RLMObjectSchema *objectSchema, NSString *keyPath)
997 RLMProperty *property;
998 std::vector<RLMProperty *> links;
1000 bool keyPathContainsToManyRelationship = false;
1002 NSUInteger start = 0, length = keyPath.length, end = NSNotFound;
1004 end = [keyPath rangeOfString:@"." options:0 range:{start, length - start}].location;
1005 NSString *propertyName = [keyPath substringWithRange:{start, end == NSNotFound ? length - start : end - start}];
1006 property = objectSchema[propertyName];
1007 RLMPrecondition(property, @"Invalid property name",
1008 @"Property '%@' not found in object of type '%@'",
1009 propertyName, objectSchema.className);
1012 keyPathContainsToManyRelationship = true;
1014 if (end != NSNotFound) {
1015 RLMPrecondition(property.type == RLMPropertyTypeObject || property.type == RLMPropertyTypeLinkingObjects,
1016 @"Invalid value", @"Property '%@' is not a link in object of type '%@'",
1017 propertyName, objectSchema.className);
1019 links.push_back(property);
1020 REALM_ASSERT(property.objectClassName);
1021 objectSchema = schema[property.objectClassName];
1025 } while (end != NSNotFound);
1027 return {std::move(links), property, keyPathContainsToManyRelationship};
1030 ColumnReference QueryBuilder::column_reference_from_key_path(RLMObjectSchema *objectSchema,
1031 NSString *keyPathString, bool isAggregate)
1033 auto keyPath = key_path_from_string(m_schema, objectSchema, keyPathString);
1035 if (isAggregate && !keyPath.containsToManyRelationship) {
1036 @throw RLMPredicateException(@"Invalid predicate",
1037 @"Aggregate operations can only be used on key paths that include an array property");
1038 } else if (!isAggregate && keyPath.containsToManyRelationship) {
1039 @throw RLMPredicateException(@"Invalid predicate",
1040 @"Key paths that include an array property must use aggregate operations");
1043 return ColumnReference(m_query, m_group, m_schema, keyPath.property, std::move(keyPath.links));
1046 void validate_property_value(const ColumnReference& column,
1047 __unsafe_unretained id const value,
1048 __unsafe_unretained NSString *const err,
1049 __unsafe_unretained RLMObjectSchema *const objectSchema,
1050 __unsafe_unretained NSString *const keyPath) {
1051 RLMProperty *prop = column.property();
1053 RLMPrecondition([RLMObjectBaseObjectSchema(RLMDynamicCast<RLMObjectBase>(value)).className isEqualToString:prop.objectClassName],
1054 @"Invalid value", err, prop.objectClassName, keyPath, objectSchema.className, value);
1057 RLMPrecondition(RLMIsObjectValidForProperty(value, prop),
1058 @"Invalid value", err, RLMTypeToString(prop.type), keyPath, objectSchema.className, value);
1060 if (RLMObjectBase *obj = RLMDynamicCast<RLMObjectBase>(value)) {
1061 RLMPrecondition(!obj->_row.is_attached() || &column.group() == &obj->_realm.group,
1062 @"Invalid value origin", @"Object must be from the Realm being queried");
1066 template <typename RequestedType, CollectionOperation::Type OperationType>
1067 struct ValueOfTypeWithCollectionOperationHelper;
1070 struct ValueOfTypeWithCollectionOperationHelper<Int, CollectionOperation::Count> {
1071 static auto convert(const CollectionOperation& operation)
1073 assert(operation.type() == CollectionOperation::Count);
1074 return operation.link_column().resolve<Link>().count();
1078 #define VALUE_OF_TYPE_WITH_COLLECTION_OPERATOR_HELPER(OperationType, function) \
1079 template <typename T> \
1080 struct ValueOfTypeWithCollectionOperationHelper<T, OperationType> { \
1081 static auto convert(const CollectionOperation& operation) \
1083 REALM_ASSERT(operation.type() == OperationType); \
1084 auto targetColumn = operation.link_column().resolve<Link>().template column<T>(operation.column().index()); \
1085 return targetColumn.function(); \
1089 VALUE_OF_TYPE_WITH_COLLECTION_OPERATOR_HELPER(CollectionOperation::Minimum, min);
1090 VALUE_OF_TYPE_WITH_COLLECTION_OPERATOR_HELPER(CollectionOperation::Maximum, max);
1091 VALUE_OF_TYPE_WITH_COLLECTION_OPERATOR_HELPER(CollectionOperation::Sum, sum);
1092 VALUE_OF_TYPE_WITH_COLLECTION_OPERATOR_HELPER(CollectionOperation::Average, average);
1093 #undef VALUE_OF_TYPE_WITH_COLLECTION_OPERATOR_HELPER
1095 template <typename Requested, CollectionOperation::Type OperationType, typename T>
1096 auto value_of_type_with_collection_operation(T&& value) {
1097 return value_of_type<Requested>(std::forward<T>(value));
1100 template <typename Requested, CollectionOperation::Type OperationType>
1101 auto value_of_type_with_collection_operation(CollectionOperation operation) {
1102 using helper = ValueOfTypeWithCollectionOperationHelper<Requested, OperationType>;
1103 return helper::convert(operation);
1106 template <CollectionOperation::Type Operation, typename... T>
1107 void QueryBuilder::add_collection_operation_constraint(RLMPropertyType propertyType, NSPredicateOperatorType operatorType, T... values)
1109 switch (propertyType) {
1110 case RLMPropertyTypeInt:
1111 add_numeric_constraint(propertyType, operatorType, value_of_type_with_collection_operation<Int, Operation>(values)...);
1113 case RLMPropertyTypeFloat:
1114 add_numeric_constraint(propertyType, operatorType, value_of_type_with_collection_operation<Float, Operation>(values)...);
1116 case RLMPropertyTypeDouble:
1117 add_numeric_constraint(propertyType, operatorType, value_of_type_with_collection_operation<Double, Operation>(values)...);
1120 REALM_ASSERT(false && "Only numeric property types should hit this path.");
1124 template <typename... T>
1125 void QueryBuilder::add_collection_operation_constraint(NSPredicateOperatorType operatorType,
1126 CollectionOperation collectionOperation, T... values)
1128 static_assert(sizeof...(T) == 2, "add_collection_operation_constraint accepts only two values as arguments");
1130 switch (collectionOperation.type()) {
1131 case CollectionOperation::Count:
1132 add_numeric_constraint(RLMPropertyTypeInt, operatorType,
1133 value_of_type_with_collection_operation<Int, CollectionOperation::Count>(values)...);
1135 case CollectionOperation::Minimum:
1136 add_collection_operation_constraint<CollectionOperation::Minimum>(collectionOperation.column().type(), operatorType, values...);
1138 case CollectionOperation::Maximum:
1139 add_collection_operation_constraint<CollectionOperation::Maximum>(collectionOperation.column().type(), operatorType, values...);
1141 case CollectionOperation::Sum:
1142 add_collection_operation_constraint<CollectionOperation::Sum>(collectionOperation.column().type(), operatorType, values...);
1144 case CollectionOperation::Average:
1145 add_collection_operation_constraint<CollectionOperation::Average>(collectionOperation.column().type(), operatorType, values...);
1150 bool key_path_contains_collection_operator(NSString *keyPath) {
1151 return [keyPath rangeOfString:@"@"].location != NSNotFound;
1154 NSString *get_collection_operation_name_from_key_path(NSString *keyPath, NSString **leadingKeyPath,
1155 NSString **trailingKey) {
1156 NSRange at = [keyPath rangeOfString:@"@"];
1157 if (at.location == NSNotFound || at.location >= keyPath.length - 1) {
1158 @throw RLMPredicateException(@"Invalid key path", @"'%@' is not a valid key path'", keyPath);
1161 if (at.location == 0 || [keyPath characterAtIndex:at.location - 1] != '.') {
1162 @throw RLMPredicateException(@"Invalid key path", @"'%@' is not a valid key path'", keyPath);
1165 NSRange trailingKeyRange = [keyPath rangeOfString:@"." options:0 range:{at.location, keyPath.length - at.location} locale:nil];
1167 *leadingKeyPath = [keyPath substringToIndex:at.location - 1];
1168 if (trailingKeyRange.location == NSNotFound) {
1170 return [keyPath substringFromIndex:at.location];
1172 *trailingKey = [keyPath substringFromIndex:trailingKeyRange.location + 1];
1173 return [keyPath substringWithRange:{at.location, trailingKeyRange.location - at.location}];
1177 CollectionOperation QueryBuilder::collection_operation_from_key_path(RLMObjectSchema *desc, NSString *keyPath) {
1178 NSString *leadingKeyPath;
1179 NSString *trailingKey;
1180 NSString *collectionOperationName = get_collection_operation_name_from_key_path(keyPath, &leadingKeyPath, &trailingKey);
1182 ColumnReference linkColumn = column_reference_from_key_path(desc, leadingKeyPath, true);
1183 util::Optional<ColumnReference> column;
1185 RLMPrecondition([trailingKey rangeOfString:@"."].location == NSNotFound, @"Invalid key path",
1186 @"Right side of collection operator may only have a single level key");
1187 NSString *fullKeyPath = [leadingKeyPath stringByAppendingFormat:@".%@", trailingKey];
1188 column = column_reference_from_key_path(desc, fullKeyPath, true);
1191 return {collectionOperationName, std::move(linkColumn), std::move(column)};
1194 void QueryBuilder::apply_collection_operator_expression(RLMObjectSchema *desc,
1195 NSString *keyPath, id value,
1196 NSComparisonPredicate *pred) {
1197 CollectionOperation operation = collection_operation_from_key_path(desc, keyPath);
1198 operation.validate_comparison(value);
1200 if (pred.leftExpression.expressionType == NSKeyPathExpressionType) {
1201 add_collection_operation_constraint(pred.predicateOperatorType, operation, operation, value);
1203 add_collection_operation_constraint(pred.predicateOperatorType, operation, value, operation);
1207 void QueryBuilder::apply_value_expression(RLMObjectSchema *desc,
1208 NSString *keyPath, id value,
1209 NSComparisonPredicate *pred)
1211 if (key_path_contains_collection_operator(keyPath)) {
1212 apply_collection_operator_expression(desc, keyPath, value, pred);
1216 bool isAny = pred.comparisonPredicateModifier == NSAnyPredicateModifier;
1217 ColumnReference column = column_reference_from_key_path(desc, keyPath, isAny);
1219 // check to see if this is a between query
1220 if (pred.predicateOperatorType == NSBetweenPredicateOperatorType) {
1221 add_between_constraint(std::move(column), value);
1225 // turn "key.path IN collection" into ored together ==. "collection IN key.path" is handled elsewhere.
1226 if (pred.predicateOperatorType == NSInPredicateOperatorType) {
1227 process_or_group(m_query, value, [&](id item) {
1228 id normalized = value_from_constant_expression_or_value(item);
1229 validate_property_value(column, normalized,
1230 @"Expected object of type %@ in IN clause for property '%@' on object of type '%@', but received: %@", desc, keyPath);
1231 add_constraint(column.type(), NSEqualToPredicateOperatorType, pred.options, column, normalized);
1236 validate_property_value(column, value, @"Expected object of type %@ for property '%@' on object of type '%@', but received: %@", desc, keyPath);
1237 if (pred.leftExpression.expressionType == NSKeyPathExpressionType) {
1238 add_constraint(column.type(), pred.predicateOperatorType, pred.options, std::move(column), value);
1240 add_constraint(column.type(), pred.predicateOperatorType, pred.options, value, std::move(column));
1244 void QueryBuilder::apply_column_expression(RLMObjectSchema *desc,
1245 NSString *leftKeyPath, NSString *rightKeyPath,
1246 NSComparisonPredicate *predicate)
1248 bool left_key_path_contains_collection_operator = key_path_contains_collection_operator(leftKeyPath);
1249 bool right_key_path_contains_collection_operator = key_path_contains_collection_operator(rightKeyPath);
1250 if (left_key_path_contains_collection_operator && right_key_path_contains_collection_operator) {
1251 @throw RLMPredicateException(@"Unsupported predicate", @"Key paths including aggregate operations cannot be compared with other aggregate operations.");
1254 if (left_key_path_contains_collection_operator) {
1255 CollectionOperation left = collection_operation_from_key_path(desc, leftKeyPath);
1256 ColumnReference right = column_reference_from_key_path(desc, rightKeyPath, false);
1257 left.validate_comparison(right);
1258 add_collection_operation_constraint(predicate.predicateOperatorType, left, left, std::move(right));
1261 if (right_key_path_contains_collection_operator) {
1262 ColumnReference left = column_reference_from_key_path(desc, leftKeyPath, false);
1263 CollectionOperation right = collection_operation_from_key_path(desc, rightKeyPath);
1264 right.validate_comparison(left);
1265 add_collection_operation_constraint(predicate.predicateOperatorType, right, std::move(left), right);
1270 ColumnReference left = column_reference_from_key_path(desc, leftKeyPath, isAny);
1271 ColumnReference right = column_reference_from_key_path(desc, rightKeyPath, isAny);
1273 // NOTE: It's assumed that column type must match and no automatic type conversion is supported.
1274 RLMPrecondition(left.type() == right.type(),
1275 RLMPropertiesComparisonTypeMismatchException,
1276 RLMPropertiesComparisonTypeMismatchReason,
1277 RLMTypeToString(left.type()),
1278 RLMTypeToString(right.type()));
1280 // TODO: Should we handle special case where left row is the same as right row (tautology)
1281 add_constraint(left.type(), predicate.predicateOperatorType, predicate.options,
1282 std::move(left), std::move(right));
1285 // Identify expressions of the form [SELF valueForKeyPath:]
1286 bool is_self_value_for_key_path_function_expression(NSExpression *expression)
1288 if (expression.expressionType != NSFunctionExpressionType)
1291 if (expression.operand.expressionType != NSEvaluatedObjectExpressionType)
1294 return [expression.function isEqualToString:@"valueForKeyPath:"];
1297 // -[NSPredicate predicateWithSubtitutionVariables:] results in function expressions of the form [SELF valueForKeyPath:]
1298 // that apply_predicate cannot handle. Replace such expressions with equivalent NSKeyPathExpressionType expressions.
1299 NSExpression *simplify_self_value_for_key_path_function_expression(NSExpression *expression) {
1300 if (is_self_value_for_key_path_function_expression(expression)) {
1301 if (NSString *keyPath = [expression.arguments.firstObject keyPath]) {
1302 return [NSExpression expressionForKeyPath:keyPath];
1308 void QueryBuilder::apply_subquery_count_expression(RLMObjectSchema *objectSchema,
1309 NSExpression *subqueryExpression, NSPredicateOperatorType operatorType, NSExpression *right) {
1310 if (right.expressionType != NSConstantValueExpressionType || ![right.constantValue isKindOfClass:[NSNumber class]]) {
1311 @throw RLMPredicateException(@"Invalid predicate expression", @"SUBQUERY(…).@count is only supported when compared with a constant number.");
1313 int64_t value = [right.constantValue integerValue];
1315 ColumnReference collectionColumn = column_reference_from_key_path(objectSchema, [subqueryExpression.collection keyPath], true);
1316 RLMObjectSchema *collectionMemberObjectSchema = m_schema[collectionColumn.property().objectClassName];
1318 // Eliminate references to the iteration variable in the subquery.
1319 NSPredicate *subqueryPredicate = [subqueryExpression.predicate predicateWithSubstitutionVariables:@{ subqueryExpression.variable : [NSExpression expressionForEvaluatedObject] }];
1320 subqueryPredicate = transformPredicate(subqueryPredicate, simplify_self_value_for_key_path_function_expression);
1322 Query subquery = RLMPredicateToQuery(subqueryPredicate, collectionMemberObjectSchema, m_schema, m_group);
1323 add_numeric_constraint(RLMPropertyTypeInt, operatorType,
1324 collectionColumn.resolve<LinkList>(std::move(subquery)).count(), value);
1327 void QueryBuilder::apply_function_subquery_expression(RLMObjectSchema *objectSchema, NSExpression *functionExpression,
1328 NSPredicateOperatorType operatorType, NSExpression *right) {
1329 if (![functionExpression.function isEqualToString:@"valueForKeyPath:"] || functionExpression.arguments.count != 1) {
1330 @throw RLMPredicateException(@"Invalid predicate", @"The '%@' function is not supported on the result of a SUBQUERY.", functionExpression.function);
1333 NSExpression *keyPathExpression = functionExpression.arguments.firstObject;
1334 if ([keyPathExpression.keyPath isEqualToString:@"@count"]) {
1335 apply_subquery_count_expression(objectSchema, functionExpression.operand, operatorType, right);
1337 @throw RLMPredicateException(@"Invalid predicate", @"SUBQUERY is only supported when immediately followed by .@count that is compared with a constant number.");
1341 void QueryBuilder::apply_function_expression(RLMObjectSchema *objectSchema, NSExpression *functionExpression,
1342 NSPredicateOperatorType operatorType, NSExpression *right) {
1343 if (functionExpression.operand.expressionType == NSSubqueryExpressionType) {
1344 apply_function_subquery_expression(objectSchema, functionExpression, operatorType, right);
1346 @throw RLMPredicateException(@"Invalid predicate", @"The '%@' function is not supported.", functionExpression.function);
1351 void QueryBuilder::apply_predicate(NSPredicate *predicate, RLMObjectSchema *objectSchema)
1353 // Compound predicates.
1354 if ([predicate isMemberOfClass:[NSCompoundPredicate class]]) {
1355 NSCompoundPredicate *comp = (NSCompoundPredicate *)predicate;
1357 switch ([comp compoundPredicateType]) {
1358 case NSAndPredicateType:
1359 if (comp.subpredicates.count) {
1360 // Add all of the subpredicates.
1362 for (NSPredicate *subp in comp.subpredicates) {
1363 apply_predicate(subp, objectSchema);
1365 m_query.end_group();
1367 // NSCompoundPredicate's documentation states that an AND predicate with no subpredicates evaluates to TRUE.
1368 m_query.and_query(std::unique_ptr<Expression>(new TrueExpression));
1372 case NSOrPredicateType: {
1373 // Add all of the subpredicates with ors inbetween.
1374 process_or_group(m_query, comp.subpredicates, [&](__unsafe_unretained NSPredicate *const subp) {
1375 apply_predicate(subp, objectSchema);
1380 case NSNotPredicateType:
1381 // Add the negated subpredicate
1383 apply_predicate(comp.subpredicates.firstObject, objectSchema);
1387 @throw RLMPredicateException(@"Invalid compound predicate type",
1388 @"Only support AND, OR and NOT predicate types");
1391 else if ([predicate isMemberOfClass:[NSComparisonPredicate class]]) {
1392 NSComparisonPredicate *compp = (NSComparisonPredicate *)predicate;
1395 RLMPrecondition(compp.comparisonPredicateModifier != NSAllPredicateModifier,
1396 @"Invalid predicate", @"ALL modifier not supported");
1398 NSExpressionType exp1Type = compp.leftExpression.expressionType;
1399 NSExpressionType exp2Type = compp.rightExpression.expressionType;
1401 if (compp.comparisonPredicateModifier == NSAnyPredicateModifier) {
1403 RLMPrecondition(exp1Type == NSKeyPathExpressionType && exp2Type == NSConstantValueExpressionType,
1404 @"Invalid predicate",
1405 @"Predicate with ANY modifier must compare a KeyPath with RLMArray with a value");
1408 if (compp.predicateOperatorType == NSBetweenPredicateOperatorType || compp.predicateOperatorType == NSInPredicateOperatorType) {
1409 // Inserting an array via %@ gives NSConstantValueExpressionType, but including it directly gives NSAggregateExpressionType
1410 if (exp1Type == NSKeyPathExpressionType && (exp2Type == NSAggregateExpressionType || exp2Type == NSConstantValueExpressionType)) {
1411 // "key.path IN %@", "key.path IN {…}", "key.path BETWEEN %@", or "key.path BETWEEN {…}".
1412 exp2Type = NSConstantValueExpressionType;
1414 else if (compp.predicateOperatorType == NSInPredicateOperatorType && exp1Type == NSConstantValueExpressionType && exp2Type == NSKeyPathExpressionType) {
1415 // "%@ IN key.path" is equivalent to "ANY key.path IN %@". Rewrite the former into the latter.
1416 compp = [NSComparisonPredicate predicateWithLeftExpression:compp.rightExpression rightExpression:compp.leftExpression
1417 modifier:NSAnyPredicateModifier type:NSEqualToPredicateOperatorType options:0];
1418 exp1Type = NSKeyPathExpressionType;
1419 exp2Type = NSConstantValueExpressionType;
1422 if (compp.predicateOperatorType == NSBetweenPredicateOperatorType) {
1423 @throw RLMPredicateException(@"Invalid predicate",
1424 @"Predicate with BETWEEN operator must compare a KeyPath with an aggregate with two values");
1426 else if (compp.predicateOperatorType == NSInPredicateOperatorType) {
1427 @throw RLMPredicateException(@"Invalid predicate",
1428 @"Predicate with IN operator must compare a KeyPath with an aggregate");
1433 if (exp1Type == NSKeyPathExpressionType && exp2Type == NSKeyPathExpressionType) {
1434 // both expression are KeyPaths
1435 apply_column_expression(objectSchema, compp.leftExpression.keyPath, compp.rightExpression.keyPath, compp);
1437 else if (exp1Type == NSKeyPathExpressionType && exp2Type == NSConstantValueExpressionType) {
1438 // comparing keypath to value
1439 apply_value_expression(objectSchema, compp.leftExpression.keyPath, compp.rightExpression.constantValue, compp);
1441 else if (exp1Type == NSConstantValueExpressionType && exp2Type == NSKeyPathExpressionType) {
1442 // comparing value to keypath
1443 apply_value_expression(objectSchema, compp.rightExpression.keyPath, compp.leftExpression.constantValue, compp);
1445 else if (exp1Type == NSFunctionExpressionType) {
1446 apply_function_expression(objectSchema, compp.leftExpression, compp.predicateOperatorType, compp.rightExpression);
1448 else if (exp1Type == NSSubqueryExpressionType) {
1449 // The subquery expressions that we support are handled by the NSFunctionExpressionType case above.
1450 @throw RLMPredicateException(@"Invalid predicate expression", @"SUBQUERY is only supported when immediately followed by .@count.");
1453 @throw RLMPredicateException(@"Invalid predicate expressions",
1454 @"Predicate expressions must compare a keypath and another keypath or a constant value");
1457 else if ([predicate isEqual:[NSPredicate predicateWithValue:YES]]) {
1458 m_query.and_query(std::unique_ptr<Expression>(new TrueExpression));
1459 } else if ([predicate isEqual:[NSPredicate predicateWithValue:NO]]) {
1460 m_query.and_query(std::unique_ptr<Expression>(new FalseExpression));
1463 // invalid predicate type
1464 @throw RLMPredicateException(@"Invalid predicate",
1465 @"Only support compound, comparison, and constant predicates");
1470 realm::Query RLMPredicateToQuery(NSPredicate *predicate, RLMObjectSchema *objectSchema,
1471 RLMSchema *schema, Group &group)
1473 auto query = get_table(group, objectSchema).where();
1475 // passing a nil predicate is a no-op
1481 QueryBuilder(query, group, schema).apply_predicate(predicate, objectSchema);
1484 // Test the constructed query in core
1485 std::string validateMessage = query.validate();
1486 RLMPrecondition(validateMessage.empty(), @"Invalid query", @"%.*s",
1487 (int)validateMessage.size(), validateMessage.c_str());