Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Commit

Permalink
[ios, macos] NSPredicate expression filters. (#11587)
Browse files Browse the repository at this point in the history
* [ios, macos] Add NSAndPredicateType predicate expression filter.

* [ios, macos] Add type to NSPredicate expressions.

* [ios, macos] Add Expression filter support.

* [ios, macos] Update NSPredicate expression based tests.

* [ios, macos] Refactor ExpressionFilters.

* [ios, macos] Add symmetric test to ExpressionFilters.

* [ios, macos] Update NSPredicate test to ExpressionFilters.

* [ios, macos] Re-introduce Filter tests.

* [ios, macos] Remove typed NSComparisonPredicate's comparable values.

* [ios, macos] Update style layers predicate tests.

* [ios, macos] Remove unused predicate conversion code.

* [ios, macos] Update documentation exaple's test.

* [ios, macos] Update Predicate and Expressions guide..

* [ios, macos] Remove mgl_ prefix from variable expressions.

* [ios, macos] Update predicates and expressions documentation.

* [ios, macos] Update changelogs.
  • Loading branch information
fabian-guerra authored Apr 16, 2018
1 parent fe4348e commit 62dd097
Show file tree
Hide file tree
Showing 17 changed files with 361 additions and 719 deletions.
26 changes: 14 additions & 12 deletions platform/darwin/docs/guides/Predicates and Expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ The following comparison operators are supported:
`NSNotEqualToPredicateOperatorType` | `key != value`<br />`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
Expand All @@ -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:

<table>
<thead>
Expand Down Expand Up @@ -101,12 +107,8 @@ attribute or, alternatively, one of the following special attributes:
</tbody>
</table>

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
Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion platform/darwin/src/MGLVectorStyleLayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
```
*/
Expand Down
223 changes: 2 additions & 221 deletions platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm
Original file line number Diff line number Diff line change
@@ -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<mbgl::NullValue>()) {
return mbgl::style::NotHasIdentifierFilter();
}

mbgl::style::IdentifierEqualsFilter idEqFilter;
idEqFilter.value = self.mgl_featureIdentifier;
return idEqFilter;
}

// Convert == nil to NotHasFilter.
if (eqFilter.value.is<mbgl::NullValue>()) {
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<mbgl::NullValue>()) {
return mbgl::style::HasIdentifierFilter();
}

mbgl::style::IdentifierNotEqualsFilter idNeFilter;
idNeFilter.value = self.mgl_featureIdentifier;
return idNeFilter;
}

// Convert != nil to HasFilter.
if (neFilter.value.is<mbgl::NullValue>()) {
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;
Expand Down
60 changes: 4 additions & 56 deletions platform/darwin/src/NSCompoundPredicate+MGLAdditions.mm
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
#import "NSCompoundPredicate+MGLAdditions.h"

#import "MGLStyleValue_Private.h"

#import "NSPredicate+MGLAdditions.h"
#import "NSExpression+MGLPrivateAdditions.h"

#include <mbgl/style/conversion/property_value.hpp>

@implementation NSCompoundPredicate (MGLAdditions)

- (std::vector<mbgl::style::Filter>)mgl_subfilters
Expand All @@ -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<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;
}

// 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: {
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)
Expand Down
2 changes: 1 addition & 1 deletion platform/darwin/src/NSExpression+MGLAdditions.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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"]) {
Expand Down
Loading

0 comments on commit 62dd097

Please sign in to comment.