Primary Key issue on iOS8 implementation of IndexedDb
Asked Answered
A

2

30

The issue is when you have two different object stores in the same indexeddb, primary key values appear to be "shared" across all stores.

<body>
    <script type="text/javascript">
        //prefixes of implementation that we want to test
window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;

//prefixes of window.IDB objects
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange

if (!window.indexedDB) {
    window.alert("Your browser doesn't support a stable version of IndexedDB.")
}


var db;
var request = window.indexedDB.open("newDatabase", 4);

request.onerror = function(event) {
  console.log("error: ");
};

request.onsuccess = function(event) {
  db = request.result;
  console.log("success: "+ db);
};

request.onupgradeneeded = function(event) {
        var db = event.target.result;
        var objectStore = db.createObjectStore("customers", {keyPath: "arseid"});
    var objectStore = db.createObjectStore("test", {keyPath: "id"});
}



function add1() {
        var x = new Date();
    var h1 = x.getHours();
    var m1 = x.getMinutes();
    var s1 = x.getSeconds();
    console.log('starting insert on ' +  h1 + ':' + m1 + ':' + s1);

    var tx = db.transaction(["customers"], "readwrite");
    for (var i = 0; i < 1000; i++) {
        var request = tx.objectStore("customers")
                .put({ arseid: i, name: "Jonathan Smith", email: "[email protected]", favourite: "chocolate cake", pet: "rudolph the red nose reindeer", address: "999 letsbe avenue, townton, countyshire" });
    }


    tx.oncomplete = function (e) {
            // Re-render all the todo's
            var x2 = new Date(); 
            var h2 = x2.getHours(); 
            var m2 = x2.getMinutes(); 
            var s2 = x2.getSeconds(); 
               console.log('transaction complete ' + h2 + ':' + m2 + ':' + s2);
        }
}


function add2() {
    //tx 2
    var tx2 = db.transaction(["test"], "readwrite");
    for (var i = 0; i < 1000; i++) {
        var request2 = tx2.objectStore("test")
                .put({ id: i, name: "Robwin Mwengway", email: "[email protected]", favourite: "chocolate cake", pet: "rudolph the red nose reindeer", address: "999 letsbe avenue, townton, countyshire" });
    }

    tx2.oncomplete = function (e) {
            var x3 = new Date(); 
            var h3 = x3.getHours(); 
            var m3 = x3.getMinutes(); 
            var s3 = x3.getSeconds(); 
               console.log('transaction complete ' + h3 + ':' + m3 + ':' + s3);
        }
}


    </script>
<button onclick="add1()">Add1 data to indexedDb</button>
<button onclick="add2()">Add2 data to indexedDb</button>
</body>

(Fiddle: http://jsfiddle.net/jonnyknowsbest/4pdp8vxe/)

In iOS8, if you run up the fiddle and click "Add1 data to IndexedDb", then 1000 entries get added to the "customers" table. If you then click "Add2 data to IndexedDb", then 1000 entries get added to the "suppliers" table, but the 1000 from the "customers" is removed.

Has anyone else come across this? Is this part of the IndexedDb specification? Chrome does not seem to have this problem.

EDIT: Found this W3 Org IndexedDB Recommendation: "There can never be multiple records in a given object store with the same key." Apple seem to have applied this at the database level.

Alister answered 24/9, 2014 at 14:13 Comment(14)
Wow, that's bad. I haven't tried iOS 8 yet, but have gotten some reports from users that my iDB-based app does not run at all in iOS 8, and this would explain it. I can't say I'm surprised, it fits in with my conspiracy theory https://mcmap.net/q/500931/-alternative-to-indexeddb-in-not-supported-browsers-safari-ios-saf-chrome :)Yod
Jeeze. Even if you let it specify autoIncreement, it seems to be broken.Bassorilievo
Ugh. I tried to fix it by using one transaction - you can specify N objectstores in one transaction. Nope, throws an error.Bassorilievo
autoIncrement seems to fail as well.Bassorilievo
As far as I can see, you must specify keys and you must make them unique per db. Going to write this up in a blog post today.Bassorilievo
@Raymond, but do you agree that this does seem to be in conflict with what the W3 Consortium Recommendation states?Alister
Oh God yes. In multiple ways. I've got a workaround I'm about to blog. I'm at raymondcamden.com. It should be up in 30 minutes.Bassorilievo
Posted my workaround here: Blog Post: iOS8 IndexedDB critical error and work around, comments welcome: raymondcamden.com/2014/9/25/IndexedDB-on-iOS-8--Broken-BadBassorilievo
Btw, if you like my workaround, let me know. I'll post it as an answer below so I can get SO brownie points.Bassorilievo
without sounding too picky, i am on twitter as @JohannthanSmyth. Just going to read it fully now.Alister
I was using @ to mean your SO user here :)Bassorilievo
Sorry, my common sense radar is WAY off today.Alister
@RaymondCamden that workaround seems spot on to me. Our team implemented something similar. I hope Apple fix all these issues quickly.Alister
thx - posting it belowBassorilievo
B
20

