Skip to content

Commit

Permalink
Results.subscribe() preview API for flexible sync. (#8244)
Browse files Browse the repository at this point in the history
  • Loading branch information
ejm01 authored Sep 26, 2023
1 parent 9e1358b commit a98c88e
Show file tree
Hide file tree
Showing 16 changed files with 1,255 additions and 37 deletions.
27 changes: 26 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,32 @@
x.y.z Release notes (yyyy-MM-dd)
=============================================================
### Enhancements
* None.
* Added `Results.subscribe` API for flexible sync.
Now you can subscribe and unsubscribe to a flexible sync subscription through an object `Result`.
```swift
// Named subscription query
let results = try await realm.objects(Dog.self).where { $0.age > 18 }.subscribe(name: "adults")
results.unsubscribe()

// Unnamed subscription query
let results = try await realm.objects(Dog.self).subscribe()
results.unsubscribe()
````

After committing the subscription to the realm's local subscription set, the method
will wait for downloads according to the `WaitForSyncMode`.
```swift
let results = try await realm.objects(Dog.self).where { $0.age > 1 }.subscribe(waitForSync: .always)
```
Where `.always` will always download the latest data for the subscription, `.onCreation` will do
it only the first time the subscription is created, and `.never` will never wait for the
data to be downloaded.

This API is currently in `Preview` and may be subject to changes in the future.
* Added a new API which allows to remove all the unnamed subscriptions from the subscription set.
```swift
realm.subscriptions.removeAll(unnamedOnly: true)
```

### Fixed
* <How to hit and notice issue? what was the impact?> ([#????](https://github.com/realm/realm-swift/issues/????), since v?.?.?)
Expand Down
165 changes: 164 additions & 1 deletion Realm/ObjectServerTests/RLMFlexibleSyncServerTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,40 @@ - (void)testFlexibleSyncRemoveAllQueriesForType {
CHECK_COUNT(1, Dog, realm);
}

- (void)testRemoveAllUnnamedSubscriptions {
bool didPopulate = [self populateData:^(RLMRealm *realm) {
[self createPeople:realm partition:_cmd];
[self createDog:realm partition:_cmd];
}];
if (!didPopulate) {
return;
}

RLMRealm *realm = [self getFlexibleSyncRealm:_cmd];
XCTAssertNotNil(realm);
CHECK_COUNT(0, Person, realm);

[self writeQueryAndCompleteForRealm:realm block:^(RLMSyncSubscriptionSet *subs) {
[subs addSubscriptionWithClassName:Person.className
where:@"age > 20 and partition == %@", NSStringFromSelector(_cmd)];
[subs addSubscriptionWithClassName:Person.className
subscriptionName:@"person_age_2"
where:@"firstName == 'firstname_1' and partition == %@", NSStringFromSelector(_cmd)];
[subs addSubscriptionWithClassName:Dog.className
where:@"breed == 'Labradoodle' and partition == %@", NSStringFromSelector(_cmd)];
}];
XCTAssertEqual(realm.subscriptions.count, 3U);
CHECK_COUNT(2, Person, realm);
CHECK_COUNT(1, Dog, realm);

[self writeQueryAndCompleteForRealm:realm block:^(RLMSyncSubscriptionSet *subs) {
[subs removeAllUnnamedSubscriptions];
}];
XCTAssertEqual(realm.subscriptions.count, 1U);
CHECK_COUNT(1, Person, realm);
CHECK_COUNT(0, Dog, realm);
}

- (void)testFlexibleSyncUpdateQuery {
bool didPopulate = [self populateData:^(RLMRealm *realm) {
[self createPeople:realm partition:_cmd];
Expand Down Expand Up @@ -1072,7 +1106,6 @@ - (void)testFlexibleSyncInitialOnConnectionTimeout {
syncConfig.cancelAsyncOpenOnNonFatalErrors = true;
config.syncConfiguration = syncConfig;


// Set delay above the timeout so it should fail
proxy.delay = 2.0;

Expand All @@ -1090,6 +1123,136 @@ - (void)testFlexibleSyncInitialOnConnectionTimeout {
[proxy stop];
}

- (void)testSubscribeWithName {
bool didPopulate = [self populateData:^(RLMRealm *realm) {
Person *person = [[Person alloc] initWithPrimaryKey:[RLMObjectId objectId]
age:30
firstName:@"Brian"
lastName:@"Epstein"];
person.partition = NSStringFromSelector(_cmd);
[realm addObject:person];
}];
if (!didPopulate) {
return;
}

RLMRealm *realm = [self getFlexibleSyncRealm:_cmd];
XCTAssertNotNil(realm);
CHECK_COUNT(0, Person, realm);

XCTestExpectation *ex = [self expectationWithDescription:@"wait for download"];
[[[Person allObjectsInRealm:realm] objectsWhere:@"lastName == 'Epstein'"] subscribeWithName:@"5thBeatle" onQueue:dispatch_get_main_queue() completion:^(RLMResults *results, NSError *error) {
XCTAssertNil(error);
XCTAssertEqual(results.count, 1U);
[ex fulfill];
}];
[self waitForExpectationsWithTimeout:5.0 handler:nil];
}

- (void)testUnsubscribeWithinBlock {
bool didPopulate = [self populateData:^(RLMRealm *realm) {
Person *person = [[Person alloc] initWithPrimaryKey:[RLMObjectId objectId]
age:30
firstName:@"Joe"
lastName:@"Doe"];
person.partition = NSStringFromSelector(_cmd);
[realm addObject:person];
}];
if (!didPopulate) {
return;
}

RLMRealm *realm = [self getFlexibleSyncRealm:_cmd];
XCTAssertNotNil(realm);
CHECK_COUNT(0, Person, realm);

XCTestExpectation *ex = [self expectationWithDescription:@"wait for download"];
[[[Person allObjectsInRealm:realm] objectsWhere:@"lastName == 'Doe'"] subscribeWithName:@"unknown" onQueue:dispatch_get_main_queue() completion:^(RLMResults *results, NSError *error) {
XCTAssertNil(error);
XCTAssertEqual(results.count, 1U);
[results unsubscribe];
[ex fulfill];
}];
[self waitForExpectationsWithTimeout:5.0 handler:nil];
XCTAssertEqual(realm.subscriptions.count, 0U);
}

- (void)testSubscribeOnQueue {
bool didPopulate = [self populateData:^(RLMRealm *realm) {
Person *person = [[Person alloc] initWithPrimaryKey:[RLMObjectId objectId]
age:30
firstName:@"Sophia"
lastName:@"Loren"];
person.partition = NSStringFromSelector(_cmd);
[realm addObject:person];
}];
if (!didPopulate) {
return;
}

RLMUser *user = [self flexibleSyncUser:_cmd];
RLMRealmConfiguration *config = [user flexibleSyncConfiguration];
config.objectClasses = @[Person.self];

XCTestExpectation *ex = [self expectationWithDescription:@"wait for download"];
[self dispatchAsyncAndWait:^{
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
XCTAssertNotNil(realm);
CHECK_COUNT(0, Person, realm);

[[[Person allObjectsInRealm:realm] objectsWhere:@"lastName == 'Loren'"]
subscribeWithCompletionOnQueue:dispatch_get_main_queue()
completion:^(RLMResults *results, NSError *error) {
XCTAssertNil(error);
XCTAssertEqual(results.realm.subscriptions.count, 1UL);
XCTAssertEqual(results.realm.subscriptions.state, RLMSyncSubscriptionStateComplete);
CHECK_COUNT(1, Person, results.realm);
[ex fulfill];
}];
}];
[self waitForExpectationsWithTimeout:20.0 handler:nil];

RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
XCTAssertEqual(realm.subscriptions.count, 1UL);
CHECK_COUNT(1, Person, realm);
}

- (void)testSubscribeWithNameAndTimeout {
bool didPopulate = [self populateData:^(RLMRealm *realm) {
[self createPeople:realm partition:_cmd];
}];
if (!didPopulate) {
return;
}

RLMRealm *realm = [self getFlexibleSyncRealm:_cmd];
XCTAssertNotNil(realm);
CHECK_COUNT(0, Person, realm);

[[realm syncSession] suspend];
XCTestExpectation *ex = [self expectationWithDescription:@"expect timeout"];
NSTimeInterval timeout = 2.0;
RLMResults *res = [[Person allObjectsInRealm:realm] objectsWhere:@"age >= 20"];
[res subscribeWithName:@"20up" waitForSync:RLMWaitForSyncModeAlways onQueue:dispatch_get_main_queue() timeout:timeout completion:^(RLMResults *results, NSError *error) {
XCTAssert(error);
NSString *expectedDesc = [NSString stringWithFormat:@"Waiting for update timed out after %.01f seconds.", timeout];
XCTAssert([error.localizedDescription isEqualToString:expectedDesc]);
XCTAssertNil(results);
[ex fulfill];
}];
XCTAssertEqual(realm.subscriptions.count, 1UL);
[self waitForExpectationsWithTimeout:5.0 handler:nil];

// resume session and wait for complete
// otherwise test will not tear down successfully
[[realm syncSession] resume];
NSDate * start = [[NSDate alloc] init];
while (realm.subscriptions.state != RLMSyncSubscriptionStateComplete && start.timeIntervalSinceNow > -10.0) {
sleep(1);
}
XCTAssertEqual(realm.subscriptions.state, RLMSyncSubscriptionStateComplete);
}

#if 0 // FIXME: this is no longer an error and needs to be updated to something which is
- (void)testFlexibleSyncInitialSubscriptionThrowsError {
RLMUser *user = [self flexibleSyncUser:_cmd];
Expand Down
Loading

0 comments on commit a98c88e

Please sign in to comment.