From 8a2df52d31ebea436b6daabf17ef9ef945006e24 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Fri, 14 Jul 2023 18:31:18 -0500 Subject: [PATCH 1/2] feat: Support query.containedBy --- .../OfflineQueryLogic/PFOfflineQueryLogic.m | 18 ++++++++++ Parse/Parse/Internal/Query/PFQueryConstants.h | 1 + Parse/Parse/Internal/Query/PFQueryConstants.m | 1 + Parse/Parse/Source/PFQuery.h | 11 ++++++ Parse/Parse/Source/PFQuery.m | 4 +++ Parse/Tests/Unit/OfflineQueryLogicUnitTests.m | 36 +++++++++++++++++++ Parse/Tests/Unit/QueryUnitTests.m | 6 ++++ 7 files changed, 77 insertions(+) diff --git a/Parse/Parse/Internal/LocalDataStore/OfflineQueryLogic/PFOfflineQueryLogic.m b/Parse/Parse/Internal/LocalDataStore/OfflineQueryLogic/PFOfflineQueryLogic.m index 6c4fc743a..8290bec7e 100644 --- a/Parse/Parse/Internal/LocalDataStore/OfflineQueryLogic/PFOfflineQueryLogic.m +++ b/Parse/Parse/Internal/LocalDataStore/OfflineQueryLogic/PFOfflineQueryLogic.m @@ -329,6 +329,22 @@ + (BOOL)matchesValue:(id)value containsAllObjectsInArray:(id)constraints { return YES; } +/** + Matches $containedBy constraints. + */ ++ (BOOL)matchesValue:(id)value + containedBy:(id)constraints { + PFParameterAssert(![constraints isKindOfClass:[NSArray class]], @"Constraint type not supported for $containedBy queries"); + PFParameterAssert(![value isKindOfClass:[NSArray class]], @"Value type not supported for $containedBy queries"); + + for (id constraint in (NSArray *)constraints) { + if (![self matchesValue:value containedIn:constraint]) { + return NO; + } + } + return YES; +} + /** Matches $regex constraints. */ @@ -446,6 +462,8 @@ + (BOOL)matchesValue:(id)value return [self matchesValue:value notContainedIn:constraint]; } else if ([operator isEqualToString:PFQueryKeyContainsAll]) { return [self matchesValue:value containsAllObjectsInArray:constraint]; + } else if ([operator isEqualToString:PFQueryKeyContainedBy]) { + return [self matchesValue:value containedBy:constraint]; } else if ([operator isEqualToString:PFQueryKeyRegex]) { return [self matchesValue:value regex:constraint withOptions:allKeyConstraints[PFQueryOptionKeyRegexOptions]]; } else if ([operator isEqualToString:PFQueryOptionKeyRegexOptions]) { diff --git a/Parse/Parse/Internal/Query/PFQueryConstants.h b/Parse/Parse/Internal/Query/PFQueryConstants.h index 8e7ee2d55..3ba8afb6f 100644 --- a/Parse/Parse/Internal/Query/PFQueryConstants.h +++ b/Parse/Parse/Internal/Query/PFQueryConstants.h @@ -19,6 +19,7 @@ extern NSString *const PFQueryKeyGreaterThanOrEqualTo; extern NSString *const PFQueryKeyContainedIn; extern NSString *const PFQueryKeyNotContainedIn; extern NSString *const PFQueryKeyContainsAll; +extern NSString *const PFQueryKeyContainedBy; extern NSString *const PFQueryKeyNearSphere; extern NSString *const PFQueryKeyWithin; extern NSString *const PFQueryKeyGeoWithin; diff --git a/Parse/Parse/Internal/Query/PFQueryConstants.m b/Parse/Parse/Internal/Query/PFQueryConstants.m index 81297d7af..97ba404cd 100644 --- a/Parse/Parse/Internal/Query/PFQueryConstants.m +++ b/Parse/Parse/Internal/Query/PFQueryConstants.m @@ -17,6 +17,7 @@ NSString *const PFQueryKeyContainedIn = @"$in"; NSString *const PFQueryKeyNotContainedIn = @"$nin"; NSString *const PFQueryKeyContainsAll = @"$all"; +NSString *const PFQueryKeyContainedBy = @"$containedBy"; NSString *const PFQueryKeyNearSphere = @"$nearSphere"; NSString *const PFQueryKeyWithin = @"$within"; NSString *const PFQueryKeyGeoWithin = @"$geoWithin"; diff --git a/Parse/Parse/Source/PFQuery.h b/Parse/Parse/Source/PFQuery.h index 699a80e99..ae844c066 100644 --- a/Parse/Parse/Source/PFQuery.h +++ b/Parse/Parse/Source/PFQuery.h @@ -273,6 +273,17 @@ typedef void (^PFQueryArrayResultBlock)(NSArray *_Nullable obje */ - (instancetype)whereKey:(NSString *)key containsAllObjectsInArray:(NSArray *)array; +/** + Adds a constraint to the query that requires a particular key's value to + be contained by the provided list of values. Get objects where all array elements match + + @param key The key to be constrained. + @param array The array of values to search for. + + @return The same instance of `PFQuery` as the receiver. This allows method chaining. + */ +- (instancetype)whereKey:(NSString *)key containedBy:(NSArray *)array; + ///-------------------------------------- #pragma mark - Adding Location Constraints ///-------------------------------------- diff --git a/Parse/Parse/Source/PFQuery.m b/Parse/Parse/Source/PFQuery.m index bd9455289..105e1d6ea 100644 --- a/Parse/Parse/Source/PFQuery.m +++ b/Parse/Parse/Source/PFQuery.m @@ -303,6 +303,10 @@ - (instancetype)whereKey:(NSString *)key containsAllObjectsInArray:(NSArray *)ar return [self whereKey:key condition:PFQueryKeyContainsAll object:array]; } +- (instancetype)whereKey:(NSString *)key containedBy:(NSArray *)inArray { + return [self whereKey:key condition:PFQueryKeyContainedBy object:inArray]; +} + - (instancetype)whereKey:(NSString *)key nearGeoPoint:(PFGeoPoint *)geopoint { return [self whereKey:key condition:PFQueryKeyNearSphere object:geopoint]; } diff --git a/Parse/Tests/Unit/OfflineQueryLogicUnitTests.m b/Parse/Tests/Unit/OfflineQueryLogicUnitTests.m index 6585305f3..130c8014e 100644 --- a/Parse/Tests/Unit/OfflineQueryLogicUnitTests.m +++ b/Parse/Tests/Unit/OfflineQueryLogicUnitTests.m @@ -1097,6 +1097,42 @@ - (void)testQueryAll { [task waitUntilFinished]; } +- (void)testQueryContainedBy { + PFOfflineQueryLogic *logic = [[PFOfflineQueryLogic alloc] init]; + PFSQLiteDatabase *database = [[PFSQLiteDatabase alloc] init]; + + PFObject *object = [PFObject objectWithClassName:@"Object"]; + object[@"numbers"] = @[@0, @2]; + object[@"letters"] = @[@"b", @"c", @"d"]; + PFQuery *query = [PFQuery queryWithClassName:@"Object"]; + BFTask *task = [BFTask taskWithResult:nil]; + + [query whereKey:@"numbers" containedBy:@[@1, @2, @3, @4]]; + PFConstraintMatcherBlock matcherBlock = [logic createMatcherForQueryState:query.state user:_user]; + + // Check matcher + task = [[task continueWithBlock:^id(BFTask *task) { + return matcherBlock(object, database); + }] continueWithBlock:^id(BFTask *task) { + XCTAssertFalse([task.result boolValue]); + return nil; + }]; + + query = [PFQuery queryWithClassName:@"Object"]; + [query whereKey:@"letters" containedBy:@[@"a", @"b", @"c", @"d", @"e"]]; + matcherBlock = [logic createMatcherForQueryState:query.state user:_user]; + + // Check matcher + task = [[task continueWithBlock:^id(BFTask *task) { + return matcherBlock(object, database); + }] continueWithBlock:^id(BFTask *task) { + XCTAssertTrue([task.result boolValue]); + return nil; + }]; + + [task waitUntilFinished]; +} + - (void)testQueryRegex { PFOfflineQueryLogic *logic = [[PFOfflineQueryLogic alloc] init]; PFSQLiteDatabase *database = [[PFSQLiteDatabase alloc] init]; diff --git a/Parse/Tests/Unit/QueryUnitTests.m b/Parse/Tests/Unit/QueryUnitTests.m index 134de6db5..3e21980f7 100644 --- a/Parse/Tests/Unit/QueryUnitTests.m +++ b/Parse/Tests/Unit/QueryUnitTests.m @@ -426,6 +426,12 @@ - (void)testWhereContainsAllObjectsInArray { XCTAssertEqualObjects(query.state.conditions, @{ @"yolo" : @{@"$all" : @[ @"yarr" ]} }); } +- (void)testWhereContainedBy { + PFQuery *query = [PFQuery queryWithClassName:@"a"]; + [query whereKey:@"yolo" containsAllObjectsInArray:@[ @"yarr" ]]; + XCTAssertEqualObjects(query.state.conditions, @{ @"yolo" : @{@"$containedBy" : @[ @"yarr" ]} }); +} + - (void)testWhereKeyNearGeoPoint { PFGeoPoint *geoPoint = [PFGeoPoint geoPointWithLatitude:10.0 longitude:20.0]; From d05971f9f5fc0dba2431e10b3269d729ea150147 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Fri, 14 Jul 2023 20:46:25 -0500 Subject: [PATCH 2/2] Fix tests --- .../OfflineQueryLogic/PFOfflineQueryLogic.m | 11 ++++------- Parse/Tests/Unit/QueryUnitTests.m | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/Parse/Parse/Internal/LocalDataStore/OfflineQueryLogic/PFOfflineQueryLogic.m b/Parse/Parse/Internal/LocalDataStore/OfflineQueryLogic/PFOfflineQueryLogic.m index 8290bec7e..3a2613766 100644 --- a/Parse/Parse/Internal/LocalDataStore/OfflineQueryLogic/PFOfflineQueryLogic.m +++ b/Parse/Parse/Internal/LocalDataStore/OfflineQueryLogic/PFOfflineQueryLogic.m @@ -332,13 +332,10 @@ + (BOOL)matchesValue:(id)value containsAllObjectsInArray:(id)constraints { /** Matches $containedBy constraints. */ -+ (BOOL)matchesValue:(id)value - containedBy:(id)constraints { - PFParameterAssert(![constraints isKindOfClass:[NSArray class]], @"Constraint type not supported for $containedBy queries"); - PFParameterAssert(![value isKindOfClass:[NSArray class]], @"Value type not supported for $containedBy queries"); - - for (id constraint in (NSArray *)constraints) { - if (![self matchesValue:value containedIn:constraint]) { ++ (BOOL)matchesValue:(NSArray *)values + containedBy:(NSArray *)constraints { + for (id value in values) { + if (![self matchesValue:value containedIn:constraints]) { return NO; } } diff --git a/Parse/Tests/Unit/QueryUnitTests.m b/Parse/Tests/Unit/QueryUnitTests.m index 3e21980f7..0fe42af81 100644 --- a/Parse/Tests/Unit/QueryUnitTests.m +++ b/Parse/Tests/Unit/QueryUnitTests.m @@ -428,7 +428,7 @@ - (void)testWhereContainsAllObjectsInArray { - (void)testWhereContainedBy { PFQuery *query = [PFQuery queryWithClassName:@"a"]; - [query whereKey:@"yolo" containsAllObjectsInArray:@[ @"yarr" ]]; + [query whereKey:@"yolo" containedBy:@[ @"yarr" ]]; XCTAssertEqualObjects(query.state.conditions, @{ @"yolo" : @{@"$containedBy" : @[ @"yarr" ]} }); }