realm / realm-js

Realm is a mobile database: an alternative to SQLite & key-value stores
https://realm.io
Apache License 2.0
5.62k stars 558 forks source link

Is it possible to create objects in onMigration #6323

Closed PraneethGunas closed 2 months ago

PraneethGunas commented 5 months ago

How frequently does the bug occur?

Always

Description

Trying to create objects in a new schema from the data in an old deleted schema.

onMigration: (oldRealm, newRealm) => {
    const oldObjects = oldRealm.objects(RealmSchema.OldSchemaName);
    oldObjects.forEach((obj) => {
      if (obj.arrayValue.length) {
        obj.arrayValue.forEach((arrItem) => {
          newRealm.write(() => {
            newRealm.create(RealmSchema.NewSchemaName, { key: oldObject.value, ..., ... });
          });
        });
      }
    });
  },

When I call newRealm.write(() => { newRealm.create(RealmSchema.NewSchemaName, {key, oldObject.value}, Realm.UpdateMode.All) }){{ it always throws }}Error: Exception in HostFunction: The Realm is already in a write transaction Assigning values to the new object is not a problem since it doesn't require a write operation but creating a new object from an old one seems like a problem. I think I'm missing something here. Please let me know if there's a workaround. The references doesn't show how to create a new object from old deleted objects.

Stacktrace & log output

Possible Unhandled Promise Rejection (id: 0):
Error: Exception in HostFunction: The Realm is already in a write transaction

Can you reproduce the bug?

Always

Reproduction Steps

Version

11.10.2

What services are you using?

Local Database only

Are you using encryption?

Yes

Platform OS and version(s)

iOS/Android

Build environment

Which debugger for React Native: Flipper

Cocoapods version

No response

kneth commented 5 months ago

@PraneethGunas

When the migration function is called, the new Realm has already opened a transaction, and you can leave out newRealm.write().

If you have links to other objects - in particular embedded objects - you might have to transform the entire structure to plain JavaScript objects before inserting them into the new Realm.

PraneethGunas commented 5 months ago

@kneth Thanks a lot! removing newRealm.write() got rid of Error: Exception in HostFunction: The Realm is already in a write transaction. I now get Exception in HostFunction: Only Realm instances are supported. Maybe this has something with what you said...

If you have links to other objects - in particular embedded objects - you might have to transform the entire structure to plain JavaScript objects before inserting them into the new Realm.

newRealm.create(
            RealmSchema.NewSchemaName,
            {
              key1: oldObject.value1,
              key2: oldObject.value2,
              key3: {                          // object type with dynamic key
                [oldObject.value3]: {
                  key3_1: oldObject.value4,
                  key3_2: oldObject.value5,
                  key3_3: derivedValue(oldObject.value6),
                },
              },
            },
            Realm.UpdateMode.All
          );

The new schema looks like this (not embedded) but the values are derived from the oldObjects

const NewSchema: ObjectSchema = {
  name: RealmSchema.NewSchemaName,
  primaryKey: 'key_1',
  properties: {
    key_1: 'string',
    key_2: 'string',
    key_3: `{}`,
  },
};
PraneethGunas commented 5 months ago

Ah! I see there is something with the creation of the third key (key_3). Removing that helps me create the realm object without errors.

Edit: is it because Realm doesn't let you have dynamic keys for the dictionary-type objects? Can I not have a structure like {uniqueId1: object, uniqueId2: object} rather than [{id: uniqueId1, value: object}, {id: uniqueId2, value: object}]

kneth commented 5 months ago

is it because Realm doesn't let you have dynamic keys for the dictionary-type objects?

I expect it to.

As an alternative, can you create the object with key_1 and key_2 and add key_3 after? Something like:

const newObject = newRealm.create(RealmSchema.NewSchemaName, {
  key_1: oldObject.value1,
  key_2: oldObject.value2,
}, Realm.UpdateMode.All);
newObject.key_3.set({ 
  [oldObject.value3]:  {
    key3_1: oldObject.value4,
    key3_2: oldObject.value5,
    key3_3: derivedValue(oldObject.value6),
  }
});