[IndexedDB] Cursors and modifications

Hi All,

We ran into an complicated issue while implementing IndexedDB. In
short, what should happen if an object store is modified while a
cursor is iterating it? Note that the modification can be done within
the same transaction, so the read/write locks preventing several
transactions from accessing the same table isn't helping here.

Detailed problem description (this assumes the API proposed by mozilla):

Consider a objectStore "words" containing the following objects:
{ name: "alpha" }
{ name: "bravo" }
{ name: "charlie" }
{ name: "delta" }

and the following program (db is a previously opened IDBDatabase):

var trans = db.transaction(["words"], READ_WRITE);
var cursor;
var result = [];
trans.objectStore("words").openCursor().onsuccess = function(e) {
  cursor = e.result;
  result.push(cursor.value);
  cursor.continue();
}
trans.objectStore("words").get("delta").onsuccess = function(e) {
  trans.objectStore("words").put({ name: "delta", myModifiedValue: 17 });
}

When the cursor reads the "delta" entry, will it see the
'myModifiedValue' property? Since we so far has defined that the
callback order is defined to be the request order, that means that put
request will be finished before the "delta" entry is iterated by the
cursor.

The problem is even more serious with cursors that iterate indexes.
Here a modification can even affect the position of the currently
iterated object in the index, and the modification can (if i'm reading
the spec correctly) come from the cursor itself.

Consider the following objectStore "people" with keyPath "name"
containing the following objects:

{ name: "Adam", count: 30 }
{ name: "Bertil", count: 31 }
{ name: "Cesar", count: 32 }
{ name: "David", count: 33 }
{ name: "Erik", count: 35 }

and an index "countIndex" with keyPath "count". What would the
following code do?

results = [];
db.objectStore("people",
READ_WRITE).index("countIndex").openObjectCursor().onsuccess =
function (e) {
  cursor = e.result;
  if (!cursor) {
    alert(results);
    return;
  }
  if (cursor.value.name == "Bertil") {
    cursor.update({name: "Bertil", count: 34 });
  }
  results.push(cursor.value.name);
  cursor.continue();
};

What does this alert? Would it alert "Adam,Bertil,Erik" as the cursor
would stay on the "Bertil" object as it is moved in the index? Or
would it alert "Adam,Bertil,Cesar,David,Bertil,Erik" as we would
iterate "Bertil" again at its new position in the index?

We could say that cursors always iterate snapshots, however this
introduces MVCC. Though it seems to me that SNAPSHOT_READ already does
that.

We could also say that cursors iterate live data though that can be
pretty confusing and forces the implementation to deal with entries
being added and removed during iteration, and it'd be tricky to define
all edge cases.

It's certainly debatable how much of a problem any of these edgecases
are for users. Note that all of this is only an issue if you modify
and read from the same records *in the same transaction*. I can't
think of a case where it isn't trivial to avoid these problems by
separating things into separate transactions. However it'd be nice to
avoid creating foot-guns for people to play with (think of the
children!).

However we still need to define *something*. I would suggest that we
define that cursors iterate snapshots. It seems the cleanest for users
and easiest to define. And once implementations add MVCC support it
should be easy to implement. I think we've come up with a decent plan
for how to do implement it in sqlite even without proper MVCC, so it
should be doable even then.

/ Jonas

Received on Friday, 2 July 2010 23:01:00 UTC