So, as you know, iOS 8 finally brought IndexedDB to Mobile Safari. I may be biased, but I find features like this far more useful than CSS updates. Not to say that I don't appreciate them, but to me, deep data storage on the client is something that is more practical and useful to more people. Of course, I work for a company that is all about designers and not developers, so what do I know? ;)
Unfortunately, it seems as if Apple may have screwed up their implementation of IndexedDB - and screwed it up bad. Like real bad. If you read the SO post I linked to above, you will see that he was using assigned IDs and discovered that if you assigned the same ID to data in two datastores, then the data inserted in the first objectstore is removed. Let me restate that just to be obvious.
Imagine you have two object stores, people and beer. You want to add an object to both, and in both cases, you use a hard coded primary key of 1. When you do, no error is thrown, but the person object is deleted. Only beer remains. (Not the worst result...) Here is a full example showing this bug in action.
<!doctype html>
<html>
<head>
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height" />
</head>
<body>
<script>
var db;
function indexedDBOk() {
return "indexedDB" in window;
}
document.addEventListener("DOMContentLoaded", function() {
//No support? Go in the corner and pout.
if(!indexedDBOk()) return;
var openRequest = indexedDB.open("ios8b",1);
openRequest.onupgradeneeded = function(e) {
var thisDB = e.target.result;
console.log("running onupgradeneeded");
if(!thisDB.objectStoreNames.contains("people")) {
thisDB.createObjectStore("people", {keyPath:"id"});
}
if(!thisDB.objectStoreNames.contains("notes")) {
thisDB.createObjectStore("notes", {keyPath:"uid"});
}
}
openRequest.onsuccess = function(e) {
console.log("running onsuccess");
db = e.target.result;
console.log("Current Object Stores");
console.dir(db.objectStoreNames);
//Listen for add clicks
document.querySelector("#addButton").addEventListener("click", addPerson, false);
}
openRequest.onerror = function(e) {
//Do something for the error
}
},false);
function addPerson(e) {
console.log("About to add person and note");
var id = Number(document.querySelector("#key").value);
//Get a transaction
//default for OS list is all, default for type is read
var transaction = db.transaction(["people"],"readwrite");
//Ask for the objectStore
var store = transaction.objectStore("people");
//Define a person
var person = {
name:"Ray",
created:new Date().toString(),
id:id
}
//Perform the add
var request = store.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");
}
//Define a note
var note = {
note:"note",
created:new Date().toString(),
uid:id
}
var transaction2 = db.transaction(["notes"],"readwrite");
//Ask for the objectStore
var store2 = transaction2.objectStore("notes");
//Perform the add
var request2 = store2.add(note);
request2.onerror = function(e) {
console.log("Error",e.target.error.name);
//some type of error handler
}
request2.onsuccess = function(e) {
console.log("Woot! Did it");
}
}
</script>
enter key: <input id="key"><br/>
<button id="addButton">Add Data</button>
</body>
</html>
This demo uses a simple form to ask you for a PK. When you click the button, it then adds a static person and note object using the value you gave for a PK. When you run this, no error is thrown. The success handler for both operations is run. But the data you created for the person is gone. This is horrible.
But wait! Who uses defined primary keys? Only nerds! I like auto incrementing keys, so why not just switch to that? Simple enough, right? I made a new demo, with a new database, and modified my objectstores:
if(!thisDB.objectStoreNames.contains("people")) {
thisDB.createObjectStore("people", {autoIncrement:true});
}
if(!thisDB.objectStoreNames.contains("notes")) {
thisDB.createObjectStore("notes", {autoIncrement:true});
}
And the same damn error occurs. I kid you not. Ok, fine iOS. So I then tried something else. According to the spec, you can create a transaction with multiple objectstores. I thought, maybe if I did that, iOS would handle the inserts better. So let's try this:
var transaction = db.transaction(["people","notes"],"readwrite");
But this threw an error: DOM IDBDatabase Exception 8: An operation failed because the requested database object could not be found.
Ok, so next I thought - what if we used autoIncrement and different key names. Maybe the key name being the same was confusing things:
if(!thisDB.objectStoreNames.contains("people")) {
thisDB.createObjectStore("people", {autoIncrement:true,keyPath:"appleisshit"});
}
if(!thisDB.objectStoreNames.contains("notes")) {
thisDB.createObjectStore("notes", {autoIncrement:true,keyPath:"id"});
}
Nope, same error. So... finally I gave up. I specified an ID number and prefixed it with a string.
function addPerson(e) {
console.log("About to add person and note");
var id = document.querySelector("#key").value;
//Get a transaction
//default for OS list is all, default for type is read
var transaction = db.transaction(["people"],"readwrite");
//Ask for the objectStore
var store = transaction.objectStore("people");
//Define a person
var person = {
name:"Ray",
created:new Date().toString(),
id:"people/"+id
}
//Perform the add
var request = store.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");
}
//Define a note
var note = {
note:"note",
created:new Date().toString(),
uid:"notes/"+id
}
var transaction2 = db.transaction(["notes"],"readwrite");
//Ask for the objectStore
var store2 = transaction2.objectStore("notes");
//Perform the add
var request2 = store2.add(note);
request2.onerror = function(e) {
console.log("Error",e.target.error.name);
//some type of error handler
}
request2.onsuccess = function(e) {
console.log("Woot! Did it");
}
}
This worked. Of course, you still have the suck part of creating your own keys. You can, however, ask the objectstore for the size and simply increment yourself. I wrote up a new version that does this. This seems to work well and for now is what I'd recommend. It works fine in Chrome too so it isn't "harmful" to use this workaround.
<!doctype html>
<html>
<head>
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height" />
</head>
<body>
<script>
var db;
function indexedDBOk() {
return "indexedDB" in window;
}
document.addEventListener("DOMContentLoaded", function() {
//No support? Go in the corner and pout.
if(!indexedDBOk()) return;
var openRequest = indexedDB.open("ios8_final3",1);
openRequest.onupgradeneeded = function(e) {
var thisDB = e.target.result;
console.log("running onupgradeneeded");
if(!thisDB.objectStoreNames.contains("people")) {
thisDB.createObjectStore("people", {keyPath:"id"});
}
if(!thisDB.objectStoreNames.contains("notes")) {
thisDB.createObjectStore("notes", {keyPath:"uid"});
}
}
openRequest.onsuccess = function(e) {
console.log("running onsuccess");
db = e.target.result;
console.log("Current Object Stores");
console.dir(db.objectStoreNames);
//Listen for add clicks
document.querySelector("#addButton").addEventListener("click", addPerson, false);
}
openRequest.onerror = function(e) {
//Do something for the error
}
},false);
function addPerson(e) {
console.log("About to add person and note");
//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");
}
}
//Define a note
var note = {
note:"note",
created:new Date().toString(),
}
db.transaction(["notes"],"readwrite").objectStore("notes").count().onsuccess = function(event) {
var total = event.target.result;
console.log(total);
note.uid = "notes/" + (total+1);
var request = db.transaction(["notes"],"readwrite").objectStore("notes").add(note);
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");
}
}
}
</script>
<button id="addButton">Add Data</button>
</body>
</html>
I hope this helps folks. As I said, maybe I'm being stupid and missing something obvious. I hope so. But considering that iOS 8 also broke file uploads (both "regular" and via XHR2), it isn't too surprising that this could be broken as well. I'm going to file a bug report now. If their reporting system supports sharing the URL, I'll do so in a comment.
Archived Comments
"I work for a company that is all about designers and not developers, so what do I know?" <= Oh no he didn't!!!
(honestly though - good to know)
I submitted it to bugreport.apple.com. The ID is 18454000. From what I can see though there is no way to share this publicly.
I filed a bug report yesterday, I got id: 18440001 so assuming the bug numbers run sequentially, 13999 bug reports have been filed in just under 23 hours. Ouch!
Is that you, johnnyknowsbest?
It is me indeed. If you want to stick a link to this blog article on SO, im more than happy to have this as the accepted answer.
SO doesn't like that because, apparently, links are "bad" and the entire Internet doesn't make sense. Seriously. (Ok, maybe I'm not being fair, and yes, links do die, but it pisses me off that SO takes an attitude like that. But - whatevs. ;) I just posted a summary though as an answer.
SO etiquette requires pasting the relevant portion of the blog post into the answer, but last I checked, it is still fine to link to the source blog post, regardless of whether or not you wrote it.
Yeah Adam - sorry - I was being a bit of a prick there.
As an update, versionchange events may be broken too. In my test, it worked, but he had an open tab and expected to receive the event in tab 1 when tab 2 changed the db. That *should* work, absolutely, but is probably not something you have to worry about normally.
Bug: https://bugs.webkit.org/sho...
I haven't gotten around to using IndexedDB in iOS yet, but I'm glad I came across your post to avoid some of the inevitable frustration when that happens.
iOS8 is a trainwreck from a quality standpoint. Luckily, the file uploads/File API issue you mentioned at the end of your post appears to be fixed in iOS 8.0.2 (released today).
https://github.com/Widen/fi...
Yeah I heard. Pretty good turn around there.
The "DOM IDBDatabase Exception 8" bug has already been filed on WebKit twice:
* https://bugs.webkit.org/sho...
* https://bugs.webkit.org/sho...
However, the fact that you can't store the same ID in two object stores seems to be a new find. Did you report that one on the WebKit Bugzilla as well? I think they may respond faster than the general Apple issue tracker.
Sorry, there should be an underscore between "show" and "bug" in those URLs I pasted.
That's not your fault, my blogware does some parsing on stuff and *should* ignore URLs but does not.
I will file it at bugs.webkit.org.
Bug files here: https://bugs.webkit.org/sho...
Going to try to fix the rendering bug too. (I mean in my comments.)
Fixed it. Had to do some fancy regex. Well, not fancy, but... meh - no one cares. ;)
I've been fighting with this for days .. trying to figure out why my records keep dissapearing! Thanks very much for this post.
IndexedDB is not working on the homescreen (as pinned website) at all!
Ugh. John, please file a bug report.
You can also file the same bug report at http://www.openradar.me/
@Ray any idea when they might be fixing the primary key problem? I've been looking at the bug reports and trying to google up results but haven't found anything indicative. Have you? Anyone else?
Sorry, I haven't heard anything.
Bug was reported (marked as duplicate).
iOS 8.1 comes today. We'll see (fat chance!) xD
Did ios8.1 solve anything here?
With me IndexedDB does not work.
I *think* I tested and it failed - but I don't remember and I'm out of country - anyone else in this thread?
Would anyone know if the latest iOS update has fixed this issue? Cannot find any report on it, and fwiw, caniuse.com still points it out as broken with a reference to this blog post. Thanks,
Thanks to this catalog of bugs, I was able to get pretty far on an IndexedDB project. Now I found another bug: https://bugs.webkit.org/sho...
When the elements of a multiEntry index array are themselves arrays, Safari seems to flatten them and concat across multiple rows.
Beware!
Ugh. It's like Apple hired the intern to add IDB support. The drunk intern.
Hi Chris! Small world.
I was going to use the primary key work around to be able to go ahead with using indexedDB for our app, but it's really seeming like that will not be a great until they fix more of the problems.
Anyone using it in a production app?
Brian
We have web-based software running fine on Windows, Android and OSX. But IOS8.1 keeps loading the data forever. Not sure what is going on.
Did Apple acknowledge the "bugs", do we have any indication that they are working to repair them?
I believe Apple's bug reports are public - right?
Update to my last comment: it's actually worse than that, any arrays as keys are a recipe for crazy cursor behavior. Bug report updated.
I installed ios 8.1.1, but my IndexedDB data keep loading forever. Anybody doing better?
I just ran into this bug. After an hour of debugging, I finally found this post. Turns out it's not my code. Thanks for the info.
Is this fixed and released? https://bugs.webkit.org/sho...
I'm having a bit of trouble following what they mean.
My take is that it is fixed but not yet released.
For anyone wondering about other storage bugs in iOS 8:
Homescreen apps
* WebSQL doesn't respond after standby: https://bugs.webkit.org/sho... / https://bugs.webkit.org/sho...
UIWebView
* IndexedDB set to null, so unshimmable: https://bugs.webkit.org/sho...
WKWebView
* WebSQL disallowed (DOM Exception 18): https://bugs.webkit.org/sho...
Jesus. Well, thanks for sharing.
New one: using WebSQL in Chrome for iOS intermittently throws a DOM Exception 18 or DOM Exception 11: https://bugs.webkit.org/sho...
Sorry for flooding your blog with these, but currently this blog post is the most comprehensive resource for storage bugs in iOS 8. :)
I absolutely do not mind and *please* continue to share.
But your PouchDB works in iOS and Android? Thanks!
Err, is that a question? A statement? From what I know of PouchDB, it is a JavaScript library that enables storage and db syncing. It does not, obviously can not, modify the browser to support anything the browser can't support.
Looks like it does! http://pouchdb.com/2014/09/...
Well, it looks like it falls back to WebSQL. That's not a fix. ;)
A wink is the same as nod to a blind horse. :D
Heh, fair enough.
indexeddb still broken in IOS 8.1.3
Another workround which seems to work is to use a separate db for each table.
Has anyone else tried this ?
There's no way to share Apple's Radar bug reports directly, but some enterprising souls built OpenRadar <http: openradar.appspot.com=""> where developers can voluntarily post public copies of their Radar bug reports.
Here is a workaround I created and have been using. It's kind of complicated but it uses the table name and original key to create a new unique key for every object you save, avoiding this bug.
http://rebrandsoft.blogspot...
Still broken in iOS 8.2.
Damn, I came here to ask that specific question :( Hopefully Apple will fix this madness soon.
Still broken in 8.3 beta too.
Word to the wise: use `oncomplete` handlers when dealing with iOS8. Apparently FF and Chrome have a different approach to committing transactions than in iOS8 WebKit. Using `complete` events seems the only way to appease all parties. But even then, I've been having a good deal of trouble with creating indexes. I've documented my issues for those facing an obscure iOS8-only error message such as "DOM IDBDatabase Exception 8: An operation failed because the requested database object could not be found." https://bugs.webkit.org/sho...
How about 8.3 release that just came out today?
A user on Twitter, @nolanlawson, reported that it is NOT fixed.
Still broken in WebKit nightly r183930, Safari 8.0.5 (10600.5.17) on OS X 10.10.4, and in Mobile Safari on iOS 8.4 (beta?). :S
Your work arounds do still work though, encouragingly.
Is this an issue on iOS only? What about OSX? Why do they use a different implementation?
It is both desktop Safari and mobile Safari. I don't really think about desktop Safari as I only use it to watch Apple keynotes.
From my quick testing, it looks like iOS9 has the fix.
But - see Nolan's testing here - more stuff broken: https://gist.github.com/nol...
I'm sure that I can predict the answer... but does iOS 8.4 fix IndexedDB?
I didn't test 8.4, just 9. ANyone else?
I did some researching to find out when bugfixes will be available on iOS.
For the bug "IndexedDB is deleting data when a PK is shared amongst two objectStores",
the fix's changeset was on 2014-10-30.
http://trac.webkit.org/chan...
WebKit version 600.1.4 is used though iOS beta 5 to iOS 9.2 beta 2(on going), and the corresponding WebKit changeset was on 2014-08-01.
http://trac.webkit.org/chan...
Looks like no luck for this fix on iOS 8 :(
Here is where I found these information.
https://github.com/uupaa/We...
Unfortunately it is in Japanase(my native language), but has many useful information such as:
- It is unlikely that Mobile Safari gets updates in minor iOS updates.
- The version of Mobile Safari for iOS 9 would probably be fixed somewhere around August 2015.
The site also has a list of expected changesets relating to IndexedDB for iOS 9
https://github.com/uupaa/We...
Hope this information helps someone.
Thanks for sharing this, Kenji.
I've also been trying to keep track of the status of all iOS IndexedDB/WebSQL bugs here: https://gist.github.com/nol...
Thanks! And shocked to see so many bugs still in iOS 9...