W3C home > Mailing lists > Public > public-webapps@w3.org > January to March 2012

Re: [Bug 15434] New: [IndexedDB] Detail steps for assigning a key to a value

From: Jonas Sicking <jonas@sicking.cc>
Date: Tue, 24 Jan 2012 02:21:15 -0800
Message-ID: <CA+c2ei9WXKz5LZrcy1uTM_Y+yXjV-uMRpeyb_4R0_izVUURBsA@mail.gmail.com>
To: Joshua Bell <jsbell@chromium.org>
Cc: "public-webapps@w3.org" <public-webapps@w3.org>
On Mon, Jan 23, 2012 at 5:34 PM, Joshua Bell <jsbell@chromium.org> wrote:
> There's another edge case here - what happens on a put (etc) request to an
> object store with a key generator when the object store's key path does not
> yield a value, yet the algorithm below exits without changing the value.
>
> Sample:
>
> store = db.createObjectStore("my-store", {keyPath: "a.b", autoIncrement:
> true});
> request = store.put("value");
>
> 3.2.5 for "put" has this error case "The object store uses in-line keys and
> the result of evaluating the object store's key path yields a value and that
> value is not a valid key." resulting in a DataError.

The intent here was for something like:

store = db.createObjectStore("my-store", {keyPath: "a.b", autoIncrement: true});
request = store.put({ a: { b: { hello: "world" } });

In this case "4.7 Steps for extracting a key from a value using a key
path" will return the { hello: "world" } object which is not a valid
key and hence a DataError is thrown.

> In this case, "4.7
> Steps for extracting a key from a value using a key path" says "no value is
> returned", so that error case doesn't apply.

Yes, in your example that error is not applicable.

> "5.1 Object Store Storage Operation" step 2 is: "If store uses a key
> generator and key is undefined, set key to the next generated key. If store
> also uses in-line keys, then set the property in value pointed to by store's
> key path to the new value for key."
>
> Per the algorithm below, the value would not change. (Another example would
> be a keyPath of "length" and putting [1,2,3])
>
> Chrome's current behavior in this case is that the put (etc) call returns
> without raising an error, but an error event is raised against the request
> indicating that the value could not be applied. This would imply having the
> algorithm below return a success/failure indicator and having the steps in
> 5.1 abort if the set fails.
>
> Thoughts?

First off, I absolutely agree that we need to write an algorithm to
exactly define how it works when a keyPath is used to modify a value.
There are lots of edge cases here and it doesn't surprise me that the
different implementations have ended up doing different things.

But first, there seems to be at least two misconceptions in this thread.

First off, modifying a value to insert a keyPath can never run into
the situation when a value already exists. Consider the following:

store1 = db.createObjectStore("mystore1", { keyPath: "a.b",
autoIncrement: true });
store1.put({ a: { b: 12 }});
store2 = db.createObjectStore("mystore2", { keyPath: "length",
autoIncrement: true });
store2.put([1,2,3]);

The first .put call will insert an entry with key 12 since the key
already exists. So no modification will even be attempted, i.e. we'll
never invoke the algorithm to modify a value using a keyPath. Same
thing in the second .put call. Here a value already exists on the
keyPath "length" and so an entry will be inserted with key 3. Again,
we don't need to even invoke the steps for modifying a value using a
keyPath.

Please let me know if I'm missing something.

The second issue is how to modify a value if the keyPath used for
modifying is the empty string. This situation can no longer occur
since the change in bug 14985 [1]. Modifying values using keyPaths
only happen when you use autoIncrement, and you can no longer use
autoIncrement together with an empty-string keyPath since that is
basically useless.


So, with that in mind we still need to figure out the various edge
cases and write a detailed set of steps for modifying a value using a
keyPath. In all these examples i'll assume that the key 1 is
generated. I've included the Firefox behavior in all cases, not
because I think it's obviously correct, but as a data point. I'm
curious to hear what you guys do too.

What happens if a there are missing objects "higher up" in the keyPath:
store = db.createObjectStore("os", { keyPath: "a.b.c", autoIncrement: true });
store.put({ x: "str" });
Here there is nowhere to directly store the new value since there is
no "a" property.
What we do in Firefox is to insert objects as needed. In this case
we'd modify the value such that we get the following:
{ x: "str", a: { b: { c: 1 } } }
Same thing goes if part of the "object chain" is there:
store = db.createObjectStore("os", { keyPath: "a.b.c", autoIncrement: true });
store.put({ x: "str", a: {} });
Here Firefox will again store { x: "str", a: { b: { c: 1 } } }


What happens if a value "higher up" in the keyPath is not an object:
store = db.createObjectStore("os", { keyPath: "a.b.c", autoIncrement: true });
store.put({ a: "str" });
Here there not only is nowhere to directly store the new value. We
also can't simply insert the missing objects since we can't add a "b"
property to the value "str". Exact same scenario appears if you
replace "str" with a 1 or null.
What we do in Firefox is to throw a DataError exception.
Another example of this is simply
store = db.createObjectStore("os", { keyPath: "a", autoIncrement: true });
store.put("str");


What happens if a value "higher up" in the keyPath is a host object:
store = db.createObjectStore("os", { keyPath: "a.b", autoIncrement: true });
store.put({ a: myBlob });
While we can set a 'b' property on the blob, the structured clone
algorithm wouldn't copy this property and so it'd be pretty useless.
The exact same question applies if a is set to a Date or a RegExp too.
In Firefox I actually think that we set a useless 'b' property on the
blob which I would say is a bug.
Whatever we decide here, we should probably be consistent with the
previous case of "a" being a string rather than a blob.


What happens if a value "higher up" in the keyPath is an Array object:
store = db.createObjectStore("os", { keyPath: "a.b", autoIncrement: true });
store.put({ a: [1, 2] });
We can simply set a 'b' property on the array since that property
should get copied during the structured clone. But since it's
non-obvious I wanted to point it out.
In Firefox we set a 'b' property on the array.


There's also the somewhat ambiguous situation where a keyPath points
to a property which does exist, but where the property has the value
'undefined':
store = db.createObjectStore("os", { keyPath: "a", autoIncrement: true });
store.put({ a: undefined });
I know I above said that the steps for modifying a value doesn't even
run if evaluating "4.7 Steps for extracting a key from a value using a
key path" had yielded a value. The question is if yielding 'undefined'
counts as yielding a value.
If it does count as yielding a value then the above code throws since
undefined isn't a valid key. I.e. we hit the "The object store uses
in-line keys and the result of evaluating the object store's key path
yields a value and that value is not a valid key." case which throws a
DataError.
In Firefox we do count it as yielding a value and so we throw an exception.


In general I like Firefox behavior (except for the above mentioned
blob-case bug). It means that we always figure out if we have a key
before we send off data to the database thread and return from the
.put/.add function. While in theory you could off-load the work to
evaluate the keyPath to the database-thread in the case when
autoIncrement is true, it seems somewhat inconsistent to do this since
it can't be done when autoIncrement is false.

This also means that when there is a keyPath we consistently know that
the primary key for the value can be found at the keyPath. This is
useful for example in the .update function since you there need to
check that the primary key hasn't changed value.

The way we do this is that we have a specialized keyPath-evaluation
code path which returns an error if any of the values that we go
through is not an object.

But I'm certainly open to other behaviors.


I'm curious to hear what the IE and Chrome implementations do in these
various edge cases. It sounds like Chrome throws for some of the edge
cases described above, for some asynchronously fire an error event,
and for some simply don't modify the stored value, but it was a bit
unclear to me in which situations you do what.

[1] https://www.w3.org/Bugs/Public/show_bug.cgi?id=14985

/ Jonas
Received on Tuesday, 24 January 2012 10:22:37 GMT

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