summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm212
-rw-r--r--platform/darwin/src/NSCompoundPredicate+MGLAdditions.mm53
-rw-r--r--platform/darwin/src/NSExpression+MGLAdditions.mm25
-rw-r--r--platform/darwin/src/NSPredicate+MGLAdditions.mm75
-rw-r--r--platform/darwin/test/MGLFilterTests.mm194
-rw-r--r--platform/darwin/test/MGLPredicateTests.mm416
-rw-r--r--platform/ios/ios.xcodeproj/project.pbxproj15
-rw-r--r--platform/macos/macos.xcodeproj/project.pbxproj12
8 files changed, 696 insertions, 306 deletions
diff --git a/platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm b/platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm
index 19c264aa40..58390b0b81 100644
--- a/platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm
+++ b/platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm
@@ -7,95 +7,189 @@
- (mbgl::style::Filter)mgl_filter
{
+ NSExpression *leftExpression = self.leftExpression;
+ NSExpression *rightExpression = self.rightExpression;
+ NSExpressionType leftType = leftExpression.expressionType;
+ NSExpressionType rightType = rightExpression.expressionType;
+ BOOL isReversed = ((leftType == NSConstantValueExpressionType || leftType == NSAggregateExpressionType)
+ && rightType == NSKeyPathExpressionType);
switch (self.predicateOperatorType) {
case NSEqualToPredicateOperatorType: {
- if (self.rightExpression.constantValue)
- {
- auto filter = mbgl::style::EqualsFilter();
- filter.key = self.leftExpression.keyPath.UTF8String;
- filter.value = self.rightExpression.mgl_filterValue;
- return filter;
- }
- else
- {
- auto filter = mbgl::style::NotHasFilter();
- filter.key = self.leftExpression.keyPath.UTF8String;
- return filter;
+ mbgl::style::EqualsFilter eqFilter;
+ eqFilter.key = self.mgl_keyPath.UTF8String;
+ eqFilter.value = self.mgl_constantValue;
+
+ // Convert == nil to NotHasFilter.
+ if (eqFilter.value.is<mbgl::NullValue>()) {
+ mbgl::style::NotHasFilter notHasFilter;
+ notHasFilter.key = eqFilter.key;
+ return notHasFilter;
}
+
+ return eqFilter;
}
case NSNotEqualToPredicateOperatorType: {
- if (self.rightExpression.constantValue)
- {
- auto filter = mbgl::style::NotEqualsFilter();
- filter.key = self.leftExpression.keyPath.UTF8String;
- filter.value = self.rightExpression.mgl_filterValue;
- return filter;
- }
- else
- {
- auto filter = mbgl::style::HasFilter();
- filter.key = self.leftExpression.keyPath.UTF8String;
- return filter;
+ mbgl::style::NotEqualsFilter neFilter;
+ neFilter.key = self.mgl_keyPath.UTF8String;
+ neFilter.value = self.mgl_constantValue;
+
+ // Convert != nil to HasFilter.
+ if (neFilter.value.is<mbgl::NullValue>()) {
+ mbgl::style::HasFilter hasFilter;
+ hasFilter.key = neFilter.key;
+ return hasFilter;
}
+
+ return neFilter;
}
case NSGreaterThanPredicateOperatorType: {
- auto filter = mbgl::style::GreaterThanFilter();
- filter.key = self.leftExpression.keyPath.UTF8String;
- filter.value = self.rightExpression.mgl_filterValue;
- return filter;
+ if (isReversed) {
+ mbgl::style::LessThanFilter ltFilter;
+ ltFilter.key = self.mgl_keyPath.UTF8String;
+ ltFilter.value = self.mgl_constantValue;
+ return ltFilter;
+ } else {
+ mbgl::style::GreaterThanFilter gtFilter;
+ gtFilter.key = self.mgl_keyPath.UTF8String;
+ gtFilter.value = self.mgl_constantValue;
+ return gtFilter;
+ }
}
case NSGreaterThanOrEqualToPredicateOperatorType: {
- auto filter = mbgl::style::GreaterThanEqualsFilter();
- filter.key = self.leftExpression.keyPath.UTF8String;
- filter.value = self.rightExpression.mgl_filterValue;
- return filter;
+ if (isReversed) {
+ mbgl::style::LessThanEqualsFilter lteFilter;
+ lteFilter.key = self.mgl_keyPath.UTF8String;
+ lteFilter.value = self.mgl_constantValue;
+ return lteFilter;
+ } else {
+ mbgl::style::GreaterThanEqualsFilter gteFilter;
+ gteFilter.key = self.mgl_keyPath.UTF8String;
+ gteFilter.value = self.mgl_constantValue;
+ return gteFilter;
+ }
}
case NSLessThanPredicateOperatorType: {
- auto filter = mbgl::style::LessThanFilter();
- filter.key = self.leftExpression.keyPath.UTF8String;
- filter.value = self.rightExpression.mgl_filterValue;
- return filter;
+ if (isReversed) {
+ mbgl::style::GreaterThanFilter gtFilter;
+ gtFilter.key = self.mgl_keyPath.UTF8String;
+ gtFilter.value = self.mgl_constantValue;
+ return gtFilter;
+ } else {
+ mbgl::style::LessThanFilter ltFilter;
+ ltFilter.key = self.mgl_keyPath.UTF8String;
+ ltFilter.value = self.mgl_constantValue;
+ return ltFilter;
+ }
}
case NSLessThanOrEqualToPredicateOperatorType: {
- auto filter = mbgl::style::LessThanEqualsFilter();
- filter.key = self.leftExpression.keyPath.UTF8String;
- filter.value = self.rightExpression.mgl_filterValue;
- return filter;
+ if (isReversed) {
+ mbgl::style::GreaterThanEqualsFilter gteFilter;
+ gteFilter.key = self.mgl_keyPath.UTF8String;
+ gteFilter.value = self.mgl_constantValue;
+ return gteFilter;
+ } else {
+ mbgl::style::LessThanEqualsFilter lteFilter;
+ lteFilter.key = self.mgl_keyPath.UTF8String;
+ lteFilter.value = self.mgl_constantValue;
+ return lteFilter;
+ }
}
case NSInPredicateOperatorType: {
- auto filter = mbgl::style::InFilter();
- filter.key = self.leftExpression.keyPath.UTF8String;
- filter.values = self.rightExpression.mgl_filterValues;
- return filter;
+ if (isReversed) {
+ if (leftType == NSConstantValueExpressionType && [leftExpression.constantValue isKindOfClass:[NSString class]]) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"CONTAINS not supported for string comparison."];
+ }
+ [NSException raise:NSInvalidArgumentException
+ format:@"Predicate cannot compare values IN attribute."];
+ }
+ mbgl::style::InFilter inFilter;
+ inFilter.key = leftExpression.keyPath.UTF8String;
+ inFilter.values = rightExpression.mgl_filterValues;
+ return inFilter;
}
case NSContainsPredicateOperatorType: {
- auto filter = mbgl::style::InFilter();
- filter.key = [self.rightExpression.constantValue UTF8String];
- filter.values = self.leftExpression.mgl_filterValues;
- return filter;
+ if (!isReversed) {
+ if (rightType == NSConstantValueExpressionType && [rightExpression.constantValue isKindOfClass:[NSString class]]) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"IN not supported for string comparison."];
+ }
+ [NSException raise:NSInvalidArgumentException
+ format:@"Predicate cannot compare attribute CONTAINS values."];
+ }
+ mbgl::style::InFilter inFilter;
+ inFilter.key = rightExpression.keyPath.UTF8String;
+ inFilter.values = leftExpression.mgl_filterValues;
+ return inFilter;
}
case NSBetweenPredicateOperatorType: {
- auto filter = mbgl::style::AllFilter();
- auto gteFilter = mbgl::style::GreaterThanEqualsFilter();
- gteFilter.key = self.leftExpression.keyPath.UTF8String;
- gteFilter.value = self.rightExpression.mgl_filterValues[0];
- filter.filters.push_back(gteFilter);
- auto lteFilter = mbgl::style::LessThanEqualsFilter();
- lteFilter.key = self.leftExpression.keyPath.UTF8String;
- lteFilter.value = self.rightExpression.mgl_filterValues[1];
- filter.filters.push_back(lteFilter);
- return filter;
+ if (isReversed) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"Predicate cannot compare bounds BETWEEN attribute."];
+ }
+ if (![rightExpression.constantValue isKindOfClass:[NSArray class]]) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"Right side of BETWEEN predicate must be an array."]; // not NSSet
+ }
+ auto values = rightExpression.mgl_filterValues;
+ if (values.size() != 2) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"Right side of BETWEEN predicate must have two items."];
+ }
+ mbgl::style::AllFilter allFilter;
+ mbgl::style::GreaterThanEqualsFilter gteFilter;
+ gteFilter.key = leftExpression.keyPath.UTF8String;
+ gteFilter.value = values[0];
+ allFilter.filters.push_back(gteFilter);
+ mbgl::style::LessThanEqualsFilter lteFilter;
+ lteFilter.key = leftExpression.keyPath.UTF8String;
+ lteFilter.value = values[1];
+ allFilter.filters.push_back(lteFilter);
+ return allFilter;
}
case NSMatchesPredicateOperatorType:
case NSLikePredicateOperatorType:
case NSBeginsWithPredicateOperatorType:
case NSEndsWithPredicateOperatorType:
case NSCustomSelectorPredicateOperatorType:
- [NSException raise:@"Unsupported operator type"
+ [NSException raise:NSInvalidArgumentException
format:@"NSPredicateOperatorType:%lu is not supported.", (unsigned long)self.predicateOperatorType];
}
return {};
}
+- (NSString *)mgl_keyPath {
+ NSExpression *leftExpression = self.leftExpression;
+ NSExpression *rightExpression = self.rightExpression;
+ NSExpressionType leftType = leftExpression.expressionType;
+ NSExpressionType rightType = rightExpression.expressionType;
+ if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) {
+ return leftExpression.keyPath;
+ } else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) {
+ return rightExpression.keyPath;
+ }
+
+ [NSException raise:NSInvalidArgumentException
+ format:@"Comparison predicate must compare an attribute (as a key path) to a constant or vice versa."];
+ return nil;
+}
+
+- (mbgl::Value)mgl_constantValue {
+ NSExpression *leftExpression = self.leftExpression;
+ NSExpression *rightExpression = self.rightExpression;
+ NSExpressionType leftType = leftExpression.expressionType;
+ NSExpressionType rightType = rightExpression.expressionType;
+ mbgl::Value value;
+ if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) {
+ value = rightExpression.mgl_filterValue;
+ } else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) {
+ value = leftExpression.mgl_filterValue;
+ } else {
+ [NSException raise:NSInvalidArgumentException
+ format:@"Comparison predicate must compare an attribute (as a key path) to a constant or vice versa."];
+ }
+ return value;
+}
+
@end
diff --git a/platform/darwin/src/NSCompoundPredicate+MGLAdditions.mm b/platform/darwin/src/NSCompoundPredicate+MGLAdditions.mm
index 07114308c9..2697467198 100644
--- a/platform/darwin/src/NSCompoundPredicate+MGLAdditions.mm
+++ b/platform/darwin/src/NSCompoundPredicate+MGLAdditions.mm
@@ -18,32 +18,49 @@
{
switch (self.compoundPredicateType) {
case NSNotPredicateType: {
+ NSAssert(self.subpredicates.count <= 1, @"NOT predicate cannot have multiple subpredicates.");
+ NSPredicate *subpredicate = self.subpredicates.firstObject;
+ mbgl::style::Filter subfilter = subpredicate.mgl_filter;
- // Translate a nested NSComparisonPredicate with operator type NSInPredicateOperatorType into a flat mbgl::NotIn filter.
- NSArray<NSComparisonPredicate *> *comparisonPredicates = [self.subpredicates filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"class == %@", [NSComparisonPredicate class]]];
- NSArray<NSComparisonPredicate *> *notInPredicates = [comparisonPredicates filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSComparisonPredicate *_Nonnull predicate, NSDictionary<NSString *,id> * _Nullable bindings) {
- return predicate.predicateOperatorType == NSInPredicateOperatorType;
- }]];
+ // Convert NOT(!= nil) to NotHasFilter.
+ if (subfilter.is<mbgl::style::HasFilter>()) {
+ auto hasFilter = subfilter.get<mbgl::style::HasFilter>();
+ return mbgl::style::NotHasFilter { .key = hasFilter.key };
+ }
+
+ // Convert NOT(== nil) to HasFilter.
+ if (subfilter.is<mbgl::style::NotHasFilter>()) {
+ auto hasFilter = subfilter.get<mbgl::style::NotHasFilter>();
+ return mbgl::style::HasFilter { .key = hasFilter.key };
+ }
+
+ // Convert NOT(IN) or NOT(CONTAINS) to NotInFilter.
+ if (subfilter.is<mbgl::style::InFilter>()) {
+ auto inFilter = subfilter.get<mbgl::style::InFilter>();
+ mbgl::style::NotInFilter notInFilter;
+ notInFilter.key = inFilter.key;
+ notInFilter.values = inFilter.values;
+ return notInFilter;
+ }
- if (notInPredicates.count) {
- auto filter = mbgl::style::NotInFilter();
- filter.key = notInPredicates.firstObject.leftExpression.keyPath.UTF8String;
- filter.values = notInPredicates.firstObject.rightExpression.mgl_filterValues;
- return filter;
- } else {
- auto filter = mbgl::style::NoneFilter();
- filter.filters = [self mgl_subfilters];
- return filter;
+ // Convert NOT(), NOT(AND), NOT(NOT), NOT(==), etc. into NoneFilter.
+ mbgl::style::NoneFilter noneFilter;
+ if (subfilter.is<mbgl::style::AnyFilter>()) {
+ // Flatten NOT(OR).
+ noneFilter.filters = subfilter.get<mbgl::style::AnyFilter>().filters;
+ } else if (subpredicate) {
+ noneFilter.filters = { subfilter };
}
+ return noneFilter;
}
case NSAndPredicateType: {
- auto filter = mbgl::style::AllFilter();
- filter.filters = [self mgl_subfilters];
+ mbgl::style::AllFilter filter;
+ filter.filters = self.mgl_subfilters;
return filter;
}
case NSOrPredicateType: {
- auto filter = mbgl::style::AnyFilter();
- filter.filters = [self mgl_subfilters];
+ mbgl::style::AnyFilter filter;
+ filter.filters = self.mgl_subfilters;
return filter;
}
}
diff --git a/platform/darwin/src/NSExpression+MGLAdditions.mm b/platform/darwin/src/NSExpression+MGLAdditions.mm
index 6af069487e..97f3e11dba 100644
--- a/platform/darwin/src/NSExpression+MGLAdditions.mm
+++ b/platform/darwin/src/NSExpression+MGLAdditions.mm
@@ -4,15 +4,19 @@
- (std::vector<mbgl::Value>)mgl_filterValues
{
- if ([self.constantValue isKindOfClass:NSArray.class]) {
- NSArray *values = self.constantValue;
- std::vector<mbgl::Value>convertedValues;
- for (id value in values) {
- convertedValues.push_back([self mgl_convertedValueWithValue:value]);
+ if ([self.constantValue isKindOfClass:[NSArray class]] || [self.constantValue isKindOfClass:[NSSet class]]) {
+ std::vector<mbgl::Value> convertedValues;
+ for (id item in self.constantValue) {
+ id constantValue = item;
+ if ([item isKindOfClass:[NSExpression class]]) {
+ constantValue = [constantValue constantValue];
+ }
+ convertedValues.push_back([self mgl_convertedValueWithValue:constantValue]);
}
return convertedValues;
}
- [NSException raise:@"Values not handled" format:@""];
+ [NSException raise:NSInvalidArgumentException
+ format:@"Constant value expression must contain an array or set."];
return { };
}
@@ -42,7 +46,10 @@
// We still do this conversion in order to provide a valid value.
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
- NSLog(@"One-time warning: Float values are converted to double and can introduce imprecision; please use double values explicitly in predicate arguments.");
+ NSLog(@"Float value in expression will be converted to a double; some imprecision may result. "
+ @"Use double values explicitly when specifying constant expression values and "
+ @"when specifying arguments to predicate and expression format strings. "
+ @"This will be logged only once.");
});
return { (double)number.doubleValue };
} else if ([number compare:@(0)] == NSOrderedDescending ||
@@ -55,8 +62,8 @@
// We use long long here to avoid any truncation.
return { (int64_t)number.longLongValue };
}
- } else if (value != [NSNull null]) {
- [NSException raise:@"Value not handled"
+ } else if (value && value != [NSNull null]) {
+ [NSException raise:NSInvalidArgumentException
format:@"Can’t convert %s:%@ to mbgl::Value", [value objCType], value];
}
return { };
diff --git a/platform/darwin/src/NSPredicate+MGLAdditions.mm b/platform/darwin/src/NSPredicate+MGLAdditions.mm
index 64ad277e4d..f999df96b0 100644
--- a/platform/darwin/src/NSPredicate+MGLAdditions.mm
+++ b/platform/darwin/src/NSPredicate+MGLAdditions.mm
@@ -16,7 +16,8 @@ public:
NSArray* getValues(std::vector<mbgl::Value> values) {
NSMutableArray *array = [NSMutableArray arrayWithCapacity:values.size()];
for (auto value : values) {
- [array addObject:mbgl::Value::visit(value, ValueEvaluator())];
+ id constantValue = mbgl::Value::visit(value, ValueEvaluator());
+ [array addObject:[NSExpression expressionForConstantValue:constantValue]];
}
return array;
}
@@ -58,20 +59,64 @@ public:
}
NSPredicate* operator()(mbgl::style::AnyFilter filter) {
- return [NSCompoundPredicate orPredicateWithSubpredicates:getPredicates(filter.filters)];
+ NSArray *subpredicates = getPredicates(filter.filters);
+ if (subpredicates.count) {
+ return [NSCompoundPredicate orPredicateWithSubpredicates:subpredicates];
+ }
+ return [NSPredicate predicateWithValue:NO];
}
NSPredicate* operator()(mbgl::style::AllFilter filter) {
- return [NSCompoundPredicate andPredicateWithSubpredicates:getPredicates(filter.filters)];
+ // Convert [all, [>=, key, lower], [<=, key, upper]] to key BETWEEN {lower, upper}
+ if (filter.filters.size() == 2) {
+ auto leftFilter = filter.filters[0];
+ auto rightFilter = filter.filters[1];
+
+ std::string lowerKey;
+ std::string upperKey;
+ mbgl::Value lowerBound;
+ mbgl::Value upperBound;
+ if (leftFilter.is<mbgl::style::GreaterThanEqualsFilter>()) {
+ lowerKey = leftFilter.get<mbgl::style::GreaterThanEqualsFilter>().key;
+ lowerBound = leftFilter.get<mbgl::style::GreaterThanEqualsFilter>().value;
+ } else if (rightFilter.is<mbgl::style::GreaterThanEqualsFilter>()) {
+ lowerKey = rightFilter.get<mbgl::style::GreaterThanEqualsFilter>().key;
+ lowerBound = rightFilter.get<mbgl::style::GreaterThanEqualsFilter>().value;
+ }
+
+ if (leftFilter.is<mbgl::style::LessThanEqualsFilter>()) {
+ upperKey = leftFilter.get<mbgl::style::LessThanEqualsFilter>().key;
+ upperBound = leftFilter.get<mbgl::style::LessThanEqualsFilter>().value;
+ } else if (rightFilter.is<mbgl::style::LessThanEqualsFilter>()) {
+ upperKey = rightFilter.get<mbgl::style::LessThanEqualsFilter>().key;
+ upperBound = rightFilter.get<mbgl::style::LessThanEqualsFilter>().value;
+ }
+
+ if (!lowerBound.is<mbgl::NullValue>() && !upperBound.is<mbgl::NullValue>()
+ && lowerKey == upperKey) {
+ return [NSPredicate predicateWithFormat:@"%K BETWEEN {%@, %@}",
+ @(lowerKey.c_str()),
+ mbgl::Value::visit(lowerBound, ValueEvaluator()),
+ mbgl::Value::visit(upperBound, ValueEvaluator())];
+ }
+ }
+
+ NSArray *subpredicates = getPredicates(filter.filters);
+ if (subpredicates.count) {
+ return [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates];
+ }
+ return [NSPredicate predicateWithValue:YES];
}
NSPredicate* operator()(mbgl::style::NoneFilter filter) {
- NSArray *predicates = getPredicates(filter.filters);
- if (predicates.count > 1) {
- NSCompoundPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:predicates];
+ NSArray *subpredicates = getPredicates(filter.filters);
+ if (subpredicates.count > 1) {
+ NSCompoundPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:subpredicates];
return [NSCompoundPredicate notPredicateWithSubpredicate:predicate];
+ } else if (subpredicates.count) {
+ return [NSCompoundPredicate notPredicateWithSubpredicate:subpredicates.firstObject];
} else {
- return [NSCompoundPredicate notPredicateWithSubpredicate:predicates.firstObject];
+ return [NSPredicate predicateWithValue:YES];
}
}
@@ -91,18 +136,22 @@ public:
{
if ([self isEqual:[NSPredicate predicateWithValue:YES]])
{
- auto filter = mbgl::style::AllFilter();
- return filter;
+ return mbgl::style::AllFilter();
}
if ([self isEqual:[NSPredicate predicateWithValue:NO]])
{
- auto filter = mbgl::style::AnyFilter();
- return filter;
+ return mbgl::style::AnyFilter();
+ }
+
+ if ([self.predicateFormat hasPrefix:@"BLOCKPREDICATE("])
+ {
+ [NSException raise:NSInvalidArgumentException
+ format:@"Block-based predicates are not supported."];
}
- [NSException raise:@"Not supported"
- format:@"Try with NSComparisonPredicate or NSCompoundPredicate instead."];
+ [NSException raise:NSInvalidArgumentException
+ format:@"Unrecognized predicate type."];
return {};
}
diff --git a/platform/darwin/test/MGLFilterTests.mm b/platform/darwin/test/MGLFilterTests.mm
deleted file mode 100644
index e688d50583..0000000000
--- a/platform/darwin/test/MGLFilterTests.mm
+++ /dev/null
@@ -1,194 +0,0 @@
-#import "MGLStyleLayerTests.h"
-
-#import "NSPredicate+MGLAdditions.h"
-#import "MGLValueEvaluator.h"
-
-
-@interface MGLFilterTests : MGLStyleLayerTests {
- MGLShapeSource *source;
- MGLLineStyleLayer *layer;
-}
-@end
-
-@implementation MGLFilterTests
-
-- (void)setUp
-{
- [super setUp];
- NSString *filePath = [[NSBundle bundleForClass:self.class] pathForResource:@"amsterdam" ofType:@"geojson"];
- NSURL *url = [NSURL fileURLWithPath:filePath];
- NSData *geoJSONData = [NSData dataWithContentsOfURL:url];
- NSError *error;
- MGLShape *shape = [MGLShape shapeWithData:geoJSONData encoding:NSUTF8StringEncoding error:&error];
- XCTAssertNil(error);
- XCTAssertNotNil(shape);
- source = [[MGLShapeSource alloc] initWithIdentifier:@"test-source" shape:shape options:nil];
- [self.mapView.style addSource:source];
- layer = [[MGLLineStyleLayer alloc] initWithIdentifier:@"test-layer" source:source];
-}
-
-- (void)tearDown
-{
- [self.mapView.style removeLayer:layer];
- [self.mapView.style removeSource:source];
-}
-
-- (NSArray<NSPredicate *> *)predicates
-{
- NSPredicate *equalPredicate = [NSPredicate predicateWithFormat:@"type == 'neighbourhood'"];
- NSPredicate *notEqualPredicate = [NSPredicate predicateWithFormat:@"type != 'park'"];
- NSPredicate *greaterThanPredicate = [NSPredicate predicateWithFormat:@"%K > %@", @"stroke-width", @2.1];
- NSPredicate *greaterThanOrEqualToPredicate = [NSPredicate predicateWithFormat:@"%K >= %@", @"stroke-width", @2.1];
- NSPredicate *lessThanOrEqualToPredicate = [NSPredicate predicateWithFormat:@"%K <= %@", @"stroke-width", @2.1];
- NSPredicate *lessThanPredicate = [NSPredicate predicateWithFormat:@"%K < %@", @"stroke-width", @2.1];
- NSPredicate *inPredicate = [NSPredicate predicateWithFormat:@"type IN %@", @[@"park", @"neighbourhood"]];
- NSPredicate *notInPredicate = [NSPredicate predicateWithFormat:@"NOT (type IN %@)", @[@"park", @"neighbourhood"]];
- NSPredicate *inNotInPredicate = [NSPredicate predicateWithFormat:@"type IN %@ AND NOT (type IN %@)", @[@"park"], @[@"neighbourhood", @"test"]];
- NSPredicate *typePredicate = [NSPredicate predicateWithFormat:@"%K == %@", @"$type", @"Feature"];
- NSPredicate *idPredicate = [NSPredicate predicateWithFormat:@"%K == %@", @"$id", @"1234123"];
- NSPredicate *specialCharsPredicate = [NSPredicate predicateWithFormat:@"%K == %@", @"ty-’pè", @"sŒm-ethįng"];
- NSPredicate *booleanPredicate = [NSPredicate predicateWithFormat:@"cluster != YES"];
- NSPredicate *nilEqualsPredicate = [NSPredicate predicateWithFormat:@"type == %@", nil];
- NSPredicate *nilNotEqualsPredicate = [NSPredicate predicateWithFormat:@"type != %@", nil];
- return @[
- equalPredicate,
- notEqualPredicate,
- greaterThanPredicate,
- greaterThanOrEqualToPredicate,
- lessThanOrEqualToPredicate,
- lessThanPredicate,
- inPredicate,
- notInPredicate,
- inNotInPredicate,
- typePredicate,
- idPredicate,
- specialCharsPredicate,
- booleanPredicate,
- nilEqualsPredicate,
- nilNotEqualsPredicate
- ];
-}
-
-- (void)testAllPredicates
-{
- for (NSPredicate *predicate in self.predicates) {
- layer.predicate = predicate;
- XCTAssertEqualObjects(layer.predicate, predicate);
- }
- [self.mapView.style addLayer:layer];
-}
-
-- (void)testContainsPredicate
-{
- // core does not have a "contains" filter but we can achieve the equivalent by creating an `mbgl::style::InFilter`
- // and searching the value for the key
- NSPredicate *expectedPredicate = [NSPredicate predicateWithFormat:@"park IN %@", @[@"park", @"neighbourhood"]];
- NSPredicate *containsPredicate = [NSPredicate predicateWithFormat:@"%@ CONTAINS %@", @[@"park", @"neighbourhood"], @"park"];
-
- layer.predicate = containsPredicate;
- XCTAssertEqualObjects(layer.predicate, expectedPredicate);
- [self.mapView.style addLayer:layer];
-}
-
-- (void)testBetweenPredicate
-{
- // core does not have a "between" filter but we can achieve the equivalent by creating a set of greater than or equal / less than or equal
- // filters for the lower and upper bounds (inclusive)
- NSPredicate *expectedPredicate = [NSCompoundPredicate predicateWithFormat:@"%K >= 2 AND %K <= 3", @"stroke-width", @"stroke-width"];
- NSPredicate *betweenPredicate = [NSPredicate predicateWithFormat:@"%K BETWEEN %@", @"stroke-width", @[@2.0, @3.0]];
-
- layer.predicate = betweenPredicate;
- XCTAssertEqualObjects(layer.predicate, expectedPredicate);
- [self.mapView.style addLayer:layer];
-}
-
-- (void)testTruePredicate
-{
- // This comes out of the class cluster as an NSTruePredicate and it is equal to `[NSPredicate predicateWithValue:YES]`
- NSPredicate *truePredicate = [NSPredicate predicateWithFormat:@"TRUEPREDICATE"];
-
- layer.predicate = truePredicate;
- XCTAssertEqualObjects(layer.predicate.description, truePredicate.description);
- [self.mapView.style addLayer:layer];
-}
-
-- (void)testFalsePredicate
-{
- // This comes out of the class cluster as an NSFalsePredicate and it is equal to `[NSPredicate predicateWithValue:NO]`
- NSPredicate *falsePredicate = [NSPredicate predicateWithFormat:@"FALSEPREDICATE"];
-
- layer.predicate = falsePredicate;
- XCTAssertEqualObjects(layer.predicate.description, falsePredicate.description);
- [self.mapView.style addLayer:layer];
-}
-
-- (void)testIntermittentEncoding
-{
- NSPredicate *specialCharsPredicate = [NSPredicate predicateWithFormat:@"%K == %@", @"ty-’pè", @"sŒm-ethįng"];
- layer.predicate = specialCharsPredicate;
-
- NSComparisonPredicate *getPredicate = (NSComparisonPredicate *)layer.predicate;
- mbgl::style::EqualsFilter filter = layer.predicate.mgl_filter.get<mbgl::style::EqualsFilter>();
-
- id objcKey = getPredicate.leftExpression.keyPath;
- id cppKey = @(filter.key.c_str());
- id objcValue = mbgl::Value::visit(getPredicate.rightExpression.mgl_filterValue, ValueEvaluator());
- id cppValue = mbgl::Value::visit(filter.value, ValueEvaluator());
-
- XCTAssertEqualObjects(objcKey, cppKey);
- XCTAssertEqualObjects(objcValue, cppValue);
-
- [self.mapView.style addLayer:layer];
-}
-
-- (void)testNestedFilters
-{
- NSPredicate *equalPredicate = [NSPredicate predicateWithFormat:@"type == 'neighbourhood'"];
- NSPredicate *notEqualPredicate = [NSPredicate predicateWithFormat:@"type != 'park'"];
-
- NSPredicate *allPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:@[equalPredicate, notEqualPredicate]];
- NSPredicate *anyPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:@[equalPredicate, notEqualPredicate]];
-
- layer.predicate = allPredicate;
- XCTAssertEqualObjects(layer.predicate, allPredicate);
- layer.predicate = anyPredicate;
- XCTAssertEqualObjects(layer.predicate, anyPredicate);
-
- [self.mapView.style addLayer:layer];
-}
-
-- (void)testAndPredicates
-{
- NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:self.predicates];
- layer.predicate = predicate;
- XCTAssertEqualObjects(predicate, layer.predicate);
- [self.mapView.style addLayer:layer];
-}
-
-- (void)testOrPredicates
-{
- NSPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:self.predicates];
- layer.predicate = predicate;
- XCTAssertEqualObjects(predicate, layer.predicate);
- [self.mapView.style addLayer:layer];
-}
-
-- (void)testNotAndPredicates
-{
- NSPredicate *predicates = [NSCompoundPredicate andPredicateWithSubpredicates:self.predicates];
- NSCompoundPredicate *predicate = [NSCompoundPredicate notPredicateWithSubpredicate:predicates];
- layer.predicate = predicate;
- XCTAssertEqualObjects(predicate, layer.predicate);
- [self.mapView.style addLayer:layer];
-}
-
-- (void)testNotOrPredicates
-{
- NSPredicate *predicates = [NSCompoundPredicate orPredicateWithSubpredicates:self.predicates];
- NSCompoundPredicate *predicate = [NSCompoundPredicate notPredicateWithSubpredicate:predicates];
- layer.predicate = predicate;
- XCTAssertEqualObjects(predicate, layer.predicate);
- [self.mapView.style addLayer:layer];
-}
-
-@end
diff --git a/platform/darwin/test/MGLPredicateTests.mm b/platform/darwin/test/MGLPredicateTests.mm
new file mode 100644
index 0000000000..07466dfd13
--- /dev/null
+++ b/platform/darwin/test/MGLPredicateTests.mm
@@ -0,0 +1,416 @@
+#import <XCTest/XCTest.h>
+#import <Mapbox/Mapbox.h>
+
+#import "NSPredicate+MGLAdditions.h"
+#import "MGLValueEvaluator.h"
+
+namespace mbgl {
+ namespace style {
+ bool operator!=(const Filter &a, const Filter &b) {
+ return !(a == b);
+ }
+ }
+}
+
+#define MGLAssertEqualFilters(actual, expected, ...) \
+ XCTAssertTrue(actual.is<__typeof__(expected)>()); \
+ if (actual.is<__typeof__(expected)>()) { \
+ XCTAssertEqual(actual.get<__typeof__(expected)>(), expected, __VA_ARGS__); \
+ }
+
+@interface MGLPredicateTests : XCTestCase
+@end
+
+@implementation MGLPredicateTests
+
+- (void)testFilterization {
+ {
+ auto actual = [NSPredicate predicateWithValue:YES].mgl_filter;
+ mbgl::style::AllFilter expected;
+ MGLAssertEqualFilters(actual, expected);
+ }
+
+ {
+ auto actual = [NSPredicate predicateWithValue:NO].mgl_filter;
+ mbgl::style::AnyFilter expected;
+ MGLAssertEqualFilters(actual, expected);
+ }
+
+ {
+ auto actual = [NSPredicate predicateWithFormat:@"a = 'b'"].mgl_filter;
+ mbgl::style::EqualsFilter expected = { .key = "a", .value = std::string("b") };
+ MGLAssertEqualFilters(actual, expected);
+ }
+
+ {
+ auto actual = [NSPredicate predicateWithFormat:@"a = nil"].mgl_filter;
+ mbgl::style::NotHasFilter expected = { .key = "a" };
+ MGLAssertEqualFilters(actual, expected);
+ }
+
+ {
+ auto actual = [NSPredicate predicateWithFormat:@"a != 'b'"].mgl_filter;
+ mbgl::style::NotEqualsFilter expected = { .key = "a", .value = std::string("b") };
+ MGLAssertEqualFilters(actual, expected);
+ }
+
+ {
+ auto actual = [NSPredicate predicateWithFormat:@"a != nil"].mgl_filter;
+ mbgl::style::HasFilter expected = { .key = "a" };
+ MGLAssertEqualFilters(actual, expected);
+ }
+
+ {
+ auto actual = [NSPredicate predicateWithFormat:@"a < 'b'"].mgl_filter;
+ mbgl::style::LessThanFilter expected = { .key = "a", .value = std::string("b") };
+ MGLAssertEqualFilters(actual, expected);
+ }
+
+ {
+ auto actual = [NSPredicate predicateWithFormat:@"a <= 'b'"].mgl_filter;
+ mbgl::style::LessThanEqualsFilter expected = { .key = "a", .value = std::string("b") };
+ MGLAssertEqualFilters(actual, expected);
+ }
+
+ {
+ auto actual = [NSPredicate predicateWithFormat:@"a > 'b'"].mgl_filter;
+ mbgl::style::GreaterThanFilter expected = { .key = "a", .value = std::string("b") };
+ MGLAssertEqualFilters(actual, expected);
+ }
+
+ {
+ auto actual = [NSPredicate predicateWithFormat:@"a >= 'b'"].mgl_filter;
+ mbgl::style::GreaterThanEqualsFilter expected = { .key = "a", .value = std::string("b") };
+ MGLAssertEqualFilters(actual, expected);
+ }
+
+ {
+ auto actual = [NSPredicate predicateWithFormat:@"a BETWEEN {'b', 'z'}"].mgl_filter;
+ mbgl::style::AllFilter expected = {
+ .filters = {
+ mbgl::style::GreaterThanEqualsFilter { .key = "a", .value = std::string("b") },
+ mbgl::style::LessThanEqualsFilter { .key = "a", .value = std::string("z") },
+ },
+ };
+ MGLAssertEqualFilters(actual, expected);
+ }
+
+ {
+ auto actual = [NSPredicate predicateWithFormat:@"a BETWEEN %@", @[@"b", @"z"]].mgl_filter;
+ mbgl::style::AllFilter expected = {
+ .filters = {
+ mbgl::style::GreaterThanEqualsFilter { .key = "a", .value = std::string("b") },
+ mbgl::style::LessThanEqualsFilter { .key = "a", .value = std::string("z") },
+ },
+ };
+ MGLAssertEqualFilters(actual, expected);
+ }
+
+ {
+ auto actual = [NSPredicate predicateWithFormat:@"a IN {'b', 'c'}"].mgl_filter;
+ mbgl::style::InFilter expected = { .key = "a", .values = { std::string("b"), std::string("c") } };
+ MGLAssertEqualFilters(actual, expected);
+ }
+
+ {
+ auto actual = [NSPredicate predicateWithFormat:@"a IN %@", @[@"b", @"c"]].mgl_filter;
+ mbgl::style::InFilter expected = { .key = "a", .values = { std::string("b"), std::string("c") } };
+ MGLAssertEqualFilters(actual, expected);
+ }
+
+ XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"'Mapbox' IN a"].mgl_filter, NSException, NSInvalidArgumentException);
+
+ {
+ auto actual = [NSPredicate predicateWithFormat:@"{'b', 'c'} CONTAINS a"].mgl_filter;
+ mbgl::style::InFilter expected = { .key = "a", .values = { std::string("b"), std::string("c") } };
+ MGLAssertEqualFilters(actual, expected);
+ }
+
+ {
+ auto actual = [NSPredicate predicateWithFormat:@"%@ CONTAINS a", @[@"b", @"c"]].mgl_filter;
+ mbgl::style::InFilter expected = { .key = "a", .values = { std::string("b"), std::string("c") } };
+ MGLAssertEqualFilters(actual, expected);
+ }
+
+ XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a CONTAINS 'Mapbox'"].mgl_filter, NSException, NSInvalidArgumentException);
+
+ {
+ auto actual = [NSPredicate predicateWithFormat:@"a == 'b' AND c == 'd'"].mgl_filter;
+ mbgl::style::AllFilter expected = {
+ .filters = {
+ mbgl::style::EqualsFilter { .key = "a", .value = std::string("b") },
+ mbgl::style::EqualsFilter { .key = "c", .value = std::string("d") },
+ },
+ };
+ MGLAssertEqualFilters(actual, expected);
+ }
+
+ {
+ auto actual = [NSPredicate predicateWithFormat:@"a == 'b' OR c == 'd'"].mgl_filter;
+ mbgl::style::AnyFilter expected = {
+ .filters = {
+ mbgl::style::EqualsFilter { .key = "a", .value = std::string("b") },
+ mbgl::style::EqualsFilter { .key = "c", .value = std::string("d") },
+ },
+ };
+ MGLAssertEqualFilters(actual, expected);
+ }
+
+ {
+ auto actual = [NSPredicate predicateWithFormat:@"NOT(a == 'b' AND c == 'd')"].mgl_filter;
+ mbgl::style::NoneFilter expected = {
+ .filters = {
+ mbgl::style::AllFilter {
+ .filters = {
+ mbgl::style::EqualsFilter { .key = "a", .value = std::string("b") },
+ mbgl::style::EqualsFilter { .key = "c", .value = std::string("d") },
+ },
+ },
+ },
+ };
+ MGLAssertEqualFilters(actual, expected);
+ }
+
+ {
+ auto actual = [NSPredicate predicateWithFormat:@"NOT(a == 'b' OR c == 'd')"].mgl_filter;
+ mbgl::style::NoneFilter expected = {
+ .filters = {
+ mbgl::style::EqualsFilter { .key = "a", .value = std::string("b") },
+ mbgl::style::EqualsFilter { .key = "c", .value = std::string("d") },
+ },
+ };
+ MGLAssertEqualFilters(actual, expected);
+ }
+
+ {
+ auto actual = [NSPredicate predicateWithFormat:@"NOT a == nil"].mgl_filter;
+ mbgl::style::HasFilter expected = { .key = "a" };
+ MGLAssertEqualFilters(actual, expected);
+ }
+
+ {
+ auto actual = [NSPredicate predicateWithFormat:@"NOT a != nil"].mgl_filter;
+ mbgl::style::NotHasFilter expected = { .key = "a" };
+ MGLAssertEqualFilters(actual, expected);
+ }
+
+ {
+ auto actual = [NSPredicate predicateWithFormat:@"NOT a IN {'b', 'c'}"].mgl_filter;
+ mbgl::style::NotInFilter expected = { .key = "a", .values = { std::string("b"), std::string("c") } };
+ MGLAssertEqualFilters(actual, expected);
+ }
+
+ {
+ auto actual = [NSPredicate predicateWithFormat:@"NOT a IN %@", @[@"b", @"c"]].mgl_filter;
+ mbgl::style::NotInFilter expected = { .key = "a", .values = { std::string("b"), std::string("c") } };
+ MGLAssertEqualFilters(actual, expected);
+ }
+
+ {
+ auto actual = [NSPredicate predicateWithFormat:@"NOT {'b', 'c'} CONTAINS a"].mgl_filter;
+ mbgl::style::NotInFilter expected = { .key = "a", .values = { std::string("b"), std::string("c") } };
+ MGLAssertEqualFilters(actual, expected);
+ }
+
+ {
+ auto actual = [NSPredicate predicateWithFormat:@"NOT %@ CONTAINS a", @[@"b", @"c"]].mgl_filter;
+ mbgl::style::NotInFilter expected = { .key = "a", .values = { std::string("b"), std::string("c") } };
+ MGLAssertEqualFilters(actual, expected);
+ }
+
+ XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a BEGINSWITH 'L'"].mgl_filter, NSException, NSInvalidArgumentException);
+ XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a ENDSWITH 'itude'"].mgl_filter, NSException, NSInvalidArgumentException);
+ XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a LIKE 'glob?trotter'"].mgl_filter, NSException, NSInvalidArgumentException);
+ XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a MATCHES 'i\\w{18}n'"].mgl_filter, NSException, NSInvalidArgumentException);
+ NSPredicate *selectorPredicate = [NSPredicate predicateWithFormat:@"(SELF isKindOfClass: %@)", [MGLPolyline class]];
+ XCTAssertThrowsSpecificNamed(selectorPredicate.mgl_filter, NSException, NSInvalidArgumentException);
+
+ XCTAssertThrowsSpecificNamed([NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary<NSString *, id> * _Nullable bindings) {
+ XCTAssertTrue(NO, @"Predicate block should not be evaluated.");
+ return NO;
+ }].mgl_filter, NSException, NSInvalidArgumentException);
+}
+
+- (void)testPredication {
+ XCTAssertNil([NSPredicate mgl_predicateWithFilter:mbgl::style::NullFilter()]);
+
+ {
+ mbgl::style::EqualsFilter filter = { .key = "a", .value = std::string("b") };
+ XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a = 'b'"]);
+ }
+
+ {
+ mbgl::style::NotHasFilter filter = { .key = "a" };
+ XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a = nil"]);
+ }
+
+ {
+ mbgl::style::NotEqualsFilter filter = { .key = "a", .value = std::string("b") };
+ XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a != 'b'"]);
+ }
+
+ {
+ mbgl::style::HasFilter filter = { .key = "a" };
+ XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a != nil"]);
+ }
+
+ {
+ mbgl::style::LessThanFilter filter = { .key = "a", .value = std::string("b") };
+ XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a < 'b'"]);
+ }
+
+ {
+ mbgl::style::LessThanEqualsFilter filter = { .key = "a", .value = std::string("b") };
+ XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a <= 'b'"]);
+ }
+
+ {
+ mbgl::style::GreaterThanFilter filter = { .key = "a", .value = std::string("b") };
+ XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a > 'b'"]);
+ }
+
+ {
+ mbgl::style::GreaterThanEqualsFilter filter = { .key = "a", .value = std::string("b") };
+ XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a >= 'b'"]);
+ }
+
+ {
+ mbgl::style::AllFilter filter = {
+ .filters = {
+ mbgl::style::GreaterThanEqualsFilter { .key = "a", .value = std::string("b") },
+ mbgl::style::LessThanEqualsFilter { .key = "a", .value = std::string("z") },
+ },
+ };
+ XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a BETWEEN {'b', 'z'}"]);
+ }
+
+ {
+ mbgl::style::AllFilter filter = {
+ .filters = {
+ mbgl::style::LessThanEqualsFilter { .key = "a", .value = std::string("z") },
+ mbgl::style::GreaterThanEqualsFilter { .key = "a", .value = std::string("b") },
+ },
+ };
+ XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a BETWEEN {'b', 'z'}"]);
+ }
+
+ {
+ mbgl::style::InFilter filter = { .key = "a", .values = { std::string("b"), std::string("c") } };
+ XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter].predicateFormat, [NSPredicate predicateWithFormat:@"a IN {'b', 'c'}"].predicateFormat);
+ }
+
+ {
+ mbgl::style::NotInFilter filter = { .key = "a", .values = { std::string("b"), std::string("c") } };
+ XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter].predicateFormat, [NSPredicate predicateWithFormat:@"NOT a IN {'b', 'c'}"].predicateFormat);
+ }
+
+ {
+ mbgl::style::AllFilter filter;
+ XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithValue:YES]);
+ }
+
+ {
+ mbgl::style::AllFilter filter = {
+ .filters = {
+ mbgl::style::EqualsFilter { .key = "a", .value = std::string("b") },
+ mbgl::style::EqualsFilter { .key = "c", .value = std::string("d") },
+ },
+ };
+ XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a == 'b' AND c == 'd'"]);
+ }
+
+ {
+ mbgl::style::AnyFilter filter;
+ XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithValue:NO]);
+ }
+
+ {
+ mbgl::style::AnyFilter filter = {
+ .filters = {
+ mbgl::style::EqualsFilter { .key = "a", .value = std::string("b") },
+ mbgl::style::EqualsFilter { .key = "c", .value = std::string("d") },
+ },
+ };
+ XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a == 'b' OR c == 'd'"]);
+ }
+
+ {
+ mbgl::style::NoneFilter filter;
+ XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithValue:YES]);
+ }
+
+ {
+ mbgl::style::NoneFilter filter = {
+ .filters = {
+ mbgl::style::EqualsFilter { .key = "a", .value = std::string("b") },
+ mbgl::style::EqualsFilter { .key = "c", .value = std::string("d") },
+ },
+ };
+ XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"NOT(a == 'b' OR c == 'd')"]);
+ }
+}
+
+- (void)testSymmetry {
+ [self testSymmetryWithFormat:@"a = 1" reverseFormat:@"1 = a" mustRoundTrip:YES];
+ [self testSymmetryWithFormat:@"a != 1" reverseFormat:@"1 != a" mustRoundTrip:YES];
+ XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a = b"].mgl_filter, NSException, NSInvalidArgumentException);
+ XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"1 = 1"].mgl_filter, NSException, NSInvalidArgumentException);
+
+ // In the predicate format language, $ is a special character denoting a
+ // variable. Use %K to escape the special feature attribute $id.
+ XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"$id == 670861802"].mgl_filter, NSException, NSInvalidArgumentException);
+ XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a = $id"].mgl_filter, NSException, NSInvalidArgumentException);
+
+ [self testSymmetryWithFormat:@"a = nil" reverseFormat:@"nil = a" mustRoundTrip:YES];
+ [self testSymmetryWithFormat:@"a != nil" reverseFormat:@"nil != a" mustRoundTrip:YES];
+
+ [self testSymmetryWithFormat:@"a < 1" reverseFormat:@"1 > a" mustRoundTrip:YES];
+ [self testSymmetryWithFormat:@"a <= 1" reverseFormat:@"1 >= a" mustRoundTrip:YES];
+ [self testSymmetryWithFormat:@"a > 1" reverseFormat:@"1 < a" mustRoundTrip:YES];
+ [self testSymmetryWithFormat:@"a >= 1" reverseFormat:@"1 <= a" mustRoundTrip:YES];
+
+ [self testSymmetryWithFormat:@"a BETWEEN {1, 2}" reverseFormat:nil mustRoundTrip:YES];
+ [self testSymmetryWithPredicate:[NSPredicate predicateWithFormat:@"a BETWEEN %@", @[@1, @2]]
+ reversePredicate:nil
+ mustRoundTrip:YES];
+ XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"{1, 2} BETWEEN a"].mgl_filter, NSException, NSInvalidArgumentException);
+ NSPredicate *betweenSetPredicate = [NSPredicate predicateWithFormat:@"a BETWEEN %@", [NSSet setWithObjects:@1, @2, nil]];
+ XCTAssertThrowsSpecificNamed(betweenSetPredicate.mgl_filter, NSException, NSInvalidArgumentException);
+ XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a BETWEEN {1}"].mgl_filter, NSException, NSInvalidArgumentException);
+ XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a BETWEEN {1, 2, 3}"].mgl_filter, NSException, NSInvalidArgumentException);
+
+ [self testSymmetryWithFormat:@"a IN {1, 2}" reverseFormat:@"{1, 2} CONTAINS a" mustRoundTrip:NO];
+ [self testSymmetryWithPredicate:[NSPredicate predicateWithFormat:@"a IN %@", @[@1, @2]]
+ reversePredicate:[NSPredicate predicateWithFormat:@"%@ CONTAINS a", @[@1, @2]]
+ mustRoundTrip:YES];
+
+ [self testSymmetryWithFormat:@"{1, 2} CONTAINS a" reverseFormat:@"a IN {1, 2}" mustRoundTrip:NO];
+ [self testSymmetryWithPredicate:[NSPredicate predicateWithFormat:@"%@ CONTAINS a", @[@1, @2]]
+ reversePredicate:[NSPredicate predicateWithFormat:@"a IN %@", @[@1, @2]]
+ mustRoundTrip:NO];
+}
+
+- (void)testSymmetryWithFormat:(NSString *)forwardFormat reverseFormat:(NSString *)reverseFormat mustRoundTrip:(BOOL)mustRoundTrip {
+ NSPredicate *forwardPredicate = [NSPredicate predicateWithFormat:forwardFormat];
+ NSPredicate *reversePredicate = reverseFormat ? [NSPredicate predicateWithFormat:reverseFormat] : nil;
+ [self testSymmetryWithPredicate:forwardPredicate reversePredicate:reversePredicate mustRoundTrip:mustRoundTrip];
+}
+
+- (void)testSymmetryWithPredicate:(NSPredicate *)forwardPredicate reversePredicate:(NSPredicate *)reversePredicate mustRoundTrip:(BOOL)mustRoundTrip {
+ auto forwardFilter = forwardPredicate.mgl_filter;
+ NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter];
+ if (mustRoundTrip) {
+ // Aggregates should round-trip, but for some reason only their format strings do.
+ XCTAssertEqualObjects(forwardPredicate.predicateFormat, forwardPredicateAfter.predicateFormat);
+ }
+
+ if (reversePredicate) {
+ auto reverseFilter = reversePredicate.mgl_filter;
+ NSPredicate *reversePredicateAfter = [NSPredicate mgl_predicateWithFilter:reverseFilter];
+ XCTAssertNotEqualObjects(reversePredicate, reversePredicateAfter);
+
+ XCTAssertEqualObjects(forwardPredicateAfter, reversePredicateAfter);
+ }
+}
+
+@end
diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj
index 139e2b014b..ebcf3a55cb 100644
--- a/platform/ios/ios.xcodeproj/project.pbxproj
+++ b/platform/ios/ios.xcodeproj/project.pbxproj
@@ -103,7 +103,7 @@
35B82BF91D6C5F8400B1B721 /* NSPredicate+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 35B82BF61D6C5F8400B1B721 /* NSPredicate+MGLAdditions.h */; };
35B82BFA1D6C5F8400B1B721 /* NSPredicate+MGLAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35B82BF71D6C5F8400B1B721 /* NSPredicate+MGLAdditions.mm */; };
35B82BFB1D6C5F8400B1B721 /* NSPredicate+MGLAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35B82BF71D6C5F8400B1B721 /* NSPredicate+MGLAdditions.mm */; };
- 35B8E08C1D6C8B5100E768D2 /* MGLFilterTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35B8E08B1D6C8B5100E768D2 /* MGLFilterTests.mm */; };
+ 35B8E08C1D6C8B5100E768D2 /* MGLPredicateTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35B8E08B1D6C8B5100E768D2 /* MGLPredicateTests.mm */; };
35CE61821D4165D9004F2359 /* UIColor+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 35CE61801D4165D9004F2359 /* UIColor+MGLAdditions.h */; };
35CE61831D4165D9004F2359 /* UIColor+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 35CE61801D4165D9004F2359 /* UIColor+MGLAdditions.h */; };
35CE61841D4165D9004F2359 /* UIColor+MGLAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35CE61811D4165D9004F2359 /* UIColor+MGLAdditions.mm */; };
@@ -570,7 +570,7 @@
359F57451D2FDBD5005217F1 /* MGLUserLocationAnnotationView_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLUserLocationAnnotationView_Private.h; sourceTree = "<group>"; };
35B82BF61D6C5F8400B1B721 /* NSPredicate+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSPredicate+MGLAdditions.h"; sourceTree = "<group>"; };
35B82BF71D6C5F8400B1B721 /* NSPredicate+MGLAdditions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSPredicate+MGLAdditions.mm"; sourceTree = "<group>"; };
- 35B8E08B1D6C8B5100E768D2 /* MGLFilterTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLFilterTests.mm; path = ../../darwin/test/MGLFilterTests.mm; sourceTree = "<group>"; };
+ 35B8E08B1D6C8B5100E768D2 /* MGLPredicateTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLPredicateTests.mm; path = ../../darwin/test/MGLPredicateTests.mm; sourceTree = "<group>"; };
35CE61801D4165D9004F2359 /* UIColor+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIColor+MGLAdditions.h"; sourceTree = "<group>"; };
35CE61811D4165D9004F2359 /* UIColor+MGLAdditions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "UIColor+MGLAdditions.mm"; sourceTree = "<group>"; };
35D13AB51D3D15E300AFB4E0 /* MGLStyleLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLStyleLayer.h; sourceTree = "<group>"; };
@@ -937,11 +937,10 @@
isa = PBXGroup;
children = (
3575798F1D513EF1000B822E /* Layers */,
- 35B8E08B1D6C8B5100E768D2 /* MGLFilterTests.mm */,
+ 40CFA64E1D78754A008103BD /* Sources */,
357F09091DF84F3800941873 /* MGLStyleValueTests.h */,
3599A3E51DF708BC00E77FB2 /* MGLStyleValueTests.m */,
DA2207BE1DC0805F0002F84D /* MGLStyleValueTests.swift */,
- 40CFA64E1D78754A008103BD /* Sources */,
);
name = Styling;
sourceTree = "<group>";
@@ -1094,9 +1093,11 @@
DAEDC4331D603417000224FF /* MGLAttributionInfoTests.m */,
353D23951D0B0DFE002BE09D /* MGLAnnotationViewTests.m */,
DA35A2C31CCA9F8300E826B2 /* MGLClockDirectionFormatterTests.m */,
+ 35D9DDE11DA25EEC00DAAD69 /* MGLCodingTests.m */,
DA35A2C41CCA9F8300E826B2 /* MGLCompassDirectionFormatterTests.m */,
DA35A2A91CCA058D00E826B2 /* MGLCoordinateFormatterTests.m */,
6407D66F1E0085FD00F6A9C3 /* MGLDocumentationExampleTests.swift */,
+ DD58A4C51D822BD000E1F038 /* MGLExpressionTests.mm */,
DA0CD58F1CF56F6A00A5F5A5 /* MGLFeatureTests.mm */,
DA2E885C1CC0382C00F24E7B /* MGLGeometryTests.mm */,
DA2DBBCC1D51E80400D38FF9 /* MGLStyleLayerTests.h */,
@@ -1105,9 +1106,8 @@
DA2E885D1CC0382C00F24E7B /* MGLOfflinePackTests.m */,
DA2E885E1CC0382C00F24E7B /* MGLOfflineRegionTests.m */,
DA2E885F1CC0382C00F24E7B /* MGLOfflineStorageTests.m */,
- 35D9DDE11DA25EEC00DAAD69 /* MGLCodingTests.m */,
+ 35B8E08B1D6C8B5100E768D2 /* MGLPredicateTests.mm */,
DA2E88601CC0382C00F24E7B /* MGLStyleTests.mm */,
- DD58A4C51D822BD000E1F038 /* MGLExpressionTests.mm */,
DA2E88551CC036F400F24E7B /* Info.plist */,
DA2784FB1DF02FF4001D5B8D /* Media.xcassets */,
);
@@ -1785,6 +1785,7 @@
TargetAttributes = {
DA1DC9491CB6C1C2006E619F = {
CreatedOnToolsVersion = 7.3;
+ DevelopmentTeam = GJZR2MEM28;
LastSwiftMigration = 0820;
};
DA25D5B81CCD9EDE00607828 = {
@@ -2002,7 +2003,7 @@
DA2207BF1DC0805F0002F84D /* MGLStyleValueTests.swift in Sources */,
40CFA6511D7875BB008103BD /* MGLShapeSourceTests.mm in Sources */,
DA35A2C51CCA9F8300E826B2 /* MGLClockDirectionFormatterTests.m in Sources */,
- 35B8E08C1D6C8B5100E768D2 /* MGLFilterTests.mm in Sources */,
+ 35B8E08C1D6C8B5100E768D2 /* MGLPredicateTests.mm in Sources */,
DD58A4C61D822BD000E1F038 /* MGLExpressionTests.mm in Sources */,
3575798B1D502B0C000B822E /* MGLBackgroundStyleLayerTests.m in Sources */,
DA2E88621CC0382C00F24E7B /* MGLOfflinePackTests.m in Sources */,
diff --git a/platform/macos/macos.xcodeproj/project.pbxproj b/platform/macos/macos.xcodeproj/project.pbxproj
index 951ae1bd37..56872a1b19 100644
--- a/platform/macos/macos.xcodeproj/project.pbxproj
+++ b/platform/macos/macos.xcodeproj/project.pbxproj
@@ -99,7 +99,7 @@
DA87A9981DC9D88400810D09 /* MGLShapeSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA87A9961DC9D88400810D09 /* MGLShapeSourceTests.mm */; };
DA87A9991DC9D88400810D09 /* MGLTileSetTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA87A9971DC9D88400810D09 /* MGLTileSetTests.mm */; };
DA87A99C1DC9D8DD00810D09 /* MGLShapeSource_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DA87A99B1DC9D8DD00810D09 /* MGLShapeSource_Private.h */; };
- DA87A99E1DC9DC2100810D09 /* MGLFilterTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35C5D84B1D6DD75B00E95907 /* MGLFilterTests.mm */; };
+ DA87A99E1DC9DC2100810D09 /* MGLPredicateTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35C5D84B1D6DD75B00E95907 /* MGLPredicateTests.mm */; };
DA87A9A01DC9DC6200810D09 /* MGLValueEvaluator.h in Headers */ = {isa = PBXBuildFile; fileRef = DA87A99F1DC9DC6200810D09 /* MGLValueEvaluator.h */; };
DA87A9A11DC9DCB400810D09 /* MGLRuntimeStylingHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = DA8F257B1D51C5F40010E6B5 /* MGLRuntimeStylingHelper.m */; };
DA87A9A21DC9DCF100810D09 /* MGLFillStyleLayerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DA8F25741D51C5F40010E6B5 /* MGLFillStyleLayerTests.m */; };
@@ -287,7 +287,7 @@
35C5D8441D6DD66D00E95907 /* NSComparisonPredicate+MGLAdditions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSComparisonPredicate+MGLAdditions.mm"; sourceTree = "<group>"; };
35C5D8451D6DD66D00E95907 /* NSCompoundPredicate+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSCompoundPredicate+MGLAdditions.h"; sourceTree = "<group>"; };
35C5D8461D6DD66D00E95907 /* NSCompoundPredicate+MGLAdditions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSCompoundPredicate+MGLAdditions.mm"; sourceTree = "<group>"; };
- 35C5D84B1D6DD75B00E95907 /* MGLFilterTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLFilterTests.mm; sourceTree = "<group>"; };
+ 35C5D84B1D6DD75B00E95907 /* MGLPredicateTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLPredicateTests.mm; path = ../../darwin/test/MGLPredicateTests.mm; sourceTree = "<group>"; };
35D65C581D65AD5500722C23 /* NSDate+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDate+MGLAdditions.h"; sourceTree = "<group>"; };
35D65C591D65AD5500722C23 /* NSDate+MGLAdditions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSDate+MGLAdditions.mm"; sourceTree = "<group>"; };
4032C5B91DE1EEBA0062E8BD /* NSValue+MGLStyleEnumAttributeAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSValue+MGLStyleEnumAttributeAdditions.h"; sourceTree = "<group>"; };
@@ -714,7 +714,6 @@
children = (
DA8F257C1D51C5F40010E6B5 /* Layers */,
DA87A99A1DC9D88800810D09 /* Sources */,
- 35C5D84B1D6DD75B00E95907 /* MGLFilterTests.mm */,
353722EB1DF850ED004D2F3F /* MGLStyleValueTests.h */,
3599A3E71DF70E2000E77FB2 /* MGLStyleValueTests.m */,
DA2207BB1DC076940002F84D /* MGLStyleValueTests.swift */,
@@ -883,17 +882,18 @@
DA8F257D1D51C5F40010E6B5 /* Styling */,
DAEDC4311D6033F1000224FF /* MGLAttributionInfoTests.m */,
DAEDC4361D606291000224FF /* MGLAttributionButtonTests.m */,
- 3526EABC1DF9B19800006B43 /* MGLCodingTests.m */,
DA35A2C11CCA9F4A00E826B2 /* MGLClockDirectionFormatterTests.m */,
+ 3526EABC1DF9B19800006B43 /* MGLCodingTests.m */,
DA35A2B51CCA14D700E826B2 /* MGLCompassDirectionFormatterTests.m */,
DA35A2A71CC9F41600E826B2 /* MGLCoordinateFormatterTests.m */,
+ DD58A4C71D822C6200E1F038 /* MGLExpressionTests.mm */,
DA0CD58D1CF56F5800A5F5A5 /* MGLFeatureTests.mm */,
DAE6C3C81CC34BD800DB3429 /* MGLGeometryTests.mm */,
DAE6C3C91CC34BD800DB3429 /* MGLOfflinePackTests.m */,
DAE6C3CA1CC34BD800DB3429 /* MGLOfflineRegionTests.m */,
DAE6C3CB1CC34BD800DB3429 /* MGLOfflineStorageTests.m */,
+ 35C5D84B1D6DD75B00E95907 /* MGLPredicateTests.mm */,
DAE6C3CC1CC34BD800DB3429 /* MGLStyleTests.mm */,
- DD58A4C71D822C6200E1F038 /* MGLExpressionTests.mm */,
DAE6C33A1CC30DB200DB3429 /* Info.plist */,
DA2784FD1DF03060001D5B8D /* Media.xcassets */,
);
@@ -1323,7 +1323,7 @@
DAE6C3D51CC34C9900DB3429 /* MGLOfflineStorageTests.m in Sources */,
40E1601D1DF217D6005EA6D9 /* MGLStyleLayerTests.m in Sources */,
DA87A9A61DCACC5000810D09 /* MGLCircleStyleLayerTests.m in Sources */,
- DA87A99E1DC9DC2100810D09 /* MGLFilterTests.mm in Sources */,
+ DA87A99E1DC9DC2100810D09 /* MGLPredicateTests.mm in Sources */,
DD58A4C91D822C6700E1F038 /* MGLExpressionTests.mm in Sources */,
DA87A9A71DCACC5000810D09 /* MGLBackgroundStyleLayerTests.m in Sources */,
DAE6C3D31CC34C9900DB3429 /* MGLOfflinePackTests.m in Sources */,