Skip to content

Commit bd09fe4

Browse files
authored
feat: Add support for compound AND queries with PFQuery.andQueryWithSubqueries (#1733)
1 parent 8c9aa95 commit bd09fe4

File tree

7 files changed

+89
-4
lines changed

7 files changed

+89
-4
lines changed

Parse/Parse/Internal/LocalDataStore/OfflineQueryLogic/PFOfflineQueryLogic.m

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,31 @@ - (PFConstraintMatcherBlock)createOrMatcherForQueries:(NSArray *)queries user:(P
587587
};
588588
}
589589

590+
/**
591+
Handles $and queries.
592+
*/
593+
- (PFConstraintMatcherBlock)createAndMatcherForQueries:(NSArray *)queries user:(PFUser *)user {
594+
NSMutableArray *matchers = [NSMutableArray array];
595+
for (PFQuery *query in queries) {
596+
PFConstraintMatcherBlock matcher = [self createMatcherWithQueryConstraints:query.state.conditions user:user];
597+
[matchers addObject:matcher];
598+
}
599+
600+
// Now AND together the constraints for each query.
601+
return ^BFTask *(PFObject *object, PFSQLiteDatabase *database) {
602+
BFTask *task = [BFTask taskWithResult:@YES];
603+
for (PFConstraintMatcherBlock matcher in matchers) {
604+
task = [task continueWithSuccessBlock:^id(BFTask *task) {
605+
if (![task.result boolValue]) {
606+
return task;
607+
}
608+
return matcher(object, database);
609+
}];
610+
}
611+
return task;
612+
};
613+
}
614+
590615
/**
591616
Returns a PFConstraintMatcherBlock that return true iff the object matches queryConstraints. This
592617
takes in a SQLiteDatabase connection because SQLite is finicky about nesting connections, so we
@@ -599,6 +624,10 @@ - (PFConstraintMatcherBlock)createMatcherWithQueryConstraints:(NSDictionary *)qu
599624
// A set of queries to be OR-ed together
600625
PFConstraintMatcherBlock matcher = [self createOrMatcherForQueries:queryConstraintValue user:user];
601626
[matchers addObject:matcher];
627+
} else if ([key isEqualToString:PFQueryKeyAnd]) {
628+
// A set of queries to be AND-ed together
629+
PFConstraintMatcherBlock matcher = [self createAndMatcherForQueries:queryConstraintValue user:user];
630+
[matchers addObject:matcher];
602631
} else if ([key isEqualToString:PFQueryKeyRelatedTo]) {
603632
PFConstraintMatcherBlock matcher = ^BFTask *(PFObject *object, PFSQLiteDatabase *database) {
604633
PFObject *parent = queryConstraintValue[PFQueryKeyObject];

Parse/Parse/Internal/Query/PFQueryConstants.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ extern NSString *const PFQueryKeySelect;
3232
extern NSString *const PFQueryKeyDontSelect;
3333
extern NSString *const PFQueryKeyRelatedTo;
3434
extern NSString *const PFQueryKeyOr;
35+
extern NSString *const PFQueryKeyAnd;
3536
extern NSString *const PFQueryKeyQuery;
3637
extern NSString *const PFQueryKeyKey;
3738
extern NSString *const PFQueryKeyObject;

Parse/Parse/Internal/Query/PFQueryConstants.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
NSString *const PFQueryKeyDontSelect = @"$dontSelect";
3131
NSString *const PFQueryKeyRelatedTo = @"$relatedTo";
3232
NSString *const PFQueryKeyOr = @"$or";
33+
NSString *const PFQueryKeyAnd = @"$and";
3334
NSString *const PFQueryKeyQuery = @"query";
3435
NSString *const PFQueryKeyKey = @"key";
3536
NSString *const PFQueryKeyObject = @"object";

Parse/Parse/Source/PFQuery.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,15 @@ typedef void (^PFQueryArrayResultBlock)(NSArray<PFGenericObject> *_Nullable obje
461461
*/
462462
+ (instancetype)orQueryWithSubqueries:(NSArray<PFQuery *> *)queries;
463463

