realm-java icon indicating copy to clipboard operation
realm-java copied to clipboard

Realm.deleteAll() is trying to delete RealmObjects across module boundaries

Open jpetitto opened this issue 6 years ago • 6 comments

I have a project with multiple modules using Realm. I was hoping that I could just call deleteAll() on a Realm for one of the modules, but it seems to be trying to delete RealmObjects from the other modules, leading to a runtime exception.

Expected Results

I'd expect deleteAll() to only delete the RealmObjects within the associated Realm.

Actual Results

When the code runs, I get the following runtime exception when calling deleteAll() on the library Realm instance:

Caused by: io.realm.exceptions.RealmException: 'AppModel' doesn't exist in current schema.
          at io.realm.internal.ColumnIndices.getColumnInfo(ColumnIndices.java:112)
          at io.realm.RealmSchema.getColumnInfo(RealmSchema.java:250)
          at io.realm.ImmutableRealmSchema.get(ImmutableRealmSchema.java:41)
          at io.realm.RealmSchema.getAll(RealmSchema.java:88)
          at io.realm.BaseRealm.deleteAll(BaseRealm.java:602)
          at io.realm.Realm.deleteAll(Realm.java:135)
          at com.johnpetitto.realmbug.MainActivity.lambda$onCreate$1$MainActivity(MainActivity.java:30)
          at com.johnpetitto.realmbug.MainActivity$$Lambda$1.execute(Unknown Source:0)
          at io.realm.Realm.executeTransaction(Realm.java:1394)
          at com.johnpetitto.realmbug.MainActivity.onCreate(MainActivity.java:30)
          at android.app.Activity.performCreate(Activity.java:6998)
          at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1230)
          at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2899)

Steps & Code to Reproduce

I created a sample project to reproduce this. Basically there is an app module and a library module. The library module has a @RealmModule in order to differentiate itself from the app code. Both modules contain a single RealmObject. Here is a repository with the full code.

Code Sample

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Realm.init(this);

    try (Realm realm = Realm.getDefaultInstance()) {
        realm.executeTransaction(r -> r.deleteAll());
    }

    RealmConfiguration libraryConfig = new RealmConfiguration.Builder()
            .deleteRealmIfMigrationNeeded()
            .modules(new LibraryModule())
            .build();

    try (Realm realm = Realm.getInstance(libraryConfig)) {
        realm.executeTransaction(r -> r.deleteAll());
    }
}

Version of Realm and tooling

Realm version(s): 4.3.3

Realm sync feature enabled: no

Android Studio version: 3.0

Which Android version and device: Android 8 on a OnePlus 3T

jpetitto avatar Feb 08 '18 20:02 jpetitto

I would think that currently, Realm.deleteAll() tries to delete the items for all currently existing classes that are in the schema of the existing Realm file.

I'm actually more surprised that

 try (Realm realm = Realm.getInstance(libraryConfig)) {

I would have expected this to have a schema mismatch and delete the Realm because migration was needed, after which it SHOULD allow deleting items. So there could be a bug there with schema caching

Zhuinden avatar Feb 09 '18 09:02 Zhuinden

Hi @jpetitto Thanks for the nice example project. I'm able to reproduce the behaviour and something indeed looks fishy. I'm looking into it.

cmelchior avatar Feb 09 '18 09:02 cmelchior

I found the issue(s).

  1. You are re-using the underlying file between the two Realm instances. This means that when you open the library instance, the class "AppModel" is already in there. Currently, we don't treat extra tables as an error as long as they don't conflict with the schema you define.

  2. The problem arises when you call schema.getAll() (which deleteAll() does). This method dynamically tries to load the schema information, but it will ask the Java module for information about all classes it finds including "AppModule", but that doesn't exist since it isn't part of the module, so the method crashes with the exception you see. This kinda conflicts with 1.

I suspect this error can also show up in other ways, e.g. through the Realm.isEmpty() method which will return the wrong result.

I'll need to dig a little further, but it looks like we need to fix schema.findAll() method to only return the classes in the defined schema for typed Realms and everything when using DynamicRealm.

3 workarounds exist right now:

// Manually delete objects of a given type. You need to call this for all types in your module.
realm.delete(LibraryModule.class);

// Use different names for files used by app / libraries
RealmConfiguration libConfig = new RealmConfiguration.Builder()
  .name("library.realm")
  .build()

RealmConfiguration appConfig = new RealmConfiguration.Builder()
  .name("app.realm")
  .build()

// Use DynamicRealm to delete everything
DynamicRealm dynamicRealm = DynamicRealm.getInstance(config);
dynamicRealm.executeTransaction(r -> r.deleteAll());

cmelchior avatar Feb 09 '18 10:02 cmelchior

Oh hey, it'd work with DynamicRealm? that's pretty cool, I didn't think of that.

Zhuinden avatar Feb 09 '18 11:02 Zhuinden

Thanks for investigating this @cmelchior - I like the solution of using separate files for each module. I'm currently manually deleting each model type, which can be a bit error prone if I forget to update it. Glad I could help surface the issue with schema.findAll() and using multiple modules.

jpetitto avatar Feb 09 '18 18:02 jpetitto

Would this issue be related to what I am seeing here? - https://stackoverflow.com/questions/49164180/need-help-understanding-how-to-remove-realm-models-from-schema?noredirect=1#comment85351896_49164180

akm0012 avatar Mar 08 '18 14:03 akm0012