summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMinh Nguyễn <mxn@1ec5.org>2016-12-26 14:24:05 -0800
committerMinh Nguyễn <mxn@1ec5.org>2017-01-04 08:52:41 -0800
commite6132d248b54d202c3fa132b3084660a0bbe3c06 (patch)
treef6b98d23625169ded08d8f3b8965d3619f3fe2da
parent83309fd369c9623a1e2638f98f75206609d44258 (diff)
downloadqtlocation-mapboxgl-e6132d248b54d202c3fa132b3084660a0bbe3c06.tar.gz
[ios, macos] Rewrote predicate/filter conversion
When converting predicates to filters, symmetric comparison predicates can now compare a value to a key in addition to the usual key-to-value order. Added error checking for unhandled combinations like key-to-key. Fixed a crash converting a CONTAINS predicate into a filter. Added support for constant value expressions inside aggregate expressions. Allow sets as aggregate expressions just like arrays, except in BETWEEN predicates where order matters. Flatten NOT predicates into more specialized filters. When converting filters to predicates, use constant value expressions inside aggregate expressions. Convert to a BETWEEN predicate when possible. Replaced predicate round-tripping integration tests with systematic unit tests for converting in either direction, plus unit tests for round-tripping and symmetry. Refined exception names and messages. Realphabetized files in groups.
-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 */,