464+
/**
465+
Returns a `PFQuery` that is the `and` of the passed in queries.
466+
467+
@param queries The list of queries to and together.
468+
469+
@return An instance of `PFQuery` that is the `and` of the passed in queries.
470+
*/
471+
+ (instancetype)andQueryWithSubqueries:(NSArray<PFQuery *> *)queries;
472+
464473
/**
465474
Adds a constraint that requires that a key's value matches a value in another key
466475
in objects returned by a sub query.

Parse/Parse/Source/PFQuery.m

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -689,23 +689,31 @@ + (instancetype)queryWithClassName:(NSString *)className predicate:(NSPredicate
689689
return [self queryWithClassName:className normalizedPredicate:normalizedPredicate];
690690
}
691691

692-
+ (instancetype)orQueryWithSubqueries:(NSArray<PFQuery *> *)queries {
693-
PFParameterAssert(queries.count, @"Can't create an `or` query from no subqueries.");
692+
+ (instancetype)queryForSubqueries:(NSArray<PFQuery *> *)queries forKey:(NSString *)key {
693+
PFParameterAssert(queries.count, @"Can't create an `%@` query from no subqueries.", key);
694694
NSMutableArray *array = [NSMutableArray arrayWithCapacity:queries.count];
695695
NSString *className = queries.firstObject.parseClassName;
696696
for (PFQuery *query in queries) {
697697
PFParameterAssert([query isKindOfClass:[PFQuery class]],
698698
@"All elements should be instances of `PFQuery` class.");
699699
PFParameterAssert([query.parseClassName isEqualToString:className],
700-
@"All sub queries of an `or` query should be on the same class.");
700+
@"All sub queries of an `%@` query should be on the same class.", key);
701701

702702
[array addObject:query];
703703
}
704704
PFQuery *query = [self queryWithClassName:className];
705-
[query.state setEqualityConditionWithObject:array forKey:PFQueryKeyOr];
705+
[query.state setEqualityConditionWithObject:array forKey:key];
706706
return query;
707707
}
708708

709+
+ (instancetype)orQueryWithSubqueries:(NSArray<PFQuery *> *)queries {
710+
return [self queryForSubqueries:queries forKey:PFQueryKeyOr];
711+
}
712+
713+
+ (instancetype)andQueryWithSubqueries:(NSArray<PFQuery *> *)queries {
714+
return [self queryForSubqueries:queries forKey:PFQueryKeyAnd];
715+
}
716+
709717
///--------------------------------------
710718
#pragma mark - Get with objectId
711719
///--------------------------------------

Parse/Tests/Unit/OfflineQueryLogicUnitTests.m

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1468,6 +1468,35 @@ - (void)testQueryOr {
14681468
[task waitUntilFinished];
14691469
}
14701470

1471+
- (void)testQueryAnd {
1472+
PFOfflineQueryLogic *logic = [[PFOfflineQueryLogic alloc] init];
1473+
PFSQLiteDatabase *database = [[PFSQLiteDatabase alloc] init];
1474+
1475+
PFObject *object = [PFObject objectWithClassName:@"Object"];
1476+
object[@"foo"] = @"bar";
1477+
object[@"sum"] = @1337;
1478+
object[@"ArrezTheGodOfWar"] = @[@"bar", @1337];
1479+
PFQuery *query = nil;
1480+
BFTask *task = [BFTask taskWithResult:nil];
1481+
1482+
PFQuery *query1 = [PFQuery queryWithClassName:@"Object"];
1483+
[query1 whereKey:@"foo" equalTo:@"bar"];
1484+
PFQuery *query2 = [PFQuery queryWithClassName:@"Object"];
1485+
[query2 whereKey:@"sum" equalTo:@1337];
1486+
query = [PFQuery andQueryWithSubqueries:@[query1, query2]];
1487+
PFConstraintMatcherBlock matcherBlock = [logic createMatcherForQueryState:query.state user:_user];
1488+
1489+
// Check matcher
1490+
task = [[task continueWithBlock:^id(BFTask *task) {
1491+
return matcherBlock(object, database);
1492+
}] continueWithBlock:^id(BFTask *task) {
1493+
XCTAssertTrue([task.result boolValue]);
1494+
return nil;
1495+
}];
1496+
1497+
[task waitUntilFinished];
1498+
}
1499+
14711500
- (void)testSortDate {
14721501
PFOfflineQueryLogic *logic = [[PFOfflineQueryLogic alloc] init];
14731502

Parse/Tests/Unit/QueryUnitTests.m

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,14 @@ - (void)testOrQuery {
120120
XCTAssertEqualObjects(query.state.conditions[@"$or"], (@[ query1, query2 ]));
121121
}
122122

123+
- (void)testAndQuery {
124+
PFQuery *query1 = [PFQuery queryWithClassName:@"Yolo"];
125+
PFQuery *query2 = [PFQuery queryWithClassName:@"Yolo"];
126+
127+
PFQuery *query = [PFQuery andQueryWithSubqueries:@[ query1, query2 ]];
128+
XCTAssertEqualObjects(query.state.conditions[@"$and"], (@[ query1, query2 ]));
129+
}
130+
123131
#pragma mark Pagination
124132

125133
- (void)testLimit {

0 commit comments

Comments
 (0)