Question

I have been unable to update a record when using the IndexedDB shim to Web SQL: https://github.com/axemclion/IndexedDBShim

I am also using IDBWrapper: https://github.com/jensarps/IDBWrapper

Despite me using these libraries, I believe that my question might still be relevant, and I might still be advised appropriately, as it could be related just to general usage of IndexedDB and Web SQL

I am implementing IndexedDB storage, using inline keys.

_store = new IDBStore({
    storeName: 'person',
    keyPath: 'PersonId',
    autoIncrement: true,
    onStoreReady: _loadPersons
});

When I save an object to the store...

_store.put(person, _loadPersons);

...it will generate an auto-incrementing key, and then store this key value automatically on the object, named after the 'keyPath' property, in this case PersonId.

This works great. If I inspect the IndexedDB storage in Chrome, there are 2 columns. Key (Key path: 'PersonId') and Value (containing the object).

If I want to update the same object, I just make sure it contains a property named PersonId, and has the same value as they key I want to save, and it will update.

However, when using the IndexedDB shim which saves to Web SQL (e.g. in Safari for Windows, or mobile browsers), when I load the objects in they do not contain the PersonId property at all.

For reference, the _loadPersons callback is as follows:

_loadPersons = function () {
    _store.getAll(_setPersons);
};

If I inspect the Web SQL storage in Safari, there are 3 columns, key (which is in the format 1-0, 1-1, 1-2), inc (1, 2, 3) and value (which contains a JSON string of the object, missing the PersonId property.

I'm not sure how the inline keys feature works in IndexedDB, but it seems that whatever it does, it doesn't happen in Web SQL.

How should I be referring to objects that I want to update, when using this approach?

Should I just use out-of-line keys?

Has anyone else implemented an IndexedDB -> Web SQL structure, if so what libraries did they use and did they come across this issue?

UPDATE:

I have looked at the code behind an example of the Indexed DB JQuery plugin using the IndexedDB polyfill: http://nparashuram.com/jquery-indexeddb/example/. However this example all the items already have an 'itemId', and it doesn't have any logic for adding new items, just moving existing items to different places (cart, wishlist), so I can only assume that this approach would be an 'out-of-line keys' approach.

UPDATE 2:

The way I resolved my situation, to enable me to work with IndexedDBShim and IDBWrapper, is detailed as an answer below. However probably the better solution is to ditch these two libraries and try something else.

Was it helpful?

Solution

Have you try my own open source library https://bitbucket.org/ytkyaw/ydn-db/wiki/Home It has full shimming from WebSQL to IndexedDB including inline/outline/autoIncrement/compound/multiEntry key. There is qunit test as well.

Edit, additional answers:

if you used autoIncrementing inline keys, and you didn't store the timeStamp on the object manually, would Web SQL know which object to update?

While autoIncrement inline key is used and a record value don't have primary key, it is considered as a new object (and it is). Note: Sqlite also has concept of auto increment numeric primary key.

Also it looks like in Web SQL every time you refresh the demo makes a new store?

Nop. If a new store is created on page refresh, it is not database.

OTHER TIPS

I came across the same issue while updating the objects. Couldn't do that using inline keys. So, I switched to out-of-line keys to solve the problem. I am using the combination of Jquery IndexedDB plugin plugin and IndexedDB Shim to support all the major browsers.

I ran into the same problem today. I've patched the shim to include the autogenerated id in the record. I've added my patch to issue 122 at https://github.com/axemclion/IndexedDBShim/issues/122

The way I resolved this was to:

  1. Always set the keyPath to be "key" when creating the IDBStore. This is to take into account the fact that the IndexedDB Shim was using the property called key to refer to the index of each record in the WebSQL database.

  2. I had to add a clause in the IDBWrapper code:

      // cope with WebSQL implementation, keyPath must always be "key" for this to work.
      if (!cursor.primaryKey) {
        cursor.value.key = cursor.key;
      }
    

Into the _getAllCursor function as shown (line 682 for me):

_getAllCursor: function (getAllTransaction, store, onSuccess, onError) {
  var all = [],
      hasSuccess = false,
      result = null;

  getAllTransaction.oncomplete = function () {
    var callback = hasSuccess ? onSuccess : onError;
    callback(result);
  };
  getAllTransaction.onabort = onError;
  getAllTransaction.onerror = onError;

  var cursorRequest = store.openCursor();
  cursorRequest.onsuccess = function (event) {
    var cursor = event.target.result;
    if (cursor) {
      // cope with WebSQL implementation, keyPath must always be key for this to work.
      if (!cursor.primaryKey) {
        cursor.value.key = cursor.key;
      }
      all.push(cursor.value);
      cursor['continue']();
    }
    else {
      hasSuccess = true;
      result = all;
    }
  };
  cursorRequest.onError = onError;
},

Basically what this is doing is, if there is no primaryKey on cursor, from testing with Chrome and Safari, this seemed to be an indicator that the code was working within a WebSQL environment. There may be more solid ways of detecting a lack of IDB support, but this works well for me. So within the if, just set the 'key' property of the cursor onto the value so that when it ends up being saved to WebSQL it has the right index on there.

However because I don't have the time to test his library and changeover to it, and he was very helpful, I am going to give Kyaw Tun the benefit of the doubt and accept his answer, especially as it appears that IDBWrapper and IndexedDBShim (and JQuery IndexedDB plugin and IndexedDBShim, according to GemK) don't appear to work together when using inline keys. Arguably it is nicer to have 1 dependent library rather than 2.

UPDATE

I did run into problems further down the line with this approach, in one of those "how did this ever work?" scenarios.

Just as I was about to remove IndexedDBShim and IDBWrapper, and replace with ydn-db, I had another look in Safari.

As mentioned, I have no idea how this ever worked, but in

IDBOjectStore.prototype.put on line 1090 (for me) of IndexedDBShim.js, the key was undefined and then never used in an update.

So I had to add

if (key === undefined && value.key !== undefined) {
  key = value.key;
}

UPDATE 2

Really, just ignore this post now, looks like IDBShim is going to be fixed, I am sure that will be a better solution. However, this solution does have another issue. The check against cursor.primaryKey isn't definitive, sometimes I have found it does contain the key value in there. So just set cursor.value.key anyway.

UPDATE 3

In case anyone has not heeded my earlier warning to ignore all this. If you set the key manually it will fail, silently in my case, because if the key is set and keypath also has a value, WebSQL cannot determine if you want inline keys (i.e. you defined keypath) or out-of-line keys (i.e. you actually provided a key value, so presumably you don't want to auto-increment). It is a misconception that to update the record you need to provide the key outside of the object value, as long as the object value you are saving has a property named after the keypath string, containing the correct key, that should be enough.

Saying this, why did I make the change detailed in the first UPDATE originally? Because on ocassion, duplicate records were being added instead of being updated.

The real problem was, for me on line 1003. The primary key is extracted from the object, and then evaluated using !. For me, in some browsers, the first key was being generated as 0. This fails the evaluation and therefore WebSQL generates a new auto-increment key.

I changed:

var primaryKey = eval("value['" + props.keyPath + "']");
if (!primaryKey) {
    if (props.autoInc === "true") {
        getNextAutoIncKey();
    }
    else {
        idbModules.util.throwDOMException(0, "Data Error - Could not eval key from keyPath");
    }
}
else {
    callback(primaryKey);
}

To:

var primaryKey = eval("value['" + props.keyPath + "']");
if (primaryKey === undefined) {
    if (props.autoInc === "true") {
        getNextAutoIncKey();
    }
    else {
        idbModules.util.throwDOMException(0, "Data Error - Could not eval key from keyPath");
    }
}
else {
    callback(primaryKey);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top