1

Here's my test case on JS Bin.

Imagine you have an IndexedDB object store. Your objects are very simple, with two properties "a" and "b". Both properties contain integers. And because you want to be able to query your object store for particular values of "a" and "b", you create a compound index. Here's some code that follows this description:

var db;

request = indexedDB.open("test", 1);
request.onerror = function (event) { console.log(event); };

request.onupgradeneeded = function (event) {
  var db = event.target.result;
  db.onerror = function (event) { console.log(event); };

  var store = db.createObjectStore("store", {keyPath: "id", autoIncrement: true});
  store.createIndex("a, b", ["a", "b"], {unique: true});

  store.add({a: 0, b: 0});
  store.add({a: 1, b: 0});
  store.add({a: 1, b: 1});
};

request.onsuccess = function (event) {
  db = request.result;
  db.onerror = function (event) { console.log(event); };
};

That works fine if you want to retrieve a particular object from your database. So if you run this:

db.transaction("store").objectStore("store").index("a, b").get([1, 0]).onsuccess = function (event) { console.log(event.target.result); };

the output is:

Object {a: 1, b: 0, psid: 2} 

Wonderful. Great. So now you want to retrieve a range of values. Say, you want to fix "b" to be 0 while letting "a" be 0 or 1. Now weird stuff starts to happen. Try this code:

console.log("[a, b] bound from [0, 0] to [1, 0]");
db.transaction("store").objectStore("store").index("a, b").openCursor(IDBKeyRange.bound([0, 0], [1, 0])).onsuccess = function (event) {
  var cursor = event.target.result;
  if (cursor) {
    console.log(cursor.value);
    cursor.continue();
  } else {
    console.log("[a, b] bound from [0, 0] to [2, 0]");
    db.transaction("store").objectStore("store").index("a, b").openCursor(IDBKeyRange.bound([0, 0], [2, 0])).onsuccess = function (event) {
      var cursor = event.target.result;
      if (cursor) {
        console.log(cursor.value);
        cursor.continue();
      }
    };
  }
};

It produces this output:

[a, b] bound from [0, 0] to [1, 0]
Object {a: 0, b: 0, id: 1}
Object {a: 1, b: 0, id: 2}
[a, b] bound from [0, 0] to [2, 0]
Object {a: 0, b: 0, id: 1}
Object {a: 1, b: 0, id: 2}
Object {a: 1, b: 1, id: 3}

Bounding between [0, 0] and [1, 0] seems to work fine. But bounding between [0, 0] and [2, 0] does not! It also returns [0, 1] as a result. Why does [1, 0] work as an upper bound but [2, 0] doesn't? I feel like I must be either making a really stupid mistake or very fundamentally misunderstanding something.

And in case you missed it, here is the link to this on JS Bin again.

EDIT for Josh: I originally only tried it with just bound(x,y) which is the same as bound(x,y,false,false). Changing it to bound(x,y,true,true) can be seen here. Output is

[a, b] bound from [0, 0] to [1, 0]
[a, b] bound from [0, 0] to [2, 0]
Object {a: 1, b: 0, id: 2}
Object {a: 1, b: 1, id: 3}

I still don't have a good intuitive understanding for how this stuff makes sense.

dumbmatter
  • 9,351
  • 7
  • 41
  • 80

2 Answers2

4

I tried to explain this in the other answer, but basically it is because of short-circuted array-sorting as defined in the indexedDB spec. You are working with range queries, and the matches all fall within the first range. If the first range criteria is met, it does not matter what the rest of the ranges are doing.

There is no solution. You can create separate indices (but run into the exploding index issue). You can instead create pre-calculated fields per object. In other words, don't use a composite index, use a single one on this specially derived hidden field of your object that you update every time the object is added/updated in indexedDB.

Community
  • 1
  • 1
Josh
  • 17,834
  • 7
  • 50
  • 68
  • Thanks, I hadn't seen your other post. I think I mostly understand. But I still don't see why using an upper bound of [1, 0] is different than using an upper bound of [2, 0] since 1 is already >= any value in the first index. I mean, I can see that is how it is defined by the part of the spec you pasted in your other post, but it just seems bizarre that 1 <= 1 and 1 <= 2 result in different answers. Am I still thinking about things wrong? – dumbmatter Oct 05 '14 at 12:56
  • Just curious, did you try IDBKeyRange.bound(x,y,true,true) or just IDBKeyRange.bound(x,y,false,false) ? – Josh Oct 05 '14 at 22:30
  • Side note: if using IDBKeyRange.only, you don't need to specify it in the statement, you can simply use `store.openCursor(keypath)` and/or `store.get(keypath)`. indexedDB will implicitly treat the expression the same as `store.openCursor(IDBKeyRange.only(keypath))`. – Josh Oct 05 '14 at 22:32
  • Ok, I too am confused about the "1 <= 1 and 1 <= 2 result in different answers" part. It should not matter. From a different angle, grasping at straws here, can you try not nesting the second onsuccess function (defining as nested)? Can you try using bind instead? Or using 'this' instead of event.target? Maybe it is a basic JS issue with nested funcs? – Josh Oct 06 '14 at 14:47
  • I'm sure it's not a JS issue, it's an IDB issue. The wise @KyawTun (who posted another answer) may be the only person who actually understands this stuff, I hope he can enlighten us. – dumbmatter Oct 07 '14 at 00:25
1

You index will not work for querying fix value of b. When working with compound index, thinks compound index has two parts, prefix and postfix. prefix key path is the one you want to filter on. postfix is the one you want to order by.

In you case, you want to filter by b and order by a, so index should be ['b', 'a']. Since prefix and postfix keyPath have can have multiple fields, very complex query can be done with IndexedDB.

Kyaw Tun
  • 12,447
  • 10
  • 56
  • 83
  • 1
    I'm sure you are correct, but where are you getting this from? I can't find this stuff in the IDB spec or anywhere else. – dumbmatter Oct 07 '14 at 00:22
  • And is there a good explanation for why setting the upper bound to `[1, 0]` is different than setting it to `[2, 0]`, since it should be doing a <= comparison if I understand correctly? – dumbmatter Oct 07 '14 at 00:25