From bc6f434a88298336a8b712579e77dbfc189db396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Wed, 26 Feb 2020 12:23:32 -0800 Subject: [PATCH] [ios, macos] Removed in expression workaround MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed the match-based in expression workaround in favor of real support for an in expression operator. Avoid interpreting “literal IN literal” predicates as legacy filters. --- .../src/NSComparisonPredicate+MGLAdditions.mm | 26 +++-- .../darwin/src/NSPredicate+MGLAdditions.mm | 1 + platform/darwin/test/MGLPredicateTests.mm | 108 +++++++++++------- platform/ios/CHANGELOG.md | 2 +- platform/macos/CHANGELOG.md | 2 +- 5 files changed, 89 insertions(+), 50 deletions(-) diff --git a/platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm b/platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm index af9216f9c..2461fcef0 100644 --- a/platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm +++ b/platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm @@ -115,14 +115,24 @@ return @[op, leftHandPredicate.mgl_jsonExpressionObject, rightHandPredicate.mgl_jsonExpressionObject]; } case NSInPredicateOperatorType: { - - NSExpression *matchExpression = [NSExpression expressionForFunction:@"MGL_MATCH" - arguments:@[self.leftExpression, - self.rightExpression, - [NSExpression expressionForConstantValue:@YES], - [NSExpression expressionForConstantValue:@NO]]]; - - return matchExpression.mgl_jsonExpressionObject; + // An “in” expression comparing two string literals is unfortunately + // misinterpreted as a legacy “in” filter due to ambiguity. Wrap one + // argument in a “literal” expression to force an expression. + // https://github.com/mapbox/mapbox-gl-js/issues/9373#issuecomment-594537077 + if (self.leftExpression.expressionType == NSConstantValueExpressionType && + self.rightExpression.expressionType == NSConstantValueExpressionType && + [self.leftExpression.constantValue isKindOfClass:[NSString class]] && + [self.rightExpression.constantValue isKindOfClass:[NSString class]]) { + NSExpression *rightLiteralExpression = [NSExpression expressionWithFormat:@"MGL_FUNCTION('literal', %@)", self.rightExpression]; + NSPredicate *literalPredicate = [NSComparisonPredicate predicateWithLeftExpression:self.leftExpression + rightExpression:rightLiteralExpression + modifier:NSDirectPredicateModifier + type:NSInPredicateOperatorType + options:self.options]; + return literalPredicate.mgl_jsonExpressionObject; + } + op = @"in"; + break; } case NSContainsPredicateOperatorType: { NSPredicate *inPredicate = [NSComparisonPredicate predicateWithLeftExpression:self.rightExpression diff --git a/platform/darwin/src/NSPredicate+MGLAdditions.mm b/platform/darwin/src/NSPredicate+MGLAdditions.mm index 436402c9e..28cc6c6f0 100644 --- a/platform/darwin/src/NSPredicate+MGLAdditions.mm +++ b/platform/darwin/src/NSPredicate+MGLAdditions.mm @@ -54,6 +54,7 @@ static NSDictionary * const MGLPredicateOperatorTypesByJSONOperator = @{ @"<=": @(NSLessThanOrEqualToPredicateOperatorType), @">": @(NSGreaterThanPredicateOperatorType), @">=": @(NSGreaterThanOrEqualToPredicateOperatorType), + @"in": @(NSInPredicateOperatorType), }; + (instancetype)predicateWithMGLJSONObject:(id)object { diff --git a/platform/darwin/test/MGLPredicateTests.mm b/platform/darwin/test/MGLPredicateTests.mm index 2ed18d48f..b39ec2c57 100644 --- a/platform/darwin/test/MGLPredicateTests.mm +++ b/platform/darwin/test/MGLPredicateTests.mm @@ -216,76 +216,104 @@ mustRoundTrip:NO]; } { - NSArray *expected = @[@"match", @[@"id"], @[@6, @5, @4, @3], @YES, @NO]; + NSArray *expected = @[@"in", @[@"id"], @[@"literal", @[@6, @5, @4, @3]]]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier IN { 6, 5, 4, 3}"]; XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"MGL_MATCH($featureIdentifier, { 3, 4, 5, 6 }, YES, NO) == YES"]; - auto forwardFilter = [NSPredicate predicateWithMGLJSONObject:expected].mgl_filter; - NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; - XCTAssertEqualObjects(predicateAfter, forwardPredicateAfter); + XCTAssertEqualObjects([NSPredicate predicateWithMGLJSONObject:expected], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMGLJSONObject:expected] + mustRoundTrip:YES]; } { - NSArray *expected = @[@"match", @[@"to-number", @[@"id"]], @[@3002970001, @3004140052, @3002950027, @3002970033], @YES, @NO]; + NSArray *expected = @[@"in", @[@"to-number", @[@"id"]], @[@"literal", @[@3002970001, @3004140052, @3002950027, @3002970033]]]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST($featureIdentifier, 'NSNumber') IN { 3002970001, 3004140052, 3002950027, 3002970033 }"]; XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"MGL_MATCH(CAST($featureIdentifier, 'NSNumber'), { 3002950027, 3002970001, 3002970033, 3004140052 }, YES, NO) == YES"]; - auto forwardFilter = [NSPredicate predicateWithMGLJSONObject:expected].mgl_filter; - NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; - XCTAssertEqualObjects(predicateAfter, forwardPredicateAfter); + XCTAssertEqualObjects([NSPredicate predicateWithMGLJSONObject:expected], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMGLJSONObject:expected] + mustRoundTrip:YES]; } { - NSArray *expected = @[@"!", @[@"match", @[@"get", @"x"], @[@6, @5, @4, @3], @YES, @NO]]; + NSArray *expected = @[@"!", @[@"in", @[@"get", @"x"], @[@"literal", @[@6, @5, @4, @3]]]]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT x IN { 6, 5, 4, 3}"]; XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"NOT MGL_MATCH(x, { 3, 4, 5, 6 }, YES, NO) == YES"]; - auto forwardFilter = [NSPredicate predicateWithMGLJSONObject:expected].mgl_filter; - NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; - XCTAssertEqualObjects(predicateAfter, forwardPredicateAfter); + XCTAssertEqualObjects([NSPredicate predicateWithMGLJSONObject:expected], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMGLJSONObject:expected] + mustRoundTrip:YES]; } { - NSArray *expected = @[@"match", @[@"get", @"a"], @[@"b", @"c"], @YES, @NO]; + NSArray *expected = @[@"in", @[@"get", @"a"], @[@"literal", @[@"b", @"c"]]]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a IN { 'b', 'c' }"]; XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"MGL_MATCH(a, { 'b', 'c' }, YES, NO) == YES"]; - auto forwardFilter = [NSPredicate predicateWithMGLJSONObject:expected].mgl_filter; - NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; - XCTAssertEqualObjects(predicateAfter, forwardPredicateAfter); + XCTAssertEqualObjects([NSPredicate predicateWithMGLJSONObject:expected], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMGLJSONObject:expected] + mustRoundTrip:YES]; } { - NSArray *expected = @[@"match", @[@"geometry-type"], @[@"LineString", @"Polygon"], @YES, @NO]; + NSArray *expected = @[@"in", @[@"geometry-type"], @[@"literal", @[@"LineString", @"Polygon"]]]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%@ IN %@", [NSExpression expressionForVariable:@"geometryType"], @[@"LineString", @"Polygon"]]; XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"MGL_MATCH($geometryType, { 'LineString', 'Polygon' }, YES, NO) == YES"]; - auto forwardFilter = [NSPredicate predicateWithMGLJSONObject:expected].mgl_filter; - NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; - XCTAssertEqualObjects(predicateAfter, forwardPredicateAfter); + NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"%@ IN {'LineString', 'Polygon'}", [NSExpression expressionForVariable:@"geometryType"]]; + XCTAssertEqualObjects([NSPredicate predicateWithMGLJSONObject:expected], predicateAfter); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMGLJSONObject:expected] + mustRoundTrip:YES]; } { - NSArray *expected = @[@"match", @[@"get", @"x"], @[@6, @5, @4, @3], @YES, @NO]; + NSArray *expected = @[@"in", @[@"get", @"x"], @[@"literal", @[@6, @5, @4, @3]]]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"{ 6, 5, 4, 3 } CONTAINS x"]; XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"MGL_MATCH(x, { 3, 4, 5, 6 }, YES, NO) == YES"]; - auto forwardFilter = [NSPredicate predicateWithMGLJSONObject:expected].mgl_filter; - NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; - XCTAssertEqualObjects(predicateAfter, forwardPredicateAfter); + NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"x IN { 6, 5, 4, 3 }"]; + XCTAssertEqualObjects([NSPredicate predicateWithMGLJSONObject:expected], predicateAfter); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMGLJSONObject:expected] + mustRoundTrip:YES]; } { - NSArray *expected = @[@"match", @[@"geometry-type"], @[@"LineString", @"Polygon"], @YES, @NO]; + NSArray *expected = @[@"in", @[@"geometry-type"], @[@"literal", @[@"LineString", @"Polygon"]]]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%@ CONTAINS %@", @[@"LineString", @"Polygon"], [NSExpression expressionForVariable:@"geometryType"]]; XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"MGL_MATCH($geometryType, { 'LineString', 'Polygon' }, YES, NO) == YES"]; - auto forwardFilter = [NSPredicate predicateWithMGLJSONObject:expected].mgl_filter; - NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; - XCTAssertEqualObjects(predicateAfter, forwardPredicateAfter); + NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"%@ IN {'LineString', 'Polygon'}", [NSExpression expressionForVariable:@"geometryType"]]; + XCTAssertEqualObjects([NSPredicate predicateWithMGLJSONObject:expected], predicateAfter); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMGLJSONObject:expected] + mustRoundTrip:YES]; } { - NSArray *expected = @[@"match", @[@"id"], @[@6, @5, @4, @3], @YES, @NO]; + NSArray *expected = @[@"in", @[@"id"], @[@"literal", @[@6, @5, @4, @3]]]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"{ 6, 5, 4, 3} CONTAINS $featureIdentifier"]; XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"MGL_MATCH($featureIdentifier, { 3, 4, 5, 6 }, YES, NO) == YES"]; - auto forwardFilter = [NSPredicate predicateWithMGLJSONObject:expected].mgl_filter; - NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; - XCTAssertEqualObjects(predicateAfter, forwardPredicateAfter); + NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"$featureIdentifier IN {6, 5, 4, 3}"]; + XCTAssertEqualObjects([NSPredicate predicateWithMGLJSONObject:expected], predicateAfter); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMGLJSONObject:expected] + mustRoundTrip:YES]; + } + { + NSArray *expected = @[@"in", @"📍", @[@"get", @"name"]]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"'📍' IN name"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + XCTAssertEqualObjects([NSPredicate predicateWithMGLJSONObject:expected], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMGLJSONObject:expected] + mustRoundTrip:YES]; + } + { + NSArray *expected = @[@"in", @"📍", @[@"get", @"name"]]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name CONTAINS '📍'"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"'📍' IN name"]; + XCTAssertEqualObjects([NSPredicate predicateWithMGLJSONObject:expected], predicateAfter); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMGLJSONObject:expected] + mustRoundTrip:YES]; + } + { + NSArray *expected = @[@"in", @"📍", @[@"literal", @"Pinole"]]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"'📍' IN 'Pinole'"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + XCTAssertEqualObjects([NSPredicate predicateWithMGLJSONObject:expected], predicate); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:predicate.mgl_filter], [NSPredicate predicateWithValue:NO]); + } + { + NSArray *expected = @[@"in", @"📍", @[@"literal", @"Pinole"]]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"'Pinole' CONTAINS '📍'"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"'📍' IN 'Pinole'"]; + XCTAssertEqualObjects([NSPredicate predicateWithMGLJSONObject:expected], predicateAfter); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:predicate.mgl_filter], [NSPredicate predicateWithValue:NO]); } } diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 0ccab01f7..4b15a4412 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -6,7 +6,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT ### Styles and rendering -* Added the `in` expression function for testing whether a value is included in an array expression or whether a string is a substring of another string. Use this function in expressions in style JSON or with the `MGL_FUNCTION()` syntax in an `NSExpression` format string. ([#16162](https://github.com/mapbox/mapbox-gl-native/pull/16162)) +* The `IN` and `CONTAINS` predicate operators can now test whether a string is a substring of another string. ([#16162](https://github.com/mapbox/mapbox-gl-native/pull/16162), [#183](https://github.com/mapbox/mapbox-gl-native-ios/pull/183)) * Added the `within` expression function for testing whether the evaluated feature lies within the given GeoJSON object. Use this function in expressions in style JSON or with the `MGL_FUNCTION()` syntax in an `NSExpression` format string. ([#16157](https://github.com/mapbox/mapbox-gl-native/pull/16157)) * Added the `MGLLineStyleLayer.lineSortKey` and `MGLFillStyleLayer.fillSortKey` properties. ([#179](https://github.com/mapbox/mapbox-gl-native-ios/pull/179), [#16194](https://github.com/mapbox/mapbox-gl-native/pull/16194), [#16220](https://github.com/mapbox/mapbox-gl-native/pull/16220)) * The `MGLSymbolStyleLayer.iconTextFit` property now respects the cap insets of any [nine-part stretchable image](https://developer.apple.com/documentation/uikit/uiimage#1658362) passed into the `-[MGLStyle setImage:forName:]` method. You can define the stretchable area in Xcode’s asset catalog or by calling the `-[UIImage resizableImageWithCapInsets:]` method. ([#182](https://github.com/mapbox/mapbox-gl-native-ios/pull/182)) diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md index 18b5a7c42..e9eb05897 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -6,7 +6,7 @@ * Added the `-[MGLMapViewDelegate mapView:shouldRemoveStyleImage:]` method for optimizing style image caching. ([#14769](https://github.com/mapbox/mapbox-gl-native/pull/14769)) * Added the `image` expression function for converting an image name into a style image. Use this function in expressions in style JSON or with the `MGL_FUNCTION()` syntax in an `NSExpression` format string. Image expressions are compatible with the `mgl_attributed:` expression function and `MGLAttributedExpression` classes for embedding icons inline in text labels. ([#15877](https://github.com/mapbox/mapbox-gl-native/pull/15877), [#15937](https://github.com/mapbox/mapbox-gl-native/pull/15937)) -* Added the `in` expression function for testing whether a value is included in an array expression or whether a string is a substring of another string. Use this function in expressions in style JSON or with the `MGL_FUNCTION()` syntax in an `NSExpression` format string. ([#16162](https://github.com/mapbox/mapbox-gl-native/pull/16162)) +* The `IN` and `CONTAINS` predicate operators can now test whether a string is a substring of another string. ([#16162](https://github.com/mapbox/mapbox-gl-native/pull/16162), [#183](https://github.com/mapbox/mapbox-gl-native-ios/pull/183)) * Added the `within` expression function for testing whether the evaluated feature lies within the given GeoJSON object. Use this function in expressions in style JSON or with the `MGL_FUNCTION()` syntax in an `NSExpression` format string. ([#16157](https://github.com/mapbox/mapbox-gl-native/pull/16157), [#16194](https://github.com/mapbox/mapbox-gl-native/pull/16194), [#16220](https://github.com/mapbox/mapbox-gl-native/pull/16220)) * Added the `MGLSymbolStyleLayer.textWritingModes` layout property. This property can be set to `MGLTextWritingModeHorizontal` or `MGLTextWritingModeVertical`. ([#14932](https://github.com/mapbox/mapbox-gl-native/pull/14932)) * Added the `MGLLineStyleLayer.lineSortKey` and `MGLFillStyleLayer.fillSortKey` properties. ([#179](https://github.com/mapbox/mapbox-gl-native-ios/pull/179))