I can confirm that iOS8 is definitely buggy here. I tried a few workarounds, but the best I can suggest is a primary key that combines some unique string, like the name of the objectStore, with a number. So for example, given two objectStores called people and notes, I'd store data with keys like so:

people/X notes/X

You can set X manually, or, use the .count() method on the objectStore to find the count and add one. Here is an example:

//Define a person
var person = {
    name:"Ray",
    created:new Date().toString(),
}

//Perform the add
db.transaction(["people"],"readwrite").objectStore("people").count().onsuccess = function(event) {
    var total = event.target.result;
    console.log(total);
    person.id = "person/" + (total+1);

    var request = db.transaction(["people"],"readwrite").objectStore("people").add(person);

    request.onerror = function(e) {
        console.log("Error",e.target.error.name);
        //some type of error handler
    }

    request.onsuccess = function(e) {
        console.log("Woot! Did it");
    }

}

Note that I specified keyPath of "id" for this OS.

Bassorilievo answered 25/9, 2014 at 14:23 Comment(3)
Assuming iOS is using the SQLite-based implementation of IndexedDB with the code at github.com/WebKit/webkit/blob/master/Source/WebKit2/… , object data is stored in a 'Records' table with a 'key' field in a SQLite DB. The key field has a UNIQUE constraint and the store ID is not part of the key. Whoops (!). I'd guess that this would affect Safari in OS X 10.10 as well.Flocky
Looks like Apple checked in a fix a few days ago: github.com/WebKit/webkit/commit/…Gamesome
Seems like it's fixed in iOS 9. See: gist.github.com/nolanlawson/08eb857c6b17a30c1b26Orleanist
F
1

I had a similar problem however my first insert to the objectstore was a small array with just username and email and the second objectstore was very large with several nested arrays of data.

My insert method below would call back success on the all items however only the second object store would be written to the db correctly.

When I tried reversing the order I was writing to the database (writing the large objectstore items first and the username/email second) both objectstores were written correctly however the the primary keys were shared between the two objects stores. Pilots Primary Key: 1,2,3,4,5 AC Primary Key: 6,7,8...

function insert_GroupRecord(Record, Store){
    //update individual sync record
    var trans = dbGroup.transaction([Store],"readwrite");
    var store = trans.objectStore(Store);
    var request = store.put(Record);
    request.onsuccess = function(e){
        IOS_postMessage({Message:"SyncStatus", status:"Group_Insert "+Store});
    };
    request.onerror = function(e){
        GroupSyncERROR = true;
        //IOS_postMessage({Message:"SyncStatus", status:"GroupSyncFail "+Store});
    };

    request.onupgradeneeded = function(evt){
        var objectStore = evt.currentTarget.result.createObjectStore("AC",{ keyPath: "id", autoIncrement: true });
        objectStore.createIndex("ident", "ident", { unique: true });
        var objectStore2 = evt.currentTarget.result.createObjectStore("Pilots",{ keyPath: "id", autoIncrement: true });
        objectStore2.createIndex("chatname", "chatname", { unique: true });
        console.log("KFM_Group Upgrade Completed");

    };
}
Figwort answered 2/10, 2014 at 15:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.