Lists are cleared if provided on update
How frequently does the bug occur?
Always
Description
After upgrading to 12.14.1, it was discovered that on record update if you pass the same unchanged list attribute value pulled from realm, the list value will be cleared e.g.
const table = this.realm.write(() => {
this.realm.create('Table', {
_id: new Realm.BSON.ObjectId(), // primaryKey
name: "Test List",
values: [1, 2, 3]
})
})
const updatedTable = this.realm.write(() => {
this.realm.create('Table', {
...table,
name: "Updated List",
}, UpdateMode.All)
})
console.log(updatedTable.values); // is now []
This only appears to be happening for lists defined as string, int etc. List objects appears to be solved by https://github.com/realm/realm-js/pull/6977
Upgrading to version 12.14.2 did not solve the issue after finding: https://github.com/realm/realm-js/issues/6889
Stacktrace & log output
No crash. Just a broken feature in the application.
Can you reproduce the bug?
Always
Reproduction Steps
A test case was added to integration-tests/tests/src/tests/objects.ts to confirm whether this issue was isolated to my project. The test case fails on the latest copy of main (currently 12.14.2):
const ListSchema: Realm.ObjectSchema = {
name: "ListObject",
primaryKey: "_id",
properties: {
_id: "objectId",
name: "string",
values: { type: "list", objectType: "int", default: [], optional: true },
names: { type: "list", objectType: "string", default: [], optional: true },
},
};
describe("with primary key", () => {
openRealmBeforeEach({ schema: [PersonWithId, CollectionSchema, ListSchema] });
...
describe("with collection", () => {
...
it("updates without list attributes clearing", function (this: Mocha.Context & RealmContext) {
const list = this.realm.write(() =>
this.realm.create(ListSchema.name, {
_id: new Realm.BSON.ObjectId(),
name: "Test List",
values: [1, 2, 3],
names: ["One", "Two", "Three"],
}),
);
expect(list.values.length).equals(3);
expect(list.names.length).equals(3);
const listAfterUpdate = this.realm.write(() => this.realm.create(ListSchema.name, {
...list,
name: "Updated List",
}, UpdateMode.All));
expect(this.realm.objects(ListSchema.name).length).equals(1); // -- Passes
expect(listAfterUpdate.name).equals("Updated List"); // -- Passes
expect(listAfterUpdate.values.length).equals(3); // -- Fails
expect(listAfterUpdate.names.length).equals(3); // -- Fails
});
Version
12.14.2
What services are you using?
Local Database only
Are you using encryption?
Yes
Platform OS and version(s)
android API 35, iOS 18.3, realm 12.14.2, RN 0.76.9
Build environment
No response
Cocoapods version
1.14.3
Debugging this further, I realised I've got the above scenario wrong. The test case provided is creating a second record due to the absent primaryKey schema configuration, and the list values on the first record are not cleared - which is correct. The actual broken scenario defines a primaryKey and also includes the list values on update which are then cleared 🤦♂️
Original test case provided in the scenario which after investigating is not a bug due to missing primary key configuration
const ListSchema: Realm.ObjectSchema = {
name: "ListObject",
properties: {
_id: "objectId",
name: "string",
values: { type: "list", objectType: "int", default: [], optional: true },
names: { type: "list", objectType: "string", default: [], optional: true },
},
};
describe("with primary key", () => {
openRealmBeforeEach({ schema: [PersonWithId, CollectionSchema, ListSchema] });
...
describe("with collection", () => {
...
it("updates without list attributes clearing", function (this: Mocha.Context & RealmContext) {
const list = this.realm.write(() =>
this.realm.create(ListSchema.name, {
_id: new Realm.BSON.ObjectId(),
name: "Test List",
values: [1, 2, 3],
names: ["One", "Two", "Three"],
}),
);
expect(list.values.length).equals(3);
expect(list.names.length).equals(3);
const listAfterUpdate = this.realm.write(() => this.realm.create(ListSchema.name, {
_id: list._id,
name: "Updated List",
}, UpdateMode.All));
expect(listAfterUpdate.name).equals("Updated List"); // -- Passes
expect(listAfterUpdate.values.length).equals(3); // -- Fails
expect(listAfterUpdate.names.length).equals(3); // -- Fails
});
New test case WITH primary key configuration and passing the list values to the update
const ListSchema: Realm.ObjectSchema = {
name: "ListObject",
primaryKey: "_id",
properties: {
_id: "objectId",
name: "string",
values: { type: "list", objectType: "int", default: [], optional: true },
names: { type: "list", objectType: "string", default: [], optional: true },
},
};
describe("with primary key", () => {
openRealmBeforeEach({ schema: [PersonWithId, CollectionSchema, ListSchema] });
...
describe("with collection", () => {
...
it("updates without list attributes clearing", function (this: Mocha.Context & RealmContext) {
const list = this.realm.write(() =>
this.realm.create(ListSchema.name, {
_id: new Realm.BSON.ObjectId(),
name: "Test List",
values: [1, 2, 3],
names: ["One", "Two", "Three"],
}),
);
expect(list.values.length).equals(3);
expect(list.names.length).equals(3);
const listAfterUpdate = this.realm.write(() => this.realm.create(ListSchema.name, {
...list,
name: "Updated List",
}, UpdateMode.All));
expect(this.realm.objects(ListSchema.name).length).equals(1); // -- Passes
expect(listAfterUpdate.name).equals("Updated List"); // -- Passes
expect(listAfterUpdate.values.length).equals(3); // -- Fails
expect(listAfterUpdate.names.length).equals(3); // -- Fails
});
Now with the correct scenario, I found that using values.toJSON() instead of values.snapshot() in the array setter, solves the issue but does break one other test case (that I know of):
https://github.com/realm/realm-js/blob/a03127726939f08f608edbdb2341605938f25708/packages/realm/src/property-accessors/Array.ts#L91
(broken test case after change)
This will require further digging as I'm most likely wrong in regards to this change. I'll update the issue in the meantime to reflect the actual bug encountered.
I think this relates to https://github.com/realm/realm-js/issues/6997 ? Also I found that when using react native and given a 'mixed' type value if you pass an array (*and you don't use JSON.stringify) it will store the first object but subsequent write or delete operations will crash they app without throwing an exception. Or better the exception is in the c++ layer and doesn't propagate to the objC layer for ios nor the JS layer for RN. I've created a ticket on on the core libray (but closed it because I found a usuable workaround) https://github.com/realm/realm-core/issues/8084