Skip to content

Commit

Permalink
Merge branch 'fp/update-core-13.13' into fp/geospatial
Browse files Browse the repository at this point in the history
* fp/update-core-13.13:
  Corrected changelog
  Updated changelog
  Fix User.callFunction JSDoc to match the v11+ API (#5768)
  Add Flexible Sync subscribe/unsubscribe APIs (#5772)
  Fix warning for deprecated namespace setting method in Android (#5862)
  Update install-test-react-native.yml (#5848)
  Update package-unit-tests.yml to add ccache and ninja (#5837)
  Enable cleartext traffic in android test app to make tests work in release builds
  Removed unused
  Updated changelog

# Conflicts:
#	packages/realm/bindgen/vendor/realm-core
#	packages/realm/src/index.ts
  • Loading branch information
papafe committed Jun 7, 2023
2 parents e3be0ce + 0e4303b commit 619e36d
Show file tree
Hide file tree
Showing 16 changed files with 446 additions and 48 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/install-test-react-native.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ jobs:
- name: Initialize app
# Using "--skip-bundle-install" to let the setup-ruby action install the bundle
# Using "--skip-pod-install" to ensure it happens after setup-ruby has executed
run: npm run init -- --skip-bundle-install --skip-pod-install --realm-version ${{ matrix.realm-version }} --react-native-version ${{ matrix.react-native-version }} --engine ${{ matrix.engine }}
run: npm run init -- --skip-bundle-install --skip-pod-install --realm-version ${{ matrix.realm-version }} --react-native-version ${{ matrix.react-native-version }} --engine ${{ matrix.engine }} --new-architecture ${{ matrix.new-architecture }}

- uses: ruby/setup-ruby@v1
if: ${{ matrix.platform == 'ios' }}
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/package-unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version: 18
# ninja-build is used by default if available and results in faster build times
- name: Install ninja
run: sudo apt-get install ninja-build
- name: ccache
uses: hendrikmuhs/ccache-action@v1
# Install the root package to get dev-dependencies
# (--ignore-scripts to avoid downloading or building the native module)
- run: npm ci --ignore-scripts
Expand Down
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,26 @@
* Improve performance of equality queries on a non-indexed mixed property by about 30%. ([realm/realm-core#6506](https://github.com/realm/realm-core/pull/6506))
* Improve performance of rolling back write transactions after making changes. ([realm/realm-core#6513](https://github.com/realm/realm-core/pull/6513))
* Extended `PropertySchema.indexed` with the `full-text` option, that allows to create an index for full-text search queries. ([#5755](https://github.com/realm/realm-js/issues/5755))
* Access token refresh for websockets was not updating the location metadata ([realm/realm-core#6630](https://github.com/realm/realm-core/issues/6630), since v11.9.0)
* Fix several UBSan failures which did not appear to result in functional bugs ([realm/realm-core#6649](https://github.com/realm/realm-core/pull/6649)).
* Using both synchronous and asynchronous transactions on the same thread or scheduler could hit an assertion failure if one of the callbacks for an asynchronous transaction happened to be scheduled during a synchronous transaction ([realm/realm-core#6659](https://github.com/realm/realm-core/pull/6649), since v10.12.0)
* Added APIs to facilitate adding and removing subscriptions. ([#5772](https://github.com/realm/realm-js/pull/5772))
* Experimental APIs: Enabled subscribing and unsubscribing directly to and from a `Results` instance via `Results.subscribe()` (asynchronous) and `Results.unsubscribe()` (synchronous).
* Added a `WaitForSync` enum specifying whether to wait or not wait for subscribed objects to be downloaded before resolving the promise returned from `Results.subscribe()`.
* Extended `SubscriptionOptions` to take a `WaitForSync` behavior and a maximum waiting timeout before returning from `Results.subscribe()`.
* Added the instance method `MutableSubscriptionSet.removeUnnamed()` for removing only unnamed subscriptions.
```javascript
const peopleOver20 = await realm
.objects("Person")
.filtered("age > 20")
.subscribe({
name: "peopleOver20",
behavior: WaitForSync.FirstTime, // Default
timeout: 2000,
});
// ...
peopleOver20.unsubscribe();
```

### Fixed
* Fix a stack overflow crash when using the query parser with long chains of AND/OR conditions. ([realm/realm-core#6428](https://github.com/realm/realm-core/pull/6428), since v10.11.0)
Expand All @@ -27,6 +47,7 @@
* Fixed an error where performing a query like "{1, 2, 3, ...} IN list" where the array is longer than 8 and all elements are smaller than some values in list, the program would crash. ([realm/realm-core#6545](https://github.com/realm/realm-core/pull/6545), since v10.20.0)
* Performing a large number of queries without ever performing a write resulted in steadily increasing memory usage, some of which was never fully freed due to an unbounded cache. ([realm/realm-core#6530](https://github.com/realm/realm-core/pull/6530), since v10.19.0)
* Partition-Based to Flexible Sync Migration for migrating a client app that uses partition based sync to use flexible sync under the hood if the server has been migrated to flexible sync is officially supported with this release. Any clients using an older version of Realm will receive a "switch to flexible sync" error message when trying to sync with the app. ([realm/realm-core#6554](https://github.com/realm/realm-core/issues/6554), since v11.9.0)
* Fix deprecated namespace method warning when building for Android ([#5646](https://github.com/realm/realm-js/issues/5646))

### Compatibility
* React Native >= v0.71.4
Expand All @@ -40,6 +61,7 @@
* Upgraded Realm Core from v13.10.1 to v13.11.0. ([#5811](https://github.com/realm/realm-js/issues/5811))
* Bump sync protocol to v9 to indicate client has fix for client reset error during async open. ([realm/realm-core#6609](https://github.com/realm/realm-core/issues/6609))
* Disabling sync session multiplexing by default in the SDK, since Core's default changed to enabled with v13.11.0. ([#5831](https://github.com/realm/realm-js/pull/5831))
* Upgraded Realm Core from v13.11.1 to v13.13.0. ([#5873](https://github.com/realm/realm-js/pull/5873))

## 12.0.0-alpha.2 (2023-04-05)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:usesCleartextTraffic="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
Expand Down
247 changes: 247 additions & 0 deletions integration-tests/tests/src/tests/sync/flexible.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ import {
FlexibleSyncConfiguration,
Realm,
SessionStopPolicy,
SubscriptionSetState,
CompensatingWriteError,
WaitForSync,
} from "realm";

import { authenticateUserBefore, importAppBefore, openRealmBeforeEach } from "../../hooks";
Expand Down Expand Up @@ -1397,6 +1399,19 @@ describe.skipIf(environment.missingServer, "Flexible sync", function () {
expect(subs).to.have.length(1);
expect([...subs][0].queryString).to.equal("age > 10");
});

it("returns true and removes a subscription with an empty name", async function (this: RealmContext) {
addSubscription(this.realm, this.realm.objects(FlexiblePersonSchema.name).filtered("age > 10"));
const { subs } = addSubscriptionForPerson(this.realm, { name: "" });
expect(subs).to.have.length(2);

await subs.update((mutableSubs) => {
expect(mutableSubs.removeByName("")).to.be.true;
});

expect(subs).to.have.length(1);
expect([...subs][0].queryString).to.equal("age > 10");
});
});

describe("#remove", function () {
Expand Down Expand Up @@ -1532,6 +1547,44 @@ describe.skipIf(environment.missingServer, "Flexible sync", function () {
});
});

describe("#removeUnnamed", function () {
it("removes all unnamed subscriptions and returns the number of subscriptions removed", async function (this: RealmContext) {
// Add 1 named and 2 unnamed subscriptions.
addSubscriptionForPerson(this.realm, { name: "test" });
addSubscription(this.realm, this.realm.objects(FlexiblePersonSchema.name).filtered("age < 5"));
await addSubscriptionAndSync(
this.realm,
this.realm.objects(FlexiblePersonSchema.name).filtered("age > 10"),
);
expect(this.realm.subscriptions).to.have.length(3);

let numRemoved = 0;
await this.realm.subscriptions.update((mutableSubs) => {
numRemoved = mutableSubs.removeUnnamed();
});

expect(numRemoved).to.equal(2);
expect(this.realm.subscriptions).to.have.length(1);
});

it("does not remove subscription with empty name", async function (this: RealmContext) {
await addSubscriptionAndSync(
this.realm,
this.realm.objects(FlexiblePersonSchema.name).filtered("age > 10"),
{ name: "" },
);
expect(this.realm.subscriptions).to.have.length(1);

let numRemoved = 0;
await this.realm.subscriptions.update((mutableSubs) => {
numRemoved = mutableSubs.removeUnnamed();
});

expect(numRemoved).to.equal(0);
expect(this.realm.subscriptions).to.have.length(1);
});
});

describe("#removeByObjectType", function () {
it("returns 0 if no subscriptions for the object type exist", async function (this: RealmContext) {
const { subs } = addSubscriptionForPerson(this.realm);
Expand Down Expand Up @@ -1571,6 +1624,200 @@ describe.skipIf(environment.missingServer, "Flexible sync", function () {
});
});

describe("Results#subscribe", function () {
it("waits for objects to sync the first time only", async function (this: RealmContext) {
expect(this.realm.subscriptions).to.have.length(0);

const peopleOver10 = this.realm.objects(FlexiblePersonSchema.name).filtered("age > 10");

// Subscribing the first time should wait for synchronization.
await peopleOver10.subscribe({ behavior: WaitForSync.FirstTime });
expect(this.realm.subscriptions.state).to.equal(SubscriptionSetState.Complete);

// Subscribing the second time should return without waiting.
await peopleOver10.subscribe({ behavior: WaitForSync.FirstTime });
expect(this.realm.subscriptions.state).to.equal(SubscriptionSetState.Pending);

expect(this.realm.subscriptions).to.have.length(1);
});

it("waits for objects to sync the first time only by default", async function (this: RealmContext) {
expect(this.realm.subscriptions).to.have.length(0);

const peopleOver10 = this.realm.objects(FlexiblePersonSchema.name).filtered("age > 10");

await peopleOver10.subscribe();
expect(this.realm.subscriptions.state).to.equal(SubscriptionSetState.Complete);

await peopleOver10.subscribe();
expect(this.realm.subscriptions.state).to.equal(SubscriptionSetState.Pending);

expect(this.realm.subscriptions).to.have.length(1);
});

it("waits for objects to sync the first time only for separate 'Results' instances w/ same query", async function (this: RealmContext) {
expect(this.realm.subscriptions).to.have.length(0);

// Subscribe to a query on 'Results' instance 1.
let peopleOver10 = this.realm.objects(FlexiblePersonSchema.name).filtered("age > 10");
await peopleOver10.subscribe({ behavior: WaitForSync.FirstTime });
expect(this.realm.subscriptions.state).to.equal(SubscriptionSetState.Complete);

// Subscribe to the same query on 'Results' instance 2 (overwrite previous 'peopleOver10' value).
peopleOver10 = this.realm.objects(FlexiblePersonSchema.name).filtered("age > 10");
await peopleOver10.subscribe({ behavior: WaitForSync.FirstTime });
expect(this.realm.subscriptions.state).to.equal(SubscriptionSetState.Pending);

expect(this.realm.subscriptions).to.have.length(1);
});

it("always waits for objects to sync", async function (this: RealmContext) {
expect(this.realm.subscriptions).to.have.length(0);

const peopleOver10 = this.realm.objects(FlexiblePersonSchema.name).filtered("age > 10");

await peopleOver10.subscribe({ behavior: WaitForSync.Always });
expect(this.realm.subscriptions.state).to.equal(SubscriptionSetState.Complete);

await peopleOver10.subscribe({ behavior: WaitForSync.Always });
expect(this.realm.subscriptions.state).to.equal(SubscriptionSetState.Complete);

expect(this.realm.subscriptions).to.have.length(1);
});

it("never waits for objects to sync", async function (this: RealmContext) {
expect(this.realm.subscriptions).to.have.length(0);

const peopleOver10 = this.realm.objects(FlexiblePersonSchema.name).filtered("age > 10");

await peopleOver10.subscribe({ behavior: WaitForSync.Never });
expect(this.realm.subscriptions.state).to.equal(SubscriptionSetState.Pending);

await peopleOver10.subscribe({ behavior: WaitForSync.Never });
expect(this.realm.subscriptions.state).to.equal(SubscriptionSetState.Pending);

expect(this.realm.subscriptions).to.have.length(1);
});

it("waits for objects to sync when timeout is longer", async function (this: RealmContext) {
expect(this.realm.subscriptions).to.have.length(0);

const peopleOver10 = this.realm.objects(FlexiblePersonSchema.name).filtered("age > 10");
// `this.timeout()` is used so that it doesn't exceed the timeout that our tests
// are configured to use (which could vary).
await peopleOver10.subscribe({ behavior: WaitForSync.Always, timeout: this.timeout() });
expect(this.realm.subscriptions.state).to.equal(SubscriptionSetState.Complete);

expect(this.realm.subscriptions).to.have.length(1);
});

it("does not wait for objects to sync when timeout is shorter", async function (this: RealmContext) {
expect(this.realm.subscriptions).to.have.length(0);

const peopleOver10 = this.realm.objects(FlexiblePersonSchema.name).filtered("age > 10");
await peopleOver10.subscribe({ behavior: WaitForSync.Always, timeout: 0 });
expect(this.realm.subscriptions.state).to.equal(SubscriptionSetState.Pending);

expect(this.realm.subscriptions).to.have.length(1);
});

it("returns the same 'Results' instance", async function (this: RealmContext) {
const beforeSubscribe = this.realm.objects(FlexiblePersonSchema.name).filtered("age > 10");
const afterSubscribe = await beforeSubscribe.subscribe();
expect(beforeSubscribe).to.equal(afterSubscribe);
});
});

describe("Results#unsubscribe", function () {
it("unsubscribes from existing subscription", async function (this: RealmContext) {
const peopleOver10 = await this.realm.objects(FlexiblePersonSchema.name).filtered("age > 10").subscribe();
expect(this.realm.subscriptions).to.have.length(1);

peopleOver10.unsubscribe();
expect(this.realm.subscriptions).to.have.length(0);
});

it("does not throw or unsubscribe when there is no matching subscription", function (this: RealmContext) {
const peopleUnder10 = this.realm.objects(FlexiblePersonSchema.name).filtered("age < 10").subscribe();
const peopleOver10 = this.realm.objects(FlexiblePersonSchema.name).filtered("age > 10");
expect(this.realm.subscriptions).to.have.length(1);

// Unsubscribe to the Results without a subscription.
peopleOver10.unsubscribe();
expect(this.realm.subscriptions).to.have.length(1);
});

it("does not unsubscribe multiple times", async function (this: RealmContext) {
await this.realm.objects(FlexiblePersonSchema.name).filtered("age < 10").subscribe();
const peopleOver10 = await this.realm.objects(FlexiblePersonSchema.name).filtered("age > 10").subscribe();
expect(this.realm.subscriptions).to.have.length(2);

peopleOver10.unsubscribe();
peopleOver10.unsubscribe();
expect(this.realm.subscriptions).to.have.length(1);
});

it("unsubscribes from subscription with matching name", async function (this: RealmContext) {
// Create 3 subscriptions with the same query: 2 named, 1 unnamed.
const queryString = "age > 10";
await this.realm.objects(FlexiblePersonSchema.name).filtered(queryString).subscribe({ name: "name1" });
await this.realm.objects(FlexiblePersonSchema.name).filtered(queryString).subscribe();
const peopleOver10 = await this.realm
.objects(FlexiblePersonSchema.name)
.filtered(queryString)
.subscribe({ name: "name2" });
expect(this.realm.subscriptions).to.have.length(3);

// Expect only the "name2" subscription to be gone.
peopleOver10.unsubscribe();

const subs = [...this.realm.subscriptions];
expect(subs).to.have.length(2);
expect(subs[0].queryString).to.equal(queryString);
expect(subs[0].name).to.equal("name1");
expect(subs[1].queryString).to.equal(queryString);
expect(subs[1].name).to.be.null;
});

it("unsubscribes from subscription with matching name when subscribing via `update()`", async function (this: RealmContext) {
// Save a reference to a Results that is not yet subscribed to.
const queryString = "age > 10";
const peopleOver10 = await this.realm.objects(FlexiblePersonSchema.name).filtered(queryString);
expect(this.realm.subscriptions).to.have.length(0);

// Create 3 subscriptions via `update()` with the same query: 2 named, 1 unnamed.
await this.realm.subscriptions.update((mutableSubs) => {
mutableSubs.add(this.realm.objects(FlexiblePersonSchema.name).filtered(queryString), { name: "name1" });
mutableSubs.add(this.realm.objects(FlexiblePersonSchema.name).filtered(queryString));
// Pass the Results reference to subscribe to.
mutableSubs.add(peopleOver10, { name: "name2" });
});
expect(this.realm.subscriptions).to.have.length(3);

// Expect only the "name2" subscription to be gone.
peopleOver10.unsubscribe();

const subs = [...this.realm.subscriptions];
expect(subs).to.have.length(2);
expect(subs[0].queryString).to.equal(queryString);
expect(subs[0].name).to.equal("name1");
expect(subs[1].queryString).to.equal(queryString);
expect(subs[1].name).to.be.null;
});

it("unsubscribes from subscription with matching query", async function (this: RealmContext) {
const queryString = "age > 10";
const results1 = await this.realm.objects(FlexiblePersonSchema.name).filtered(queryString).subscribe();
const results2 = await this.realm.objects(FlexiblePersonSchema.name).filtered(queryString);
expect(this.realm.subscriptions).to.have.length(1);

// Even though `subscribe()` was called on `results1`, `unsubscribe()` removes unnamed
// subscriptions by query, thus removing the one `results1` subscribed to.
results2.unsubscribe();
expect(this.realm.subscriptions).to.have.length(0);
});
});

// TODO Right now there is no is_valid method we can use to verify if the subs
// are in a valid state... maybe need a different solution as this will crash
xdescribe("when realm is closed", function () {
Expand Down
1 change: 1 addition & 0 deletions packages/realm/react-native/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ allprojects {
apply plugin: 'com.android.library'

android {
namespace 'io.realm.react'
compileSdkVersion rootProject.hasProperty('compileSdkVersion') ? rootProject.compileSdkVersion : 28
buildToolsVersion rootProject.hasProperty('buildToolsVersion') ? rootProject.buildToolsVersion : '28.0.3'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.realm.react">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application />
</manifest>
Loading

0 comments on commit 619e36d

Please sign in to comment.