Dexie.js: change.recreate support (Changing primary key)

Created on 22 Apr 2015  路  11Comments  路  Source: dfahlander/Dexie.js

My application has the need to change a primary key on a particular table. Can you recommend a concise way of achieving this?

Thanks David!

backlog

Most helpful comment

Using IndexedDB natively does work, but took me some time to figure out. In case someone else stumbles on this issue, the code below will work. David, please comment if this code displays poor practice or anything that may break in a future Dexie change.

(function(){

    var database = new Dexie('Testing');


    // Original table whose primary key
    // will eventually need to change
    database
        .version(1)
        .stores({
            things: '&uuid'
        });



    // Version change whose sole purpose is
    // to delete the table. One could create
    // a temporary table upon this version
    // change to store "things" if that's
    // important to you.
    database
        .version(2)
        .upgrade(function(transaction){
            transaction.idbtrans.db.deleteObjectStore('things');
        });



    // Let Dexie take over and recreate the
    // table. This would also be a good place
    // to populate the table with the "things"
    // that you saved, then you can delete the
    // temporary table.
    database
        .version(3)
        .stores({
            things: '++id'
        });



    database.open();
    database.on('error', function(error){
        console.log('Oh no! - ' + error);
    });

})();

All 11 comments

Using IndexedDB natively does work, but took me some time to figure out. In case someone else stumbles on this issue, the code below will work. David, please comment if this code displays poor practice or anything that may break in a future Dexie change.

(function(){

    var database = new Dexie('Testing');


    // Original table whose primary key
    // will eventually need to change
    database
        .version(1)
        .stores({
            things: '&uuid'
        });



    // Version change whose sole purpose is
    // to delete the table. One could create
    // a temporary table upon this version
    // change to store "things" if that's
    // important to you.
    database
        .version(2)
        .upgrade(function(transaction){
            transaction.idbtrans.db.deleteObjectStore('things');
        });



    // Let Dexie take over and recreate the
    // table. This would also be a good place
    // to populate the table with the "things"
    // that you saved, then you can delete the
    // temporary table.
    database
        .version(3)
        .stores({
            things: '++id'
        });



    database.open();
    database.on('error', function(error){
        console.log('Oh no! - ' + error);
    });

})();

Ok, good that you solved it! My intention has originally been that this should just work by changing the primkey. The issue has been to keep existing data while doing this. Possible to achieve by copying data to another store before deleting it, but there's a bug in IE that makes deleteObjectStore() fail if having read from it.

I'll reopen this issue as a reminder to add a unit test for this use case.
Thanks a lot,
David

I had the same problem. Thanks to the code above I was able to get it working. However, in my case, I had to add the scope method in the second version, and explicitly set the store to null. Failure in donig so, resulted in a Failed to open db: UpgradeError: Not yet support for changing primary key. Does it make sense to call set things: null?

        .version(2)
        .stores({
            things: null
        })
        .upgrade(function(transaction){
            transaction.idbtrans.db.deleteObjectStore('things');
        });

Moin, I tried and migrated some data using this strategy and came across a strange bug/behaviour. Basically if i set a table null it is already gone in the previous upgrade steps. I'm using Dexie version 2.0.0-beta.10

  let db = new Dexie('TestDB');
  db
    .version(1)
    .stores({
      table1: '++id'
    });
  db
    .version(2)
    .stores({
      table1: null,
      table1_tmp: '++id'
    })
    .upgrade(async t => {
      let objects = await t.db.table1.toArray();
      // table1_tmp is undefined if it is set to null in the next version?!
      await t.db.table1_tmp.bulkPut(objects);
    });
  db
    .version(3)
    .stores({
      table1: '++id2',
      // setting table1_tmp to null here affects the previous upgrade step?!
      table1_tmp: null
    })
    .upgrade(async t => {
      // move objects back to table1
    });

Thanks for pointing out. @chrahunt is working on a rewrite of the update process that will do it step-by-step. I am hoping to get it in to 2.0 release, but can't promise that.

I am hoping to get it in to 2.0 release, but can't promise that.

That'd be great. I'm currently debugging the runUpgraders/updateTablesAndIndexesmethods to find a quick fix but the whole process is a little involved. The global tables array doesn't seem to match the objectStoreNames of the IDBTransaction in the upgrade routine of version 2. Any suggestions?

Ok, I gave up the debugging. Here is the workaround involving native IndexedDB access:

  let db = new Dexie('TestDB');
  db
    .version(1)
    .stores({
      table1: '++id'
    });
  db
    .version(2)
    .stores({
      table1: null,
      table1_tmp: '++id'
    })
    .upgrade(async t => {
      let objects = await t.table1.toArray();
      let tmpStore = t.idbtrans.objectStore('table1_tmp');
      objects.forEach(o => tmpStore.put(o));
    });
  db
    .version(3)
    .stores({
      table1: '++id2',
      table1_tmp: null
    })
    .upgrade(async t => {
      let objects = await new Promise<any[]>((resolve, reject) => {
        let request = t.idbtrans.objectStore('table1_tmp').getAll();
        request.onsuccess = () => resolve(request.result);
        request.onerror = () => reject(request.error);
      });
      objects.forEach(o => { o.id2 = o.id; delete o.id; });
      await t.table1.bulkPut(objects);
    });

Is there any update on this issue? Sadly the mentioned workaround are not working for me either (always receiving the UpgradeError: Not yet support for changing primary key error) when making a two step/versions upgrade and trying to null the table. Using Dexie v2.0.0-rc.1

My bad, I've had two tables that needed an update to the primary key. Making a two step upgrade with nulling the schema, it worked for me as well!

@sechel Thanks for the workaround, had to put it into a project right now at least. One remark about the workaround: request event properties are all lowercase (not camelCase), i.e. onsuccess and onerror.

@sim642 you are right, I corrected the code. One comment on the use of async here: If you take the workaround literally then it will not work in Firefox, you'll have to use native generator functions or plain promises for that.

The latest 3.0 release has improved upgrading support for this use case

https://github.com/dfahlander/Dexie.js/releases/tag/v3.0.0-alpha.3

(see Improved Database Upgrading (PR #714) under Details.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

fulltic picture fulltic  路  4Comments

oviniciuslara picture oviniciuslara  路  4Comments

asdip138 picture asdip138  路  3Comments

kamleshchandnani picture kamleshchandnani  路  4Comments

Script47 picture Script47  路  3Comments