diff --git a/platform/darwin/docs/guides/Predicates and Expressions.md b/platform/darwin/docs/guides/Predicates and Expressions.md index ee8f831fbdc..6b191720b71 100644 --- a/platform/darwin/docs/guides/Predicates and Expressions.md +++ b/platform/darwin/docs/guides/Predicates and Expressions.md @@ -31,6 +31,9 @@ The following comparison operators are supported: `NSNotEqualToPredicateOperatorType` | `key != value`
`key <> value` `NSBetweenPredicateOperatorType` | `key BETWEEN { 32, 212 }` +The `key` may be cast explicitly into the key's type. Example: `CAST(key, 'NSNumber')` or +`CAST(key, 'NSString')` + The following compound operators are supported: `NSCompoundPredicateType` | Format string syntax @@ -56,8 +59,11 @@ Format String Syntax” chapter of the _[Predicate Programming Guide](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Predicates/AdditionalChapters/Introduction.html)_ in Apple developer documentation. -The predicate's left-hand expression must be a string that identifies a feature -attribute or, alternatively, one of the following special attributes: +Either the predicate’s left-hand or right-hand expression can be an `NSString` +(to match strings) or `NSNumber` (to match numbers, including Boolean values) +or an array of `NSString`s or `NSNumber`s, depending on the operator and the +type of values expected for the attribute being tested or, alternatively, +one of the following special attributes: @@ -101,12 +107,8 @@ attribute or, alternatively, one of the following special attributes:
-The predicate’s right-hand expression must be an `NSString` (to match strings) -or `NSNumber` (to match numbers, including Boolean values) or an array of -`NSString`s or `NSNumber`s, depending on the operator and the type of values -expected for the attribute being tested. For floating-point values, use -`-[NSNumber numberWithDouble:]` instead of `-[NSNumber numberWithFloat:]` -to avoid precision issues. +For floating-point values, use `-[NSNumber numberWithDouble:]` instead of +`-[NSNumber numberWithFloat:]` to avoid precision issues. Automatic type casting is not performed. Therefore, a feature only matches this predicate if its value for the attribute in question is of the same type as the @@ -116,10 +118,10 @@ sensitivity) are unsupported for comparison and aggregate operators that are used in the predicate. It is possible to create expressions that contain special characters in the -predicate format syntax. This includes the `$` in the `$id` and `$type` special -style attributes and also `hyphen-minus` and `tag:subtag`. However, you must use -`%K` in the format string to represent these variables: -`@"%K == 'LineString'", @"$type"`. +predicate format syntax. This includes the `$` in the `$featureIdentifier` and +`$geometryType` special style attributes and also `hyphen-minus` and `tag:subtag`. +However, you must use `%@` in the format string to represent these variables: +`@"$geometryType == 'LineString'"`. ## Using expressions to configure layout and paint attributes diff --git a/platform/darwin/src/MGLVectorStyleLayer.h b/platform/darwin/src/MGLVectorStyleLayer.h index b3c8fc24334..d9431215a14 100644 --- a/platform/darwin/src/MGLVectorStyleLayer.h +++ b/platform/darwin/src/MGLVectorStyleLayer.h @@ -47,7 +47,7 @@ MGL_EXPORT ```swift let layer = MGLLineStyleLayer(identifier: "contour", source: terrain) layer.sourceLayerIdentifier = "contours" - layer.predicate = NSPredicate(format: "(index == 5 || index == 10) && ele >= 1500.0") + layer.predicate = NSPredicate(format: "(index == 5 || index == 10) && CAST(ele, 'NSNumber') >= 1500.0") mapView.style?.addLayer(layer) ``` */ diff --git a/platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm b/platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm index c39e987d854..380215ff32f 100644 --- a/platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm +++ b/platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm @@ -1,231 +1,12 @@ #import "NSComparisonPredicate+MGLAdditions.h" +#import "MGLStyleValue_Private.h" + #import "NSPredicate+MGLAdditions.h" #import "NSExpression+MGLPrivateAdditions.h" @implementation NSComparisonPredicate (MGLAdditions) -- (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: { - mbgl::style::EqualsFilter eqFilter; - eqFilter.key = self.mgl_keyPath.UTF8String; - eqFilter.value = self.mgl_constantValue; - - // Convert $type == to TypeEqualsFilter. - if (eqFilter.key == "$type") { - mbgl::style::TypeEqualsFilter typeEqFilter; - typeEqFilter.value = self.mgl_featureType; - return typeEqFilter; - } - - // Convert $id == to IdentifierEqualsFilter. - if (eqFilter.key == "$id") { - // Convert $id == nil to NotHasIdentifierFilter. - if (eqFilter.value.is()) { - return mbgl::style::NotHasIdentifierFilter(); - } - - mbgl::style::IdentifierEqualsFilter idEqFilter; - idEqFilter.value = self.mgl_featureIdentifier; - return idEqFilter; - } - - // Convert == nil to NotHasFilter. - if (eqFilter.value.is()) { - mbgl::style::NotHasFilter notHasFilter; - notHasFilter.key = eqFilter.key; - return notHasFilter; - } - - return eqFilter; - } - case NSNotEqualToPredicateOperatorType: { - mbgl::style::NotEqualsFilter neFilter; - neFilter.key = self.mgl_keyPath.UTF8String; - neFilter.value = self.mgl_constantValue; - - // Convert $type != to TypeNotEqualsFilter. - if (neFilter.key == "$type") { - mbgl::style::TypeNotEqualsFilter typeNeFilter; - typeNeFilter.value = self.mgl_featureType; - return typeNeFilter; - } - - // Convert $id != to IdentifierNotEqualsFilter. - if (neFilter.key == "$id") { - // Convert $id != nil to HasIdentifierFilter. - if (neFilter.value.is()) { - return mbgl::style::HasIdentifierFilter(); - } - - mbgl::style::IdentifierNotEqualsFilter idNeFilter; - idNeFilter.value = self.mgl_featureIdentifier; - return idNeFilter; - } - - // Convert != nil to HasFilter. - if (neFilter.value.is()) { - mbgl::style::HasFilter hasFilter; - hasFilter.key = neFilter.key; - return hasFilter; - } - - return neFilter; - } - case NSGreaterThanPredicateOperatorType: { - 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: { - 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: { - 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: { - 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: { - 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."]; - } - - // Convert $type IN to TypeInFilter. - if ([leftExpression.keyPath isEqualToString:@"$type"]) { - mbgl::style::TypeInFilter typeInFilter; - typeInFilter.values = rightExpression.mgl_aggregateFeatureType; - return typeInFilter; - } - - // Convert $id IN to IdentifierInFilter. - if ([leftExpression.keyPath isEqualToString:@"$id"]) { - mbgl::style::IdentifierInFilter idInFilter; - idInFilter.values = rightExpression.mgl_aggregateFeatureIdentifier; - return idInFilter; - } - - mbgl::style::InFilter inFilter; - inFilter.key = leftExpression.keyPath.UTF8String; - inFilter.values = rightExpression.mgl_aggregateMBGLValue; - return inFilter; - } - case NSContainsPredicateOperatorType: { - 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."]; - } - - // Convert CONTAINS $type to TypeInFilter. - if ([rightExpression.keyPath isEqualToString:@"$type"]) { - mbgl::style::TypeInFilter typeInFilter; - typeInFilter.values = leftExpression.mgl_aggregateFeatureType; - return typeInFilter; - } - - // Convert CONTAINS $id to IdentifierInFilter. - if ([rightExpression.keyPath isEqualToString:@"$id"]) { - mbgl::style::IdentifierInFilter idInFilter; - idInFilter.values = leftExpression.mgl_aggregateFeatureIdentifier; - return idInFilter; - } - - mbgl::style::InFilter inFilter; - inFilter.key = rightExpression.keyPath.UTF8String; - inFilter.values = leftExpression.mgl_aggregateMBGLValue; - return inFilter; - } - case NSBetweenPredicateOperatorType: { - 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_aggregateMBGLValue; - 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:NSInvalidArgumentException - format:@"NSPredicateOperatorType:%lu is not supported.", (unsigned long)self.predicateOperatorType]; - } - - return {}; -} - - (NSString *)mgl_keyPath { NSExpression *leftExpression = self.leftExpression; NSExpression *rightExpression = self.rightExpression; diff --git a/platform/darwin/src/NSCompoundPredicate+MGLAdditions.mm b/platform/darwin/src/NSCompoundPredicate+MGLAdditions.mm index 19568b8033d..5a98b763ea3 100644 --- a/platform/darwin/src/NSCompoundPredicate+MGLAdditions.mm +++ b/platform/darwin/src/NSCompoundPredicate+MGLAdditions.mm @@ -1,8 +1,12 @@ #import "NSCompoundPredicate+MGLAdditions.h" +#import "MGLStyleValue_Private.h" + #import "NSPredicate+MGLAdditions.h" #import "NSExpression+MGLPrivateAdditions.h" +#include + @implementation NSCompoundPredicate (MGLAdditions) - (std::vector)mgl_subfilters @@ -14,62 +18,6 @@ @implementation NSCompoundPredicate (MGLAdditions) return filters; } -- (mbgl::style::Filter)mgl_filter -{ - 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; - - // Convert NOT(!= nil) to NotHasFilter. - if (subfilter.is()) { - auto hasFilter = subfilter.get(); - return mbgl::style::NotHasFilter { .key = hasFilter.key }; - } - - // Convert NOT(== nil) to HasFilter. - if (subfilter.is()) { - auto hasFilter = subfilter.get(); - return mbgl::style::HasFilter { .key = hasFilter.key }; - } - - // Convert NOT(IN) or NOT(CONTAINS) to NotInFilter. - if (subfilter.is()) { - auto inFilter = subfilter.get(); - mbgl::style::NotInFilter notInFilter; - notInFilter.key = inFilter.key; - notInFilter.values = inFilter.values; - return notInFilter; - } - - // Convert NOT(), NOT(AND), NOT(NOT), NOT(==), etc. into NoneFilter. - mbgl::style::NoneFilter noneFilter; - if (subfilter.is()) { - // Flatten NOT(OR). - noneFilter.filters = subfilter.get().filters; - } else if (subpredicate) { - noneFilter.filters = { subfilter }; - } - return noneFilter; - } - case NSAndPredicateType: { - mbgl::style::AllFilter filter; - filter.filters = self.mgl_subfilters; - return filter; - } - case NSOrPredicateType: { - mbgl::style::AnyFilter filter; - filter.filters = self.mgl_subfilters; - return filter; - } - } - - [NSException raise:@"Compound predicate type not handled" - format:@""]; - return {}; -} - @end @implementation NSCompoundPredicate (MGLExpressionAdditions) diff --git a/platform/darwin/src/NSExpression+MGLAdditions.mm b/platform/darwin/src/NSExpression+MGLAdditions.mm index 3c42b80a134..c175868daea 100644 --- a/platform/darwin/src/NSExpression+MGLAdditions.mm +++ b/platform/darwin/src/NSExpression+MGLAdditions.mm @@ -562,7 +562,7 @@ + (instancetype)expressionWithMGLJSONObject:(id)object { argumentObjects = [argumentObjects subarrayWithRange:NSMakeRange(1, argumentObjects.count - 1)]; NSArray *subexpressions = MGLSubexpressionsWithJSONObjects(argumentObjects); return [NSExpression expressionForFunction:operand selectorName:@"mgl_numberWithFallbackValues:" arguments:subexpressions]; - } else if ([op isEqualToString:@"to-string"]) { + } else if ([op isEqualToString:@"to-string"] || [op isEqualToString:@"string"]) { NSExpression *operand = [NSExpression expressionWithMGLJSONObject:argumentObjects.firstObject]; return [NSExpression expressionWithFormat:@"CAST(%@, 'NSString')", operand]; } else if ([op isEqualToString:@"get"]) { diff --git a/platform/darwin/src/NSPredicate+MGLAdditions.mm b/platform/darwin/src/NSPredicate+MGLAdditions.mm index ed48c794aa4..bbd324bb631 100644 --- a/platform/darwin/src/NSPredicate+MGLAdditions.mm +++ b/platform/darwin/src/NSPredicate+MGLAdditions.mm @@ -3,6 +3,8 @@ #import "MGLValueEvaluator.h" #import "MGLStyleValue_Private.h" +#include + class FilterEvaluator { public: @@ -206,25 +208,18 @@ @implementation NSPredicate (MGLAdditions) - (mbgl::style::Filter)mgl_filter { - if ([self isEqual:[NSPredicate predicateWithValue:YES]]) - { - return mbgl::style::AllFilter(); - } - - if ([self isEqual:[NSPredicate predicateWithValue:NO]]) - { - return mbgl::style::AnyFilter(); - } - - if ([self.predicateFormat hasPrefix:@"BLOCKPREDICATE("]) - { + mbgl::style::conversion::Error valueError; + NSArray *jsonObject = self.mgl_jsonExpressionObject; + auto value = mbgl::style::conversion::convert(mbgl::style::conversion::makeConvertible(jsonObject), valueError); + + if (!value) { [NSException raise:NSInvalidArgumentException - format:@"Block-based predicates are not supported."]; + format:@"Invalid filter value: %@", @(valueError.message.c_str())]; + return {}; } - - [NSException raise:NSInvalidArgumentException - format:@"Unrecognized predicate type."]; - return {}; + mbgl::style::Filter filter = std::move(*value); + + return filter; } + (instancetype)mgl_predicateWithFilter:(mbgl::style::Filter)filter diff --git a/platform/darwin/test/MGLCircleStyleLayerTests.mm b/platform/darwin/test/MGLCircleStyleLayerTests.mm index 2c877d4ddf9..d7bf2a5afd0 100644 --- a/platform/darwin/test/MGLCircleStyleLayerTests.mm +++ b/platform/darwin/test/MGLCircleStyleLayerTests.mm @@ -30,8 +30,8 @@ - (void)testPredicates { XCTAssertNil(layer.sourceLayerIdentifier); XCTAssertNil(layer.predicate); - layer.predicate = [NSPredicate predicateWithValue:NO]; - XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithValue:NO]); + layer.predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier = 1"]; + XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithFormat:@"$featureIdentifier = 1"]); layer.predicate = nil; XCTAssertNil(layer.predicate); } diff --git a/platform/darwin/test/MGLDocumentationExampleTests.swift b/platform/darwin/test/MGLDocumentationExampleTests.swift index 6c16ed426c0..92f5162aab5 100644 --- a/platform/darwin/test/MGLDocumentationExampleTests.swift +++ b/platform/darwin/test/MGLDocumentationExampleTests.swift @@ -328,7 +328,7 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate { //#-example-code let layer = MGLLineStyleLayer(identifier: "contour", source: terrain) layer.sourceLayerIdentifier = "contours" - layer.predicate = NSPredicate(format: "(index == 5 || index == 10) && ele >= 1500.0") + layer.predicate = NSPredicate(format: "(index == 5 || index == 10) && CAST(ele, 'NSNumber') >= 1500.0") mapView.style?.addLayer(layer) //#-end-example-code diff --git a/platform/darwin/test/MGLFillExtrusionStyleLayerTests.mm b/platform/darwin/test/MGLFillExtrusionStyleLayerTests.mm index 9fa5a7eb8b1..6081d104e12 100644 --- a/platform/darwin/test/MGLFillExtrusionStyleLayerTests.mm +++ b/platform/darwin/test/MGLFillExtrusionStyleLayerTests.mm @@ -30,8 +30,8 @@ - (void)testPredicates { XCTAssertNil(layer.sourceLayerIdentifier); XCTAssertNil(layer.predicate); - layer.predicate = [NSPredicate predicateWithValue:NO]; - XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithValue:NO]); + layer.predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier = 1"]; + XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithFormat:@"$featureIdentifier = 1"]); layer.predicate = nil; XCTAssertNil(layer.predicate); } diff --git a/platform/darwin/test/MGLFillStyleLayerTests.mm b/platform/darwin/test/MGLFillStyleLayerTests.mm index 1c2342bb0f7..a5019f10323 100644 --- a/platform/darwin/test/MGLFillStyleLayerTests.mm +++ b/platform/darwin/test/MGLFillStyleLayerTests.mm @@ -30,8 +30,8 @@ - (void)testPredicates { XCTAssertNil(layer.sourceLayerIdentifier); XCTAssertNil(layer.predicate); - layer.predicate = [NSPredicate predicateWithValue:NO]; - XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithValue:NO]); + layer.predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier = 1"]; + XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithFormat:@"$featureIdentifier = 1"]); layer.predicate = nil; XCTAssertNil(layer.predicate); } diff --git a/platform/darwin/test/MGLHeatmapStyleLayerTests.mm b/platform/darwin/test/MGLHeatmapStyleLayerTests.mm index 54ba5c2fe92..e4b19172571 100644 --- a/platform/darwin/test/MGLHeatmapStyleLayerTests.mm +++ b/platform/darwin/test/MGLHeatmapStyleLayerTests.mm @@ -30,8 +30,8 @@ - (void)testPredicates { XCTAssertNil(layer.sourceLayerIdentifier); XCTAssertNil(layer.predicate); - layer.predicate = [NSPredicate predicateWithValue:NO]; - XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithValue:NO]); + layer.predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier = 1"]; + XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithFormat:@"$featureIdentifier = 1"]); layer.predicate = nil; XCTAssertNil(layer.predicate); } diff --git a/platform/darwin/test/MGLLineStyleLayerTests.mm b/platform/darwin/test/MGLLineStyleLayerTests.mm index 1a81a16eeeb..5490278e983 100644 --- a/platform/darwin/test/MGLLineStyleLayerTests.mm +++ b/platform/darwin/test/MGLLineStyleLayerTests.mm @@ -30,8 +30,8 @@ - (void)testPredicates { XCTAssertNil(layer.sourceLayerIdentifier); XCTAssertNil(layer.predicate); - layer.predicate = [NSPredicate predicateWithValue:NO]; - XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithValue:NO]); + layer.predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier = 1"]; + XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithFormat:@"$featureIdentifier = 1"]); layer.predicate = nil; XCTAssertNil(layer.predicate); } diff --git a/platform/darwin/test/MGLPredicateTests.mm b/platform/darwin/test/MGLPredicateTests.mm index 18918f1b8f3..ab4a7e2d88d 100644 --- a/platform/darwin/test/MGLPredicateTests.mm +++ b/platform/darwin/test/MGLPredicateTests.mm @@ -23,295 +23,9 @@ @interface MGLPredicateTests : XCTestCase @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:@"%K = 'Point'", @"$type"].mgl_filter; - mbgl::style::TypeEqualsFilter expected = { .value = mbgl::FeatureType::Point }; - MGLAssertEqualFilters(actual, expected); - } - - { - auto actual = [NSPredicate predicateWithFormat:@"%K = 67086180", @"$id"].mgl_filter; - mbgl::style::IdentifierEqualsFilter expected = { .value = UINT64_C(67086180) }; - MGLAssertEqualFilters(actual, expected); - } - - { - auto actual = [NSPredicate predicateWithFormat:@"%K = nil", @"$id"].mgl_filter; - mbgl::style::NotHasIdentifierFilter expected; - MGLAssertEqualFilters(actual, expected); - } - - { - auto actual = [NSPredicate predicateWithFormat:@"a = nil"].mgl_filter; - mbgl::style::NotHasFilter expected = { .key = "a" }; - MGLAssertEqualFilters(actual, expected); - } - - { - auto actual = [NSPredicate predicateWithFormat:@"%K != 'Point'", @"$type"].mgl_filter; - mbgl::style::TypeNotEqualsFilter expected = { .value = mbgl::FeatureType::Point }; - MGLAssertEqualFilters(actual, expected); - } - - { - auto actual = [NSPredicate predicateWithFormat:@"%K != 67086180", @"$id"].mgl_filter; - mbgl::style::IdentifierNotEqualsFilter expected = { .value = UINT64_C(67086180) }; - MGLAssertEqualFilters(actual, expected); - } - - { - auto actual = [NSPredicate predicateWithFormat:@"%K != nil", @"$id"].mgl_filter; - mbgl::style::HasIdentifierFilter expected; - 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); - } - - { - auto actual = [NSPredicate predicateWithFormat:@"%K IN {'LineString', 'Polygon'}", @"$type"].mgl_filter; - mbgl::style::TypeInFilter expected = { .values = { mbgl::FeatureType::LineString, mbgl::FeatureType::Polygon } }; - MGLAssertEqualFilters(actual, expected); - } - - { - auto actual = [NSPredicate predicateWithFormat:@"%K IN %@", @"$type", @[@"LineString", @"Polygon"]].mgl_filter; - mbgl::style::TypeInFilter expected = { .values = { mbgl::FeatureType::LineString, mbgl::FeatureType::Polygon } }; - MGLAssertEqualFilters(actual, expected); - } - - { - auto actual = [NSPredicate predicateWithFormat:@"%K IN {67086180, 3709678893, 3352016856, 4189833989}", @"$id"].mgl_filter; - mbgl::style::IdentifierInFilter expected = { .values = { UINT64_C(67086180), UINT64_C(3709678893), UINT64_C(3352016856), UINT64_C(4189833989) } }; - MGLAssertEqualFilters(actual, expected); - } - - { - auto actual = [NSPredicate predicateWithFormat:@"%K IN %@", @"$id", @[@67086180, @3709678893, @3352016856, @4189833989]].mgl_filter; - mbgl::style::IdentifierInFilter expected = { .values = { UINT64_C(67086180), UINT64_C(3709678893), UINT64_C(3352016856), UINT64_C(4189833989) } }; - 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); - } - - { - auto actual = [NSPredicate predicateWithFormat:@"%@ CONTAINS %K", @[@"LineString", @"Polygon"], @"$type"].mgl_filter; - mbgl::style::TypeInFilter expected = { .values = { mbgl::FeatureType::LineString, mbgl::FeatureType::Polygon } }; - MGLAssertEqualFilters(actual, expected); - } - - { - auto actual = [NSPredicate predicateWithFormat:@"{67086180, 3709678893, 3352016856, 4189833989} CONTAINS %K", @"$id"].mgl_filter; - mbgl::style::IdentifierInFilter expected = { .values = { UINT64_C(67086180), UINT64_C(3709678893), UINT64_C(3352016856), UINT64_C(4189833989) } }; - MGLAssertEqualFilters(actual, expected); - } - - { - auto actual = [NSPredicate predicateWithFormat:@"%@ CONTAINS %K", @[@67086180, @3709678893, @3352016856, @4189833989], @"$id"].mgl_filter; - mbgl::style::IdentifierInFilter expected = { .values = { UINT64_C(67086180), UINT64_C(3709678893), UINT64_C(3352016856), UINT64_C(4189833989) } }; - 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 * _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'"]); @@ -351,12 +65,12 @@ - (void)testPredication { mbgl::style::TypeEqualsFilter filter = { .value = mbgl::FeatureType::Unknown }; XCTAssertThrowsSpecificNamed([NSPredicate mgl_predicateWithFilter:filter], NSException, NSInternalInconsistencyException); } - + { 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'"]); @@ -379,32 +93,32 @@ - (void)testPredication { NSPredicate *expected = [NSPredicate predicateWithFormat:@"%K != nil", @"$id"]; XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], expected); } - + { 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 = { @@ -414,7 +128,7 @@ - (void)testPredication { }; XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a BETWEEN {'b', 'z'}"]); } - + { mbgl::style::AllFilter filter = { .filters = { @@ -424,7 +138,7 @@ - (void)testPredication { }; 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); @@ -441,7 +155,7 @@ - (void)testPredication { NSPredicate *expected = [NSPredicate predicateWithFormat:@"%K IN {67086180, 3709678893, 3352016856, 4189833989}", @"$id"]; XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], expected); } - + { 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); @@ -458,12 +172,12 @@ - (void)testPredication { NSPredicate *expected = [NSPredicate predicateWithFormat:@"NOT %K IN {67086180, 3709678893, 3352016856, 4189833989}", @"$id"]; XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], expected); } - + { mbgl::style::AllFilter filter; XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithValue:YES]); } - + { mbgl::style::AllFilter filter = { .filters = { @@ -473,12 +187,12 @@ - (void)testPredication { }; 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 = { @@ -488,12 +202,12 @@ - (void)testPredication { }; 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 = { @@ -505,141 +219,339 @@ - (void)testPredication { } } -- (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:@"1 <= a && 2 >= a" mustRoundTrip:YES]; - [self testSymmetryWithPredicate:[NSPredicate predicateWithFormat:@"a BETWEEN %@", @[@1, @2]] - reversePredicate:[NSPredicate predicateWithFormat:@"1 <= a && 2 >= a"] - 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]; - - // The reverse formats here are a bit backwards because we canonicalize - // a reverse CONTAINS to a forward IN. - [self testSymmetryWithFormat:@"{1, 2} CONTAINS a" reverseFormat:@"{1, 2} CONTAINS a" mustRoundTrip:NO]; - [self testSymmetryWithPredicate:[NSPredicate predicateWithFormat:@"%@ CONTAINS a", @[@1, @2]] - reversePredicate:[NSPredicate predicateWithFormat:@"%@ CONTAINS a", @[@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)testUnsupportedFilterPredicates { + XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"1 == 2"].mgl_filter, NSException, NSInvalidArgumentException); + XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"1 == 1"].mgl_filter, NSException, NSInvalidArgumentException); + XCTAssertThrowsSpecificNamed([NSPredicate predicateWithValue:YES].mgl_filter, NSException, NSInvalidArgumentException); + XCTAssertThrowsSpecificNamed([NSPredicate predicateWithValue:NO].mgl_filter, NSException, NSInvalidArgumentException); + 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 * _Nullable bindings) { + XCTAssertTrue(NO, @"Predicate block should not be evaluated."); + return NO; + }].mgl_filter, NSException, NSInvalidArgumentException); } -- (void)testSymmetryWithPredicate:(NSPredicate *)forwardPredicate reversePredicate:(NSPredicate *)reversePredicate mustRoundTrip:(BOOL)mustRoundTrip { - auto forwardFilter = forwardPredicate.mgl_filter; - NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; - if (mustRoundTrip) { - // A collection of ints may turn into an aggregate of longs, for - // example, so compare formats instead of the predicates themselves. - XCTAssertEqualObjects(forwardPredicate.predicateFormat, forwardPredicateAfter.predicateFormat); +- (void)testComparisonPredicates { + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"x == YES"]; + NSArray *jsonExpression = @[@"==", @[@"get", @"x"], @YES]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; } - - if (reversePredicate) { - auto reverseFilter = reversePredicate.mgl_filter; - NSPredicate *reversePredicateAfter = [NSPredicate mgl_predicateWithFilter:reverseFilter]; - XCTAssertNotEqualObjects(reversePredicate, reversePredicateAfter); - - XCTAssertEqualObjects(forwardPredicateAfter, reversePredicateAfter); + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') < 5"]; + NSArray *jsonExpression = @[@"<", @[@"to-number", @[@"get", @"x"]], @5]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; } -} - -- (void)testComparisonExpressionArray { { - NSArray *expected = @[@"==", @1, @2]; - XCTAssertEqualObjects([NSPredicate predicateWithFormat:@"1 = 2"].mgl_jsonExpressionObject, expected); + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') > 5"]; + NSArray *jsonExpression = @[@">", @[@"to-number", @[@"get", @"x"]], @5]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; } { - NSArray *expected = @[@"all", @[@"<=", @10, @[@"get", @"x"]], @[@"<=", @[@"get", @"x"], @100]]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"x BETWEEN {10, 100}"]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:expected], predicate); + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') <= 5"]; + NSArray *jsonExpression = @[@"<=", @[@"to-number", @[@"get", @"x"]], @5]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; } { - NSArray *expected = @[@"all", @[@">=", @[@"get", @"x"], @10], @[@"<=", @[@"get", @"x"], @100]]; - NSExpression *limits = [NSExpression expressionForAggregate:@[[NSExpression expressionForConstantValue:@10], [NSExpression expressionForConstantValue:@100]]]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"x BETWEEN %@", limits]; - XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:expected], predicate); + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') >= 5"]; + NSArray *jsonExpression = @[@">=", @[@"to-number", @[@"get", @"x"]], @5]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSString') > 'value'"]; + NSArray *jsonExpression = @[@">", @[@"to-string", @[@"get", @"x"]], @"value"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a = 'b'"]; + NSArray *jsonExpression = @[@"==", @[@"get", @"a"], @"b"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$geometryType = 'Point'"]; + NSArray *jsonExpression = @[@"==", @[@"geometry-type"], @"Point"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier = 67086180"]; + NSArray *jsonExpression = @[@"==", @[@"id"], @67086180]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier = nil"]; + NSArray *jsonExpression = @[@"==", @[@"id"], [NSNull null]]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a = nil"]; + NSArray *jsonExpression = @[@"==", @[@"get", @"a"], [NSNull null]]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$geometryType != 'Point'"]; + NSArray *jsonExpression = @[@"!=", @[@"geometry-type"], @"Point"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier != 67086180"]; + NSArray *jsonExpression = @[@"!=", @[@"id"], @67086180]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier != nil"]; + NSArray *jsonExpression = @[@"!=", @[@"id"], [NSNull null]]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a != 'b'"]; + NSArray *jsonExpression = @[@"!=", @[@"get", @"a"], @"b"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a != nil"]; + NSArray *jsonExpression = @[@"!=", @[@"get", @"a"], [NSNull null]]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') < 'b'"]; + NSArray *jsonExpression = @[@"<", @[@"to-string", @[@"get", @"a"]], @"b"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') <= 'b'"]; + NSArray *jsonExpression = @[@"<=", @[@"to-string", @[@"get", @"a"]], @"b"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') > 'b'"]; + NSArray *jsonExpression = @[@">", @[@"to-string", @[@"get", @"a"]], @"b"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') >= 'b'"]; + NSArray *jsonExpression = @[@">=", @[@"to-string", @[@"get", @"a"]], @"b"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') BETWEEN {'b', 'z'}"]; + NSArray *jsonExpression =@[@"all", @[@"<=", @"b", @[@"to-string", @[@"get", @"a"]]], @[@"<=", @[@"to-string", @[@"get", @"a"]], @"z"]]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; } { - NSArray *expected = @[@"all", @[@"<=", @10, @[@"get", @"x"]], @[@"<=", @[@"get", @"x"], @100]]; NSExpression *limits = [NSExpression expressionForAggregate:@[[NSExpression expressionForConstantValue:@10], [NSExpression expressionForConstantValue:@100]]]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"x BETWEEN %@", limits]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:expected], predicate); + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') BETWEEN %@", limits]; + NSArray *jsonExpression = @[@"all", @[@">=", @[@"to-number", @[@"get", @"x"]], @10], @[@"<=", @[@"to-number", @[@"get", @"x"]], @100]]; + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; } { - NSArray *expected = @[@"all", @[@"<=", @10, @[@"get", @"x"]], @[@">=", @100, @[@"get", @"x"]]]; + NSArray *expected = @[@"all", @[@"<=", @10, @[@"to-number", @[@"get", @"x"]]], @[@"<=", @[@"to-number", @[@"get", @"x"]], @100]]; NSExpression *limits = [NSExpression expressionForAggregate:@[[NSExpression expressionForConstantValue:@10], [NSExpression expressionForConstantValue:@100]]]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"x BETWEEN %@", limits]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') BETWEEN %@", limits]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:expected], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:expected] + mustRoundTrip:NO]; } { - NSArray *expected = @[@"all", @[@">=", @[@"get", @"x"], @10], @[@">=", @100, @[@"get", @"x"]]]; + NSArray *expected = @[@"all", @[@"<=", @10, @[@"to-number", @[@"get", @"x"]]], @[@">=", @100, @[@"to-number", @[@"get", @"x"]]]]; NSExpression *limits = [NSExpression expressionForAggregate:@[[NSExpression expressionForConstantValue:@10], [NSExpression expressionForConstantValue:@100]]]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"x BETWEEN %@", limits]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') BETWEEN %@", limits]; XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:expected], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:expected] + mustRoundTrip:NO]; } { - NSArray *expected = @[@"all", @[@"==", @10, @[@"get", @"x"]], @[@"<=", @[@"get", @"x"], @100]]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%@ == x && x <= %@", [NSExpression expressionForConstantValue:@10], [NSExpression expressionForConstantValue:@100]]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + NSArray *expected = @[@"all", @[@">=", @[@"to-number", @[@"get", @"x"]], @10], @[@">=", @100, @[@"to-number", @[@"get", @"x"]]]]; + NSExpression *limits = [NSExpression expressionForAggregate:@[[NSExpression expressionForConstantValue:@10], [NSExpression expressionForConstantValue:@100]]]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') BETWEEN %@", limits]; XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:expected], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:expected] + mustRoundTrip:NO]; } { NSArray *expected = @[@"match", @[@"id"], @6, @YES, @5, @YES, @4, @YES, @3, @YES, @NO]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier IN { 6, 5, 4, 3}"]; XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"MGL_MATCH($featureIdentifier, 6, YES, 5, YES, 4, YES, 3, YES, NO) == YES"]; - XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:expected], predicateAfter); + NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"MGL_MATCH(CAST($featureIdentifier, 'NSNumber'), 3, YES, 4, YES, 5, YES, 6, YES, NO) == YES"]; + auto forwardFilter = [NSPredicate mgl_predicateWithJSONObject:expected].mgl_filter; + NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; + XCTAssertEqualObjects(predicateAfter, forwardPredicateAfter); } { NSArray *expected = @[@"!", @[@"match", @[@"get", @"x"], @6, @YES, @5, @YES, @4, @YES, @3, @YES, @NO]]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT x IN { 6, 5, 4, 3}"]; XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"NOT MGL_MATCH(x, 6, YES, 5, YES, 4, YES, 3, YES, NO) == YES"]; - XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:expected], predicateAfter); + NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"NOT MGL_MATCH(CAST(x, 'NSNumber'), 3, YES, 4, YES, 5, YES, 6, YES, NO) == YES"]; + auto forwardFilter = [NSPredicate mgl_predicateWithJSONObject:expected].mgl_filter; + NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; + XCTAssertEqualObjects(predicateAfter, forwardPredicateAfter); + } + { + NSArray *expected = @[@"match", @[@"get", @"a"], @"b", @YES, @"c", @YES, @NO]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a IN { 'b', 'c' }"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"MGL_MATCH(CAST(a, 'NSString'), 'b', YES, 'c', YES, NO) == YES"]; + auto forwardFilter = [NSPredicate mgl_predicateWithJSONObject:expected].mgl_filter; + NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; + XCTAssertEqualObjects(predicateAfter, forwardPredicateAfter); + } + { + NSArray *expected = @[@"match", @[@"geometry-type"], @"LineString", @YES, @"Polygon", @YES, @NO]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%@ IN %@", [NSExpression expressionForVariable:@"geometryType"], @[@"LineString", @"Polygon"]]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"MGL_MATCH($geometryType, 'LineString', YES, 'Polygon', YES, NO) == YES"]; + auto forwardFilter = [NSPredicate mgl_predicateWithJSONObject:expected].mgl_filter; + NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; + XCTAssertEqualObjects(predicateAfter, forwardPredicateAfter); } { NSArray *expected = @[@"match", @[@"get", @"x"], @6, @YES, @5, @YES, @4, @YES, @3, @YES, @NO]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"{ 6, 5, 4, 3} CONTAINS x"]; XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"MGL_MATCH(x, 6, YES, 5, YES, 4, YES, 3, YES, NO) == YES"]; - XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:expected], predicateAfter); + NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"MGL_MATCH(CAST(x, 'NSNumber'), 3, YES, 4, YES, 5, YES, 6, YES, NO) == YES"]; + auto forwardFilter = [NSPredicate mgl_predicateWithJSONObject:expected].mgl_filter; + NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; + XCTAssertEqualObjects(predicateAfter, forwardPredicateAfter); + } + { + NSArray *expected = @[@"match", @[@"geometry-type"], @"LineString", @YES, @"Polygon", @YES, @NO]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%@ CONTAINS %@", @[@"LineString", @"Polygon"], [NSExpression expressionForVariable:@"geometryType"]]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"MGL_MATCH($geometryType, 'LineString', YES, 'Polygon', YES, NO) == YES"]; + auto forwardFilter = [NSPredicate mgl_predicateWithJSONObject:expected].mgl_filter; + NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; + XCTAssertEqualObjects(predicateAfter, forwardPredicateAfter); } { NSArray *expected = @[@"match", @[@"id"], @6, @YES, @5, @YES, @4, @YES, @3, @YES, @NO]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"{ 6, 5, 4, 3} CONTAINS $featureIdentifier"]; XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"MGL_MATCH($featureIdentifier, 6, YES, 5, YES, 4, YES, 3, YES, NO) == YES"]; - XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:expected], predicateAfter); + NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"MGL_MATCH(CAST($featureIdentifier, 'NSNumber'), 3, YES, 4, YES, 5, YES, 6, YES, NO) == YES"]; + auto forwardFilter = [NSPredicate mgl_predicateWithJSONObject:expected].mgl_filter; + NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; + XCTAssertEqualObjects(predicateAfter, forwardPredicateAfter); + } +} + +- (void)testCompoundPredicates { + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a == 'b' AND c == 'd'"]; + NSArray *jsonExpression = @[@"all", @[@"==", @[@"get", @"a"], @"b"], @[@"==", @[@"get", @"c"], @"d"]]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a == 'b' OR c == 'd'"]; + NSArray *jsonExpression = @[@"any", @[@"==", @[@"get", @"a"], @"b"], @[@"==", @[@"get", @"c"], @"d"]]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT(a == 'b' AND c == 'd')"]; + NSArray *jsonExpression = @[@"!", @[@"all", @[@"==", @[@"get", @"a"], @"b"], @[@"==", @[@"get", @"c"], @"d"]]]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT(a == 'b' OR c == 'd')"]; + NSArray *jsonExpression = @[@"!", @[@"any", @[@"==", @[@"get", @"a"], @"b"], @[@"==", @[@"get", @"c"], @"d"]]]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT a == nil"]; + NSArray *jsonExpression = @[@"!", @[@"==", @[@"get", @"a"], [NSNull null]]]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT a != nil"]; + NSArray *jsonExpression = @[@"!", @[@"!=", @[@"get", @"a"], [NSNull null]]]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate + mustRoundTrip:NO]; + } +} + +- (void)testSymmetryWithPredicate:(NSPredicate *)forwardPredicate mustRoundTrip:(BOOL)mustRoundTrip { + auto forwardFilter = forwardPredicate.mgl_filter; + NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; + if (mustRoundTrip) { + // A collection of ints may turn into an aggregate of longs, for + // example, so compare formats instead of the predicates themselves. + XCTAssertEqualObjects(forwardPredicate.predicateFormat, forwardPredicateAfter.predicateFormat); + } else { + XCTAssertEqualObjects(forwardPredicate, forwardPredicateAfter); } } diff --git a/platform/darwin/test/MGLStyleLayerTests.mm.ejs b/platform/darwin/test/MGLStyleLayerTests.mm.ejs index aa62095b095..f70f0bba23a 100644 --- a/platform/darwin/test/MGLStyleLayerTests.mm.ejs +++ b/platform/darwin/test/MGLStyleLayerTests.mm.ejs @@ -36,8 +36,8 @@ XCTAssertNil(layer.sourceLayerIdentifier); XCTAssertNil(layer.predicate); - layer.predicate = [NSPredicate predicateWithValue:NO]; - XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithValue:NO]); + layer.predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier = 1"]; + XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithFormat:@"$featureIdentifier = 1"]); layer.predicate = nil; XCTAssertNil(layer.predicate); } diff --git a/platform/darwin/test/MGLSymbolStyleLayerTests.mm b/platform/darwin/test/MGLSymbolStyleLayerTests.mm index 1d4be119f2d..cf2f80125a6 100644 --- a/platform/darwin/test/MGLSymbolStyleLayerTests.mm +++ b/platform/darwin/test/MGLSymbolStyleLayerTests.mm @@ -30,8 +30,8 @@ - (void)testPredicates { XCTAssertNil(layer.sourceLayerIdentifier); XCTAssertNil(layer.predicate); - layer.predicate = [NSPredicate predicateWithValue:NO]; - XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithValue:NO]); + layer.predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier = 1"]; + XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithFormat:@"$featureIdentifier = 1"]); layer.predicate = nil; XCTAssertNil(layer.predicate); } diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 64df3dd0135..6eec7c0e48c 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -22,6 +22,8 @@ The 4.0._x_ series of releases will be the last to support iOS 8. The minimum iO * The `MGLSymbolStyleLayer.textFontNames` property can now depend on a feature’s attributes. ([#10850](https://github.com/mapbox/mapbox-gl-native/pull/10850)) * Added `MGLShapeSourceOptionWrapsCoordinates`, to specify whether the shape of an `MGLComputedShapeSource` should be wrapped to accommodate coordinates with longitudes beyond −180 and 180; and `MGLShapeSourceOptionClipsCoordinates`, to specify whether the shape of an `MGLComputedShapeSource` should be clipped at the edge of each tile. ([#11041](https://github.com/mapbox/mapbox-gl-native/pull/11041)) * Added support for Mapzen Terrarium DEM encoding. ([#11274](https://github.com/mapbox/mapbox-gl-native/pull/11274)) +* The layer filtering predicate's format strings now can contain arithmetic and calls to built-in `NSExpression` functions. ([#11587](https://github.com/mapbox/mapbox-gl-native/pull/11587)) +* The layer filtering predicate's key paths now may need to be cast to `NSString` or `NSNumber`. ([#11587](https://github.com/mapbox/mapbox-gl-native/pull/11587)) ### Map rendering diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md index 321c018dc69..9df33211af6 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -12,6 +12,8 @@ * Fixed incorrect color calibration on macOS 10.13 High Sierra when using color-related methods of `MGLStyleLayer` subclasses, as well as when displaying an `MGLAttributionInfo`. It is no longer necessary to explicitly convert an `NSColor` to the sRGB color space before using these classes on High Sierra. ([#11391](https://github.com/mapbox/mapbox-gl-native/pull/11391)) * The `MGLSymbolStyleLayer.textFontNames` property can now depend on a feature’s attributes. ([#10850](https://github.com/mapbox/mapbox-gl-native/pull/10850)) * Properties such as `MGLSymbolStyleLayer.iconAllowsOverlap` and `MGLSymbolStyleLayer.iconIgnoresPlacement` now account for symbols in other sources. ([#10436](https://github.com/mapbox/mapbox-gl-native/pull/10436)) +* The layer filtering predicate's format strings now can contain arithmetic and calls to built-in `NSExpression` functions. ([#11587](https://github.com/mapbox/mapbox-gl-native/pull/11587)) +* The layer filtering predicate's key paths now may need to be cast to `NSString` or `NSNumber`. ([#11587](https://github.com/mapbox/mapbox-gl-native/pull/11587)) ### Map rendering