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

[ios, macos] NSPredicate expression filters. #11587

Merged
merged 16 commits into from
Apr 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This illustrates the fact that many existing uses of predicates in v3.x will need to be modified slightly when migrating to v4.0.0. On balance, I think this is necessary until mapbox/mapbox-gl-js#6459, but it does mean we’ll need to be clear about this requirement in the documentation, particularly the function-to-expression migration guide and the “Predicates and Expressions” guide.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, the iOS and macOS changelogs should have a blurb noting that:

  • Predicate format strings can contain arithmetic and calls to built-in NSExpression functions.
  • Key paths may need to be cast to NSString or NSNumber in some cases.

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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably shouldn’t drop this conversion code until #11610 lands.

/cc @lucaswoj

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My mistake: we only need to keep FilterEvaluator around, since that’s responsible for converting mbgl::Filters to NSPredicates as part of the predicate property’s getter. -mgl_filter, meanwhile, is part of the setter, which is already dead code, so we can remove it.

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