Skip to content

feat: Add support for compound AND queries with PFQuery.andQueryWithSubqueries #1733

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 14, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,31 @@ - (PFConstraintMatcherBlock)createOrMatcherForQueries:(NSArray *)queries user:(P
};
}

/**
Handles $and queries.
*/
- (PFConstraintMatcherBlock)createAndMatcherForQueries:(NSArray *)queries user:(PFUser *)user {
NSMutableArray *matchers = [NSMutableArray array];
for (PFQuery *query in queries) {
PFConstraintMatcherBlock matcher = [self createMatcherWithQueryConstraints:query.state.conditions user:user];
[matchers addObject:matcher];
}

// Now AND together the constraints for each query.
return ^BFTask *(PFObject *object, PFSQLiteDatabase *database) {
BFTask *task = [BFTask taskWithResult:@YES];
for (PFConstraintMatcherBlock matcher in matchers) {
task = [task continueWithSuccessBlock:^id(BFTask *task) {
if (![task.result boolValue]) {
return task;
}
return matcher(object, database);
}];
}
return task;
};
}

/**
Returns a PFConstraintMatcherBlock that return true iff the object matches queryConstraints. This
takes in a SQLiteDatabase connection because SQLite is finicky about nesting connections, so we
Expand All @@ -599,6 +624,10 @@ - (PFConstraintMatcherBlock)createMatcherWithQueryConstraints:(NSDictionary *)qu
// A set of queries to be OR-ed together
PFConstraintMatcherBlock matcher = [self createOrMatcherForQueries:queryConstraintValue user:user];
[matchers addObject:matcher];
} else if ([key isEqualToString:PFQueryKeyAnd]) {
// A set of queries to be AND-ed together
PFConstraintMatcherBlock matcher = [self createAndMatcherForQueries:queryConstraintValue user:user];
[matchers addObject:matcher];
} else if ([key isEqualToString:PFQueryKeyRelatedTo]) {
PFConstraintMatcherBlock matcher = ^BFTask *(PFObject *object, PFSQLiteDatabase *database) {
PFObject *parent = queryConstraintValue[PFQueryKeyObject];
Expand Down
1 change: 1 addition & 0 deletions Parse/Parse/Internal/Query/PFQueryConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ extern NSString *const PFQueryKeySelect;
extern NSString *const PFQueryKeyDontSelect;
extern NSString *const PFQueryKeyRelatedTo;
extern NSString *const PFQueryKeyOr;
extern NSString *const PFQueryKeyAnd;
extern NSString *const PFQueryKeyQuery;
extern NSString *const PFQueryKeyKey;
extern NSString *const PFQueryKeyObject;
Expand Down
1 change: 1 addition & 0 deletions Parse/Parse/Internal/Query/PFQueryConstants.m
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
NSString *const PFQueryKeyDontSelect = @"$dontSelect";
NSString *const PFQueryKeyRelatedTo = @"$relatedTo";
NSString *const PFQueryKeyOr = @"$or";
NSString *const PFQueryKeyAnd = @"$and";
NSString *const PFQueryKeyQuery = @"query";
NSString *const PFQueryKeyKey = @"key";
NSString *const PFQueryKeyObject = @"object";
Expand Down
9 changes: 9 additions & 0 deletions Parse/Parse/Source/PFQuery.h
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,15 @@ typedef void (^PFQueryArrayResultBlock)(NSArray<PFGenericObject> *_Nullable obje
*/
+ (instancetype)orQueryWithSubqueries:(NSArray<PFQuery *> *)queries;

/**
Returns a `PFQuery` that is the `and` of the passed in queries.

@param queries The list of queries to and together.

@return An instance of `PFQuery` that is the `and` of the passed in queries.
*/
+ (instancetype)andQueryWithSubqueries:(NSArray<PFQuery *> *)queries;

/**
Adds a constraint that requires that a key's value matches a value in another key
in objects returned by a sub query.
Expand Down
16 changes: 12 additions & 4 deletions Parse/Parse/Source/PFQuery.m
Original file line number Diff line number Diff line change
Expand Up @@ -689,23 +689,31 @@ + (instancetype)queryWithClassName:(NSString *)className predicate:(NSPredicate
return [self queryWithClassName:className normalizedPredicate:normalizedPredicate];
}

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

[array addObject:query];
}
PFQuery *query = [self queryWithClassName:className];
[query.state setEqualityConditionWithObject:array forKey:PFQueryKeyOr];
[query.state setEqualityConditionWithObject:array forKey:key];
return query;
}

+ (instancetype)orQueryWithSubqueries:(NSArray<PFQuery *> *)queries {
return [self queryForSubqueries:queries forKey:PFQueryKeyOr];
}

+ (instancetype)andQueryWithSubqueries:(NSArray<PFQuery *> *)queries {
return [self queryForSubqueries:queries forKey:PFQueryKeyAnd];
}

///--------------------------------------
#pragma mark - Get with objectId
///--------------------------------------
Expand Down
29 changes: 29 additions & 0 deletions Parse/Tests/Unit/OfflineQueryLogicUnitTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -1468,6 +1468,35 @@ - (void)testQueryOr {
[task waitUntilFinished];
}

- (void)testQueryAnd {
PFOfflineQueryLogic *logic = [[PFOfflineQueryLogic alloc] init];
PFSQLiteDatabase *database = [[PFSQLiteDatabase alloc] init];

PFObject *object = [PFObject objectWithClassName:@"Object"];
object[@"foo"] = @"bar";
object[@"sum"] = @1337;
object[@"ArrezTheGodOfWar"] = @[@"bar", @1337];
PFQuery *query = nil;
BFTask *task = [BFTask taskWithResult:nil];

PFQuery *query1 = [PFQuery queryWithClassName:@"Object"];
[query1 whereKey:@"foo" equalTo:@"bar"];
PFQuery *query2 = [PFQuery queryWithClassName:@"Object"];
[query2 whereKey:@"sum" equalTo:@1337];
query = [PFQuery andQueryWithSubqueries:@[query1, query2]];
PFConstraintMatcherBlock 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)testSortDate {
PFOfflineQueryLogic *logic = [[PFOfflineQueryLogic alloc] init];

Expand Down
8 changes: 8 additions & 0 deletions Parse/Tests/Unit/QueryUnitTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,14 @@ - (void)testOrQuery {
XCTAssertEqualObjects(query.state.conditions[@"$or"], (@[ query1, query2 ]));
}

- (void)testAndQuery {
PFQuery *query1 = [PFQuery queryWithClassName:@"Yolo"];
PFQuery *query2 = [PFQuery queryWithClassName:@"Yolo"];

PFQuery *query = [PFQuery andQueryWithSubqueries:@[ query1, query2 ]];
XCTAssertEqualObjects(query.state.conditions[@"$and"], (@[ query1, query2 ]));
}

#pragma mark Pagination

- (void)testLimit {
Expand Down