W3C home > Mailing lists > Public > public-webapps@w3.org > July to September 2010

Re: [IndexedDB] Implicit transactions

From: Jonas Sicking <jonas@sicking.cc>
Date: Thu, 5 Aug 2010 12:56:40 -0700
Message-ID: <AANLkTi=H+sJhS855Hv5QqOixNX2Uf9eHTk1Z=O9BV4tE@mail.gmail.com>
To: Jeremy Orlow <jorlow@chromium.org>
Cc: Shawn Wilsher <sdwilsh@mozilla.com>, public-webapps WG <public-webapps@w3.org>
Ok, I'm going to start by taking a step back here.

There is no such thing as implicit transactions.

db.objectStore("foo", mode)

is just syntactic sugar for

db.transaction(["foo"], mode).objectStore("foo")

so it always starts a new transaction. I think for now, lets take
db.objectStore(..) out of the discussion and focus on how we want
db.transaction() to work. In the end we may or may not want to keep
db.objectStore() if it causes too much confusion.

One thing that we have to first realize is that every IDBObjectStore
instance is tied to a specific transaction. This is required to avoid
ambiguity in what transaction a request is made against. Consider the
following code

trans1 = db.transaction(["foo", "bar"], READ_WRITE);
trans2 = db.transaction(["foo", "students"], READ_ONLY);
os1 = trans1.objectStore("foo");
os2 = trans2.objectStore("foo");
alert(os1 === os2);
os1.get("someKey").onsuccess = ...;

In this code, the alert will always display "false". The os1 and os2
are two distinct objects. They have to be, otherwise we wouldn't know
which transaction to place the get() request against.

Once a transaction has been committed or aborted, using any of the
IDBObjectStore objects connected with it will throw an error. So the
example mentioned earlier in the thread (i'll use different syntax
than used previously in the thread):

var gMyos = null;
function fun1() {
  gMyos = db.transaction(["foo"]).objectStore("foo");
  gMyos.get("someKey").onsuccess = ...;
}
function fun2() {
  gMyos.get("someOtherKey");
}

If we return to the main even loop between calling fun1 and fun2, the
.get() call in fun2 will *always* throw. IMHO it's a good thing that
this consistently throws. Consider also

function fun3() {
  var trans = db.transaction(["foo", "bar"], READ_WRITE);
  trans.objectStore("bar").openCursor(...).onsuccess = ...;
}

It would IMHO be a bad thing if calling fun3 right before calling fun2
all of a sudden made fun2 not throw and instead place a request
against the transaction created in fun3.

While I definitely think it can be confusing that there are several
IDBObjectStore instances referring to the same underlying objectStore,
I think this is ultimately a good thing as it reduces the risk of
accidentally placing a request against the wrong transaction. It means
that in order to place a request against a transaction, you must
either have a reference to that transaction, or a reference to an
objectStore retrieved from that transaction.

Another way to think of it is this. You generally don't place requests
against an objectStore or index. You place them against a transaction.
By tying IDBObjectStores to a given transaction, it's always explicit
which transaction you are using.

On Thu, Aug 5, 2010 at 3:04 AM, Jeremy Orlow <jorlow@chromium.org> wrote:
> On Wed, Aug 4, 2010 at 7:47 PM, Shawn Wilsher <sdwilsh@mozilla.com> wrote:
>>
>>  On 8/4/2010 10:53 AM, Jeremy Orlow wrote:
>>>
>>>
>>> Whoa....transaction() is synchronous?!?  Ok, so I guess the entire
>>> premise
>>> of my question was super confused.  :-)
>>>
>> It is certainly spec'd that way [1].  The locks do not get acquired until
>> the first actual bit of work is done though.
>
> I fully understand how the trick works.  I just didn't comprehend the fact
> that the Mozilla proposal (what's now in the spec) was removing any way to
> get into an IDBTransactionEvent handler besides doing an initial data
> access.  I wouldn't have agreed to the proposal had I realized this.
> Lets say I had the following bit of initialization code in my program:
> var myDB = ...
> var myObjectStore = myDB.objectStore("someObjectStore");
> var myIndex = myObjectStore.index("someIndex");
> var anotherObjectStore = myDB.objectStore("anotherObjectStore");

As described above, grabbing references like this is not what you want
to do. If we were to allow this I think we would run a severe risk of
making it very hard to understand which transaction you are placing
requests against.

> And then I wanted to start a transaction that'd access some key and then
> presumably do some other work.  As currently specced, here's what I'd need
> to do:
> myDB.transaction().objectStore("someObjectStore").index("someIndex").get("someKey").onsuccess(function()
> {
>     anotherObjectStore.get("someOtherKey").onsuccess(...);
> });
> vs doing something like this:
> myDB.asyncTransaction().onsuccess(function() {
>     myIndex.get("someKey").onsuccess(function() {
>         anotherObjectStore.get("someOtherKey").onsuccess(...);
>     });
> });
> With the former, we actually have more typing and the code is harder to
> read.  Sure, when I'm writing short code snipits, the synchronous form can
> be more convenient and readable, but forcing this upon every situation is
> going to be a hinderance.
> Please, lets add back in a transaction method that returns an IDBRequest.

The current design of the spec is that which IDBObjectStore you place
a request against determines which transaction you are using.

It sounds to me like you want a design where each IDBObjectStore
instance represents just a objectStore name, and depending on *when*
you place a request against it determines which transaction is used.

To me the latter is a much more subtle and risky. Especially if we
*also* want to have the ability to synchronously start transactions.
Which transaction would the following code snippets use for the .get()
request?

Example 1:
trans1 = db.transaction(["foo", "bar"], READ_WRITE);
trans2 = db.transaction(["foo", "fish"]);
myStashedFooObjectStoreReference.get("someKey").onsuccess = ...;

Example 2:
trans = db.transaction(["foo"], READ_WRITE);
var fooStore = trans.objectStore("foo");
fooStore.add("someKey", someValue).onsuccess = function(e) {
  trans2 = db.transaction(["foo", "bar"]);
  fooStore.get("someOtherKey");
  fooStore = trans2.objectStore("foo");
  fooStore.get("aThirdKey");
}

Basically if we want to make *when* you place a request determine
which transaction is used, rather than *which* IDBObjectStore you use,
then we can't allow transactions to be synchronously created. That
reason alone is enough to make me think that we should stick with the
current model in the spec.

Here is what your example above would look like:
trans = myDB.transaction(...);
trans.objectStore("someObjectStore").index("someIndex").get("someKey").onsuccess
= ...;
trans.objectStore("anotherObjectStore").get("someOtherKey").onsuccess = ...;

Which I think is simpler than either of your examples. Yes, it's a bit
more typing, but it's much less asynchronous nesting which is IMHO a
bigger advantage.

/ Jonas
Received on Thursday, 5 August 2010 19:57:34 GMT

This archive was generated by hypermail 2.3.1 : Tuesday, 26 March 2013 18:49:40 GMT