question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Exception "Primary key property XXX has duplicate values after migration."

See original GitHub issue

When I started using the migration, I began to receive periodic crash messages from crashlytics like this:

Caused by java.lang.IllegalStateException: Primary key property 'Relation.id' has duplicate values after migration.
       at io.realm.internal.OsSharedRealm.nativeGetSharedRealm(OsSharedRealm.java)
       at io.realm.internal.OsSharedRealm.<init>(OsSharedRealm.java:171)
       at io.realm.internal.OsSharedRealm.getInstance(OsSharedRealm.java:241)
       at io.realm.BaseRealm.<init>(BaseRealm.java:136)
       at io.realm.BaseRealm.<init>(BaseRealm.java:105)
       at io.realm.Realm.<init>(Realm.java:164)
       at io.realm.Realm.createInstance(Realm.java:435)
       at io.realm.RealmCache.doCreateRealmOrGetFromCache(RealmCache.java:342)
       at io.realm.RealmCache.createRealmOrGetFromCache(RealmCache.java:282)
       at io.realm.Realm.getInstance(Realm.java:364)
       at my.app.package.realm.RealmTemplate.lambda$observeObject$3(RealmTemplate.java:72)
       at my.app.package.realm.-$$Lambda$RealmTemplate$Qgzvh8wX0nrAdIuh0uNxSlktAfQ.apply(lambda)
       at io.reactivex.internal.operators.flowable.FlowableSwitchMap$SwitchMapSubscriber.onNext(FlowableSwitchMap.java:114)
       at io.reactivex.internal.operators.flowable.FlowableObserveOn$ObserveOnSubscriber.runAsync(FlowableObserveOn.java:400)
       at io.reactivex.internal.operators.flowable.FlowableObserveOn$BaseObserveOnSubscriber.run(FlowableObserveOn.java:176)
       at io.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:109)
       at android.os.Handler.handleCallback(Handler.java:815)
       at android.os.Handler.dispatchMessage(Handler.java:104)
       at android.os.Looper.loop(Looper.java:207)
       at android.os.HandlerThread.run(HandlerThread.java:61)

But in migration, I do not touch upon the realm object Relation. My migration:

final class Migration implements RealmMigration {
    private static final int MIN_DB_VERSION_FOR_MIGRATION = 11;
    private static final String CLASS_APP_INSTANCE = "AppInstance";

    @Override
    public void migrate(@NonNull final DynamicRealm realm, final long originalVersion, final long newVersion) {
        Timber.d("Start migration flow from DB v.%s to v.%s", originalVersion, newVersion);
        long oldVersion = originalVersion;

        if (oldVersion < MIN_DB_VERSION_FOR_MIGRATION) {
            final IllegalStateException exception = new IllegalStateException("Migration is unsupported for DB v." + originalVersion);
            Timber.w(exception);
            throw exception;
        } else {
            // Access the Realm schema in order to create, modify or delete classes and their fields.
            final RealmSchema schema = realm.getSchema();

            /*
            Migration v.11 -> v.12

            class House v.11                      ->            class House v.12
                                               add->            @Nullable String alert
            */
            if (oldVersion == MIN_DB_VERSION_FOR_MIGRATION) {
                Timber.d("Migrate DB from v.11 to v.12");
                //change schema
                schema.get("House").addField("alert", String.class);
                // Request missing data from server
                final DynamicRealmObject appInstance = realm.where(CLASS_APP_INSTANCE).findFirst();
                if (appInstance != null) {
                    appInstance.set("initialSyncDate", null);
                }
                oldVersion++;
            }

            /*
            Migration v.12 -> v.13

            class AppInstance v.12                ->            class AppInstance v.13
            String version                  rename->            String appVersion
            String versionCode              rename->            String appVersionCode
                                               add->            String osVersion
                                               add->            String osBuild
            */
            if (oldVersion == 12) {
                Timber.d("Migrate DB from v.12 to v.13");
                realm.delete(CLASS_APP_INSTANCE);
                //change schema
                schema.get(CLASS_APP_INSTANCE)
                        .renameField("version", "appVersion")
                        .renameField("versionCode", "appVersionCode")
                        .addField("osVersion", String.class)
                        .addField("osBuild", String.class);
                oldVersion++;
            }

            /*
            Migration v.13 -> v.14

            class PendingFilledField v.13         ->            class PendingFilledField v.14
            boolean deleted;                remove->

            class Address v.13                    ->            class Address v.14
            String geoLAT              change type->            Double geoLAT
            String geoLON              change type->            Double geoLON
            */
            if (oldVersion == 13) {
                Timber.d("Migrate DB from v.13 to v.14");
                //change schema
                schema.get("PendingFilledField")
                        .removeField("deleted");
                final RealmObjectSchema address = schema.get("Address");
                address.addField("geoLAT_tmp", Double.class)
                        .transform(obj -> {
                            if (RealmFieldType.STRING == obj.getFieldType("geoLAT")) {
                                final String oldGeoLAT = obj.getString("geoLAT");
                                if (oldGeoLAT != null) {
                                    obj.setDouble("geoLAT_tmp", Double.parseDouble(oldGeoLAT));
                                }
                            }
                        })
                        .removeField("geoLAT")
                        .renameField("geoLAT_tmp", "geoLAT");
                address.addField("geoLON_tmp", Double.class)
                        .transform(obj -> {
                            if (RealmFieldType.STRING == obj.getFieldType("geoLON")) {
                                final String oldGeoLON = obj.getString("geoLON");
                                if (oldGeoLON != null) {
                                    obj.setDouble("geoLON_tmp", Double.parseDouble(oldGeoLON));
                                }
                            }
                        })
                        .removeField("geoLON")
                        .renameField("geoLON_tmp", "geoLON");
                oldVersion++;
            }
        }
    }

