Skip to content

Commit

Permalink
feat: Support Compound And Queries
Browse files Browse the repository at this point in the history
  • Loading branch information
dplewis committed Jul 11, 2023
1 parent 07f98fe commit fb89260
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 4 deletions.
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;
}

Check warning on line 607 in Parse/Parse/Internal/LocalDataStore/OfflineQueryLogic/PFOfflineQueryLogic.m

View check run for this annotation

Codecov / codecov/patch

Parse/Parse/Internal/LocalDataStore/OfflineQueryLogic/PFOfflineQueryLogic.m#L606-L607

Added lines #L606 - L607 were not covered by tests
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 @@ -454,6 +454,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 @@ -683,23 +683,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

0 comments on commit fb89260

Please sign in to comment.