diff options
author | Michael Lehenbauer <mikelehen@gmail.com> | 2018-04-23 10:03:55 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-04-23 10:03:55 -0700 |
commit | e4384c3e809556e75907df74cd116307f397472f (patch) | |
tree | 98841eba1d45eb94adcb798b6f792c16ba4b8d18 | |
parent | 8e70791465f9b5e4bb75dfc18de4a4ed90961974 (diff) |
Array Contains Queries (not added to public headers yet). (#1138)
-rw-r--r-- | Firestore/Example/SwiftBuildTest/main.swift | 3 | ||||
-rw-r--r-- | Firestore/Example/Tests/Core/FSTQueryTests.mm | 49 | ||||
-rw-r--r-- | Firestore/Example/Tests/Integration/API/FIRQueryTests.mm | 45 | ||||
-rw-r--r-- | Firestore/Example/Tests/Integration/API/FIRValidationTests.mm | 22 | ||||
-rw-r--r-- | Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm | 24 | ||||
-rw-r--r-- | Firestore/Example/Tests/Util/FSTHelpers.h | 2 | ||||
-rw-r--r-- | Firestore/Example/Tests/Util/FSTHelpers.mm | 2 | ||||
-rw-r--r-- | Firestore/Source/API/FIRQuery+Internal.h | 35 | ||||
-rw-r--r-- | Firestore/Source/API/FIRQuery.mm | 16 | ||||
-rw-r--r-- | Firestore/Source/Core/FSTQuery.h | 1 | ||||
-rw-r--r-- | Firestore/Source/Core/FSTQuery.mm | 25 | ||||
-rw-r--r-- | Firestore/Source/Remote/FSTSerializerBeta.mm | 4 |
12 files changed, 217 insertions, 11 deletions
diff --git a/Firestore/Example/SwiftBuildTest/main.swift b/Firestore/Example/SwiftBuildTest/main.swift index f04de5b..c5034da 100644 --- a/Firestore/Example/SwiftBuildTest/main.swift +++ b/Firestore/Example/SwiftBuildTest/main.swift @@ -83,6 +83,9 @@ func makeRefs(database db: Firestore) -> (CollectionReference, DocumentReference func makeQuery(collection collectionRef: CollectionReference) -> Query { let query = collectionRef.whereField(FieldPath(["name"]), isEqualTo: "Fred") .whereField("age", isGreaterThanOrEqualTo: 24) + // TODO(array-features): Uncomment when API is publicly exposed. + // .whereField("tags", arrayContains:"active") + // .whereField(FieldPath(["tags"]), arrayContains:"active") .whereField(FieldPath.documentID(), isEqualTo: "fred") .order(by: FieldPath(["age"])) .order(by: "name", descending: true) diff --git a/Firestore/Example/Tests/Core/FSTQueryTests.mm b/Firestore/Example/Tests/Core/FSTQueryTests.mm index 02310aa..6118d0f 100644 --- a/Firestore/Example/Tests/Core/FSTQueryTests.mm +++ b/Firestore/Example/Tests/Core/FSTQueryTests.mm @@ -151,6 +151,55 @@ NS_ASSUME_NONNULL_BEGIN XCTAssertFalse([query2 matchesDocument:doc6]); } +- (void)testArrayContainsFilter { + FSTQuery *query = [FSTTestQuery("collection") + queryByAddingFilter:FSTTestFilter("array", @"array_contains", @42)]; + + // not an array. + FSTDocument *doc = FSTTestDoc("collection/1", 0, @{ @"array" : @1 }, NO); + XCTAssertFalse([query matchesDocument:doc]); + + // empty array. + doc = FSTTestDoc("collection/1", 0, @{ @"array" : @[] }, NO); + XCTAssertFalse([query matchesDocument:doc]); + + // array without element (and make sure it doesn't match in a nested field or a different field). + doc = FSTTestDoc( + "collection/1", 0, + @{ @"array" : @[ @41, @"42", + @{ @"a" : @42, + @"b" : @[ @42 ] } ], + @"different" : @[ @42 ] }, + NO); + XCTAssertFalse([query matchesDocument:doc]); + + // array with element. + doc = FSTTestDoc("collection/1", 0, @{ @"array" : @[ @1, @"2", @42, @{ @"a" : @1 } ] }, NO); + XCTAssertTrue([query matchesDocument:doc]); +} + +- (void)testArrayContainsFilterWithObjectValue { + // Search for arrays containing the object { a: [42] } + FSTQuery *query = + [FSTTestQuery("collection") queryByAddingFilter:FSTTestFilter("array", @"array_contains", + @{ @"a" : @[ @42 ] })]; + + // array without element. + FSTDocument *doc = FSTTestDoc( + "collection/1", 0, + @{ @"array" : @[ + @{ @"a" : @42 }, + @{ @"a" : @[ @42, @43 ] }, + @{ @"b" : @[ @42 ] } + ] }, + NO); + XCTAssertFalse([query matchesDocument:doc]); + + // array with element. + doc = FSTTestDoc("collection/1", 0, @{ @"array" : @[ @1, @"2", @42, @{ @"a" : @[ @42 ] } ] }, NO); + XCTAssertTrue([query matchesDocument:doc]); +} + - (void)testNullFilter { FSTQuery *query = [FSTTestQuery("collection") queryByAddingFilter:FSTTestFilter("sort", @"==", [NSNull null])]; diff --git a/Firestore/Example/Tests/Integration/API/FIRQueryTests.mm b/Firestore/Example/Tests/Integration/API/FIRQueryTests.mm index d1c0d75..bdd3df1 100644 --- a/Firestore/Example/Tests/Integration/API/FIRQueryTests.mm +++ b/Firestore/Example/Tests/Integration/API/FIRQueryTests.mm @@ -20,8 +20,7 @@ #import "Firestore/Example/Tests/Util/FSTEventAccumulator.h" #import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h" -#import "Firestore/Source/API/FIRFirestore+Internal.h" -#import "Firestore/Source/Core/FSTFirestoreClient.h" +#import "Firestore/Source/API/FIRQuery+Internal.h" @interface FIRQueryTests : FSTIntegrationTestCase @end @@ -294,4 +293,46 @@ ])); } +// TODO(array-features): Enable once backend support lands. +- (void)xtestArrayContainsQueries { + NSDictionary *testDocs = @{ + @"a" : @{@"array" : @[ @42 ]}, + @"b" : @{@"array" : @[ @"a", @42, @"c" ]}, + @"c" : @{@"array" : @[ @41.999, @"42", + @{ @"a" : @[ @42 ] } ]}, + @"d" : @{@"array" : @[ @42 ], @"array2" : @[ @"bingo" ]} + }; + FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs]; + + // Search for 42 + FIRQuerySnapshot *snapshot = + [self readDocumentSetForRef:[collection queryWhereField:@"array" arrayContains:@42]]; + XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[ + @{ @"array" : @[ @42 ] }, + @{ @"array" : @[ @"a", @42, @"c" ] }, + @{ @"array" : @[ @42 ], + @"array2" : @[ @"bingo" ] } + ])); + + // Search for "array" to contain both @42 and "a". + snapshot = [self readDocumentSetForRef:[[collection queryWhereField:@"array" arrayContains:@42] + queryWhereField:@"array" + arrayContains:@"a"]]; + XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[ + @{ @"array" : @[ @"a", @42, @"c" ] }, + ])); + + // Search two different array fields ("array" contains 42 and "array2" contains "bingo"). + snapshot = [self readDocumentSetForRef:[[collection queryWhereField:@"array" arrayContains:@42] + queryWhereField:@"array2" + arrayContains:@"bingo"]]; + XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[ + @{ @"array" : @[ @42 ], + @"array2" : @[ @"bingo" ] } + ])); + + // NOTE: The backend doesn't currently support null, NaN, objects, or arrays, so there isn't much + // of anything else interesting to test. +} + @end diff --git a/Firestore/Example/Tests/Integration/API/FIRValidationTests.mm b/Firestore/Example/Tests/Integration/API/FIRValidationTests.mm index ee0f386..6d10aba 100644 --- a/Firestore/Example/Tests/Integration/API/FIRValidationTests.mm +++ b/Firestore/Example/Tests/Integration/API/FIRValidationTests.mm @@ -19,6 +19,7 @@ #import <XCTest/XCTest.h> #import "Firestore/Source/API/FIRFieldValue+Internal.h" +#import "Firestore/Source/API/FIRQuery+Internal.h" #import "Firestore/Example/Tests/Util/FSTHelpers.h" #import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h" @@ -419,14 +420,20 @@ @"Invalid Query. Query limit (-1) is invalid. Limit must be positive."); } -- (void)testQueryInequalityOnNullOrNaNFails { +- (void)testNonEqualityQueriesOnNullOrNaNFail { FSTAssertThrows([[self collectionRef] queryWhereField:@"a" isGreaterThan:nil], @"Invalid Query. You can only perform equality comparisons on nil / NSNull."); FSTAssertThrows([[self collectionRef] queryWhereField:@"a" isGreaterThan:[NSNull null]], @"Invalid Query. You can only perform equality comparisons on nil / NSNull."); + FSTAssertThrows([[self collectionRef] queryWhereField:@"a" arrayContains:nil], + @"Invalid Query. You can only perform equality comparisons on nil / NSNull."); + FSTAssertThrows([[self collectionRef] queryWhereField:@"a" arrayContains:[NSNull null]], + @"Invalid Query. You can only perform equality comparisons on nil / NSNull."); FSTAssertThrows([[self collectionRef] queryWhereField:@"a" isGreaterThan:@(NAN)], @"Invalid Query. You can only perform equality comparisons on NaN."); + FSTAssertThrows([[self collectionRef] queryWhereField:@"a" arrayContains:@(NAN)], + @"Invalid Query. You can only perform equality comparisons on NaN."); } - (void)testQueryCannotBeCreatedFromDocumentsMissingSortValues { @@ -498,6 +505,12 @@ @"Invalid query. When querying by document ID you must provide a valid string or " "DocumentReference, but it was of type: __NSCFNumber"; FSTAssertThrows([collection queryWhereFieldPath:[FIRFieldPath documentID] isEqualTo:@1], reason); + + reason = + @"Invalid query. You can't do arrayContains queries on document ID since document IDs are " + @"not arrays."; + FSTAssertThrows([collection queryWhereFieldPath:[FIRFieldPath documentID] arrayContains:@1], + reason); } - (void)testQueryInequalityFieldMustMatchFirstOrderByField { @@ -526,6 +539,8 @@ XCTAssertNoThrow([base queryWhereField:@"y" isEqualTo:@"cat"], @"Inequality and equality on different fields works"); + XCTAssertNoThrow([base queryWhereField:@"y" arrayContains:@"cat"], + @"Inequality and array_contains on different fields works"); XCTAssertNoThrow([base queryOrderedByField:@"x"], @"inequality same as order by works"); XCTAssertNoThrow([[coll queryOrderedByField:@"x"] queryWhereField:@"x" isGreaterThan:@32], @@ -535,6 +550,11 @@ XCTAssertNoThrow([[[coll queryOrderedByField:@"x"] queryOrderedByField:@"y"] queryWhereField:@"x" isGreaterThan:@32], @"inequality same as first order by works."); + + XCTAssertNoThrow([[coll queryOrderedByField:@"x"] queryWhereField:@"y" isEqualTo:@"cat"], + @"equality different than orderBy works."); + XCTAssertNoThrow([[coll queryOrderedByField:@"x"] queryWhereField:@"y" arrayContains:@"cat"], + @"array_contains different than orderBy works."); } #pragma mark - GeoPoint Validation diff --git a/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm b/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm index 8d62b4d..bbb3822 100644 --- a/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm +++ b/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm @@ -487,6 +487,18 @@ NS_ASSUME_NONNULL_BEGIN XCTAssertEqualObjects(actual, expected); } +- (void)testEncodesArrayContainsFilter { + FSTRelationFilter *input = FSTTestFilter("item.tags", @"array_contains", @"food"); + GCFSStructuredQuery_Filter *actual = [self.serializer encodedRelationFilter:input]; + + GCFSStructuredQuery_Filter *expected = [GCFSStructuredQuery_Filter message]; + GCFSStructuredQuery_FieldFilter *prop = expected.fieldFilter; + prop.field.fieldPath = @"item.tags"; + prop.op = GCFSStructuredQuery_FieldFilter_Operator_ArrayContains; + prop.value.stringValue = @"food"; + XCTAssertEqualObjects(actual, expected); +} + #pragma mark - encodedQuery - (void)testEncodesFirstLevelKeyQueries { @@ -556,9 +568,10 @@ NS_ASSUME_NONNULL_BEGIN } - (void)testEncodesMultipleFiltersOnDeeperCollections { - FSTQuery *q = [[FSTTestQuery("rooms/1/messages/10/attachments") + FSTQuery *q = [[[FSTTestQuery("rooms/1/messages/10/attachments") queryByAddingFilter:FSTTestFilter("prop", @">=", @(42))] - queryByAddingFilter:FSTTestFilter("author", @"==", @"dimond")]; + queryByAddingFilter:FSTTestFilter("author", @"==", @"dimond")] + queryByAddingFilter:FSTTestFilter("tags", @"array_contains", @"pending")]; FSTQueryData *model = [self queryDataForQuery:q]; GCFSTarget *expected = [GCFSTarget message]; @@ -579,11 +592,18 @@ NS_ASSUME_NONNULL_BEGIN field2.op = GCFSStructuredQuery_FieldFilter_Operator_Equal; field2.value.stringValue = @"dimond"; + GCFSStructuredQuery_Filter *filter3 = [GCFSStructuredQuery_Filter message]; + GCFSStructuredQuery_FieldFilter *field3 = filter3.fieldFilter; + field3.field.fieldPath = @"tags"; + field3.op = GCFSStructuredQuery_FieldFilter_Operator_ArrayContains; + field3.value.stringValue = @"pending"; + GCFSStructuredQuery_CompositeFilter *composite = expected.query.structuredQuery.where.compositeFilter; composite.op = GCFSStructuredQuery_CompositeFilter_Operator_And; [composite.filtersArray addObject:filter1]; [composite.filtersArray addObject:filter2]; + [composite.filtersArray addObject:filter3]; [expected.query.structuredQuery.orderByArray addObject:[GCFSStructuredQuery_Order messageWithProperty:@"prop" ascending:YES]]; diff --git a/Firestore/Example/Tests/Util/FSTHelpers.h b/Firestore/Example/Tests/Util/FSTHelpers.h index 9b5f96a..131da2d 100644 --- a/Firestore/Example/Tests/Util/FSTHelpers.h +++ b/Firestore/Example/Tests/Util/FSTHelpers.h @@ -212,7 +212,7 @@ FSTQuery *FSTTestQuery(const absl::string_view path); /** * A convenience method to create a FSTFilter using a string representation for both field - * and operator (<, <=, ==, >=, >). + * and operator (<, <=, ==, >=, >, array_contains). */ id<FSTFilter> FSTTestFilter(const absl::string_view field, NSString *op, id value); diff --git a/Firestore/Example/Tests/Util/FSTHelpers.mm b/Firestore/Example/Tests/Util/FSTHelpers.mm index e1ed18c..888a45f 100644 --- a/Firestore/Example/Tests/Util/FSTHelpers.mm +++ b/Firestore/Example/Tests/Util/FSTHelpers.mm @@ -210,6 +210,8 @@ id<FSTFilter> FSTTestFilter(const absl::string_view field, NSString *opString, i op = FSTRelationFilterOperatorGreaterThanOrEqual; } else if ([opString isEqualToString:@">"]) { op = FSTRelationFilterOperatorGreaterThan; + } else if ([opString isEqualToString:@"array_contains"]) { + op = FSTRelationFilterOperatorArrayContains; } else { FSTCFail(@"Unsupported operator type: %@", opString); } diff --git a/Firestore/Source/API/FIRQuery+Internal.h b/Firestore/Source/API/FIRQuery+Internal.h index 3c2b2a7..c4f9f23 100644 --- a/Firestore/Source/API/FIRQuery+Internal.h +++ b/Firestore/Source/API/FIRQuery+Internal.h @@ -23,7 +23,42 @@ NS_ASSUME_NONNULL_BEGIN /** Internal FIRQuery API we don't want exposed in our public header files. */ @interface FIRQuery (Internal) + (FIRQuery *)referenceWithQuery:(FSTQuery *)query firestore:(FIRFirestore *)firestore; + @property(nonatomic, strong, readonly) FSTQuery *query; + +@end + +// TODO(array-features): Move to FIRQuery.h once backend support is available. +@interface FIRQuery () + +/** + * Creates and returns a new `FIRQuery` with the additional filter that documents must contain + * the specified field, it must be an array, and the array must contain the provided value. + * + * @param field The name of the field containing an array to search + * @param value The value that must be contained in the array + * + * @return The created `FIRQuery`. + */ +// clang-format off +- (FIRQuery *)queryWhereField:(NSString *)field + arrayContains:(id)value NS_SWIFT_NAME(whereField(_:arrayContains:)); +// clang-format on + +/** + * Creates and returns a new `FIRQuery` with the additional filter that documents must contain + * the specified field, it must be an array, and the array must contain the provided value. + * + * @param path The path of the field containing an array to search + * @param value The value that must be contained in the array + * + * @return The created `FIRQuery`. + */ +// clang-format off +- (FIRQuery *)queryWhereFieldPath:(FIRFieldPath *)path + arrayContains:(id)value NS_SWIFT_NAME(whereField(_:arrayContains:)); +// clang-format on + @end NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/API/FIRQuery.mm b/Firestore/Source/API/FIRQuery.mm index 2d78ac0..32d8327 100644 --- a/Firestore/Source/API/FIRQuery.mm +++ b/Firestore/Source/API/FIRQuery.mm @@ -238,6 +238,17 @@ addSnapshotListenerInternalWithOptions:(FSTListenOptions *)internalOptions value:value]; } +- (FIRQuery *)queryWhereField:(NSString *)field arrayContains:(id)value { + return + [self queryWithFilterOperator:FSTRelationFilterOperatorArrayContains field:field value:value]; +} + +- (FIRQuery *)queryWhereFieldPath:(FIRFieldPath *)path arrayContains:(id)value { + return [self queryWithFilterOperator:FSTRelationFilterOperatorArrayContains + path:path.internalValue + value:value]; +} + - (FIRQuery *)queryWhereField:(NSString *)field isGreaterThanOrEqualTo:(id)value { return [self queryWithFilterOperator:FSTRelationFilterOperatorGreaterThanOrEqual field:field @@ -443,6 +454,11 @@ addSnapshotListenerInternalWithOptions:(FSTListenOptions *)internalOptions value:(id)value { FSTFieldValue *fieldValue; if (fieldPath.IsKeyFieldPath()) { + if (filterOperator == FSTRelationFilterOperatorArrayContains) { + FSTThrowInvalidArgument( + @"Invalid query. You can't do arrayContains queries on document ID since document IDs " + @"are not arrays."); + } if ([value isKindOfClass:[NSString class]]) { NSString *documentKey = (NSString *)value; if ([documentKey containsString:@"/"]) { diff --git a/Firestore/Source/Core/FSTQuery.h b/Firestore/Source/Core/FSTQuery.h index 3a67e7f..572fabb 100644 --- a/Firestore/Source/Core/FSTQuery.h +++ b/Firestore/Source/Core/FSTQuery.h @@ -34,6 +34,7 @@ typedef NS_ENUM(NSInteger, FSTRelationFilterOperator) { FSTRelationFilterOperatorEqual, FSTRelationFilterOperatorGreaterThanOrEqual, FSTRelationFilterOperatorGreaterThan, + FSTRelationFilterOperatorArrayContains, }; /** Interface used for all query filters. */ diff --git a/Firestore/Source/Core/FSTQuery.mm b/Firestore/Source/Core/FSTQuery.mm index 8f49c26..0cd11e8 100644 --- a/Firestore/Source/Core/FSTQuery.mm +++ b/Firestore/Source/Core/FSTQuery.mm @@ -58,6 +58,8 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe return @">="; case FSTRelationFilterOperatorGreaterThan: return @">"; + case FSTRelationFilterOperatorArrayContains: + return @"array_contains"; default: FSTCFail(@"Unknown FSTRelationFilterOperator %lu", (unsigned long)filterOperator); } @@ -119,7 +121,8 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe #pragma mark - Public Methods - (BOOL)isInequality { - return self.filterOperator != FSTRelationFilterOperatorEqual; + return self.filterOperator != FSTRelationFilterOperatorEqual && + self.filterOperator != FSTRelationFilterOperatorArrayContains; } - (const firebase::firestore::model::FieldPath &)field { @@ -150,6 +153,8 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe if (_field.IsKeyFieldPath()) { FSTAssert([self.value isKindOfClass:[FSTReferenceValue class]], @"Comparing on key, but filter value not a FSTReferenceValue."); + FSTAssert(self.filterOperator != FSTRelationFilterOperatorArrayContains, + @"arrayContains queries don't make sense on document keys."); FSTReferenceValue *refValue = (FSTReferenceValue *)self.value; NSComparisonResult comparison = FSTDocumentKeyComparator(document.key, refValue.value); return [self matchesComparison:comparison]; @@ -180,9 +185,19 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe /** Returns YES if receiver is true with the given value as its LHS. */ - (BOOL)matchesValue:(FSTFieldValue *)other { - // Only compare types with matching backend order (such as double and int). - return self.value.typeOrder == other.typeOrder && - [self matchesComparison:[other compare:self.value]]; + if (self.filterOperator == FSTRelationFilterOperatorArrayContains) { + if ([other isMemberOfClass:[FSTArrayValue class]]) { + FSTArrayValue *arrayValue = (FSTArrayValue *)other; + return [arrayValue.internalValue containsObject:self.value]; + } else { + return false; + } + } else { + // Only perform comparison queries on types with matching backend order (such as double and + // int). + return self.value.typeOrder == other.typeOrder && + [self matchesComparison:[other compare:self.value]]; + } } - (BOOL)matchesComparison:(NSComparisonResult)comparison { @@ -701,7 +716,7 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe - (const FieldPath *)inequalityFilterField { for (id<FSTFilter> filter in self.filters) { if ([filter isKindOfClass:[FSTRelationFilter class]] && - ((FSTRelationFilter *)filter).filterOperator != FSTRelationFilterOperatorEqual) { + ((FSTRelationFilter *)filter).isInequality) { return &filter.field; } } diff --git a/Firestore/Source/Remote/FSTSerializerBeta.mm b/Firestore/Source/Remote/FSTSerializerBeta.mm index ebb49a7..5cbfecc 100644 --- a/Firestore/Source/Remote/FSTSerializerBeta.mm +++ b/Firestore/Source/Remote/FSTSerializerBeta.mm @@ -952,6 +952,8 @@ NS_ASSUME_NONNULL_BEGIN return GCFSStructuredQuery_FieldFilter_Operator_GreaterThanOrEqual; case FSTRelationFilterOperatorGreaterThan: return GCFSStructuredQuery_FieldFilter_Operator_GreaterThan; + case FSTRelationFilterOperatorArrayContains: + return GCFSStructuredQuery_FieldFilter_Operator_ArrayContains; default: FSTFail(@"Unhandled FSTRelationFilterOperator: %ld", (long)filterOperator); } @@ -970,6 +972,8 @@ NS_ASSUME_NONNULL_BEGIN return FSTRelationFilterOperatorGreaterThanOrEqual; case GCFSStructuredQuery_FieldFilter_Operator_GreaterThan: return FSTRelationFilterOperatorGreaterThan; + case GCFSStructuredQuery_FieldFilter_Operator_ArrayContains: + return FSTRelationFilterOperatorArrayContains; default: FSTFail(@"Unhandled FieldFilter.operator: %d", filterOperator); } |