    @Override
    public boolean equals(final Object o) {
        final boolean result;
        if (this == o) {
            result = true;
        } else {
            result = o != null && getClass() == o.getClass();
        }
        return result;
    }

    @Override
    public int hashCode() {
        return 42;
    }
}

Relation class:

public class Relation extends RealmObject {
    @PrimaryKey
    private Long id;
    private House house;
    @Nullable
    private String type;
    @Nullable
    private String status;
    @Nullable
    private String fio;
    @Nullable
    private String relationReason;
}

Version of Realm and tooling

Realm version(s): 5.1.0

Realm sync feature enabled: no

Realm configuration:

new RealmConfiguration
                .Builder()
                .name(realmName)
                .schemaVersion(Constants.DB_VERSION)
                .directory(dbDir)
                .migration(MIGRATION)
                .encryptionKey(key)
                .initialData(initTx)
                .build()

Android Studio version: 3.1.2

Which Android version and device:

  • Lenovo TAB3 730X, android 6.0
  • Redmi Note 4, android 7.0
  • Redmi 5 Plus, android 7.1.2

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:1
  • Comments:8 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
jpmcostacommented, Jun 11, 2020

I have “solved” my issue by ensuring that ids were sane by the end of the migration. Since I never depend on the ids for ordering nor I store them directly as a property in other tables, I didn’t have any other considerations. I’m not sure if this has any side effects on the relations between objects, but my main focus was on stopping the constant crashes.

private fun fixIds(realm: DynamicRealm, className: String) {
    val classSchema = realm.schema.get(className) ?: return
    if (!classSchema.isPrimaryKey("id")) return

    classSchema.removePrimaryKey()

    var maxId = realm.where(className).max("id")?.toLong() ?: NO_ID
    var previousId: Long? = null
    val objects = realm.where(className).sort("id", Sort.ASCENDING).findAll().createSnapshot()
    for (obj in objects) {
        val id = obj.getLong("id")
        if (id == NO_ID || id == previousId) {
            obj.set("id", ++maxId)
        }
        previousId = id
    }

    classSchema.addPrimaryKey("id")
}
0reactions
deeplathiacommented, Jun 9, 2020

Just had this same issue on version 5.0.1 after a migration. The only change that was made to the schema was:

   `final RealmObjectSchema baseProfileSchema = schema.get("DeviceModel");
        
    if (baseProfileSchema != null) {
            try {
                baseProfileSchema.addField("id", String.class);
            } catch (Exception e) {
                addErrorEvent();
            }
        }`

Here are the crash logs which are not at all related to the field that I’ve added:

Caused by java.lang.IllegalStateException: Primary key property 'CartAttributes.name' has duplicate values after migration. at io.realm.internal.OsSharedRealm.nativeGetSharedRealm(OsSharedRealm.java) at io.realm.internal.OsSharedRealm.<init>(OsSharedRealm.java:171) at io.realm.internal.OsSharedRealm.getInstance(OsSharedRealm.java:241) at io.realm.BaseRealm.<init>(BaseRealm.java:134) at io.realm.BaseRealm.<init>(BaseRealm.java:103) at io.realm.Realm.<init>(Realm.java:161) at io.realm.Realm.createInstance(Realm.java:446) at io.realm.RealmCache.doCreateRealmOrGetFromCache(RealmCache.java:342) at io.realm.RealmCache.createRealmOrGetFromCache(RealmCache.java:282) at io.realm.Realm.getInstance(Realm.java:375)

Read more comments on GitHub >

github_iconTop Results From Across the Web

MySQL 1062 - Duplicate entry '0' for key 'PRIMARY'
A PRIMARY KEY is a unique index, so if it contains duplicates, you cannot assign the ... Run the following query in the...
Read more >
The attempt to add a row to the data flow task buffer with error ...
After migration and upgrade, a few SSIS packages started throwing errors like The ... Can't insert duplicate key in object Table XXX.
Read more >
Cannot Create XML Backup Due to ObjectNotFoundException ...
In this case, you can check the first line and see that the row has a primary key of 5. Each property is...
Read more >
SQLSTATE[23000]: Integrity constraint violation: 1062 ... - Drupal
PDOException : SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '' for key 2: INSERT INTO {file_managed} (filesize, ...
Read more >
Removing Duplicate Items from a Mailbox | EighTwOne (821)
For those involved with Exchange migration projects or managing ... Note that PidTagSearchKey will have duplicate values for copied objects.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found