As I continue my research into IndexedDB, today I decided to look into how you could perform a search for data based on an array property. Search is - to me - the most critical weak point of IndexedDB now. But "weak" doesn't imply powerless, and maybe I'm just not fully appreciating the power of IndexedDB yet. Today I was specifically curious about the following use-case:
Imagine an object called Person. This object contains a number of properties, but for now, imagine it only contains a field called name and a field called tags. The name is - obviously - the name. Tags is an array of strings. Here are a few simple examples:
In the data above, we have three people defined. Note the array of tags assigned to each person. I wanted to know how I would search for a particular tag. So for example, how would I find the people with "beer" as a tag?
I began my test by writing my IndexedDB setup logic. (As a side note - I did not do the "hack" to make this code work in Chrome. The hack isn't terribly bad. I blogged about it here. But since I didn't want to muddy up my code too much I focused on writing to the spec and tested with Firefox.) Note that my objectstore has two indexes. One for name and one for tags.
I then whipped up a simple form that would let me enter data, click a button, and have it added to my objectstore. The form is simple enough for me to skip showing the HTML, but here is how I handle the action in JavaScript.
The only thing I'll call out there is the split call on the tags value. This lets me enter lists in the form field ("a,b") and have it converted to an array for storage. For my testing I made 3 people. One with tag A, one with tags A and B, and one with tag C.
Finally - I wrote a super quick 'dump' utility that would write out the data to a div. This was made to write everything out and give me something to compare against when I did my searches.
Ok - so at this point I had a form to let me enter data and a button I could click to quickly list out what data existed in the objectstore. Now I needed to search.
I added a simple text field and button to my page that would let me enter a tag and run a function. Getting data from an IndexedDB objectstore can be done in two basic ways. You either ask for data based on a particular value (people where name = ray) or ask for a range of data (people where the name begins with A and higher). Both can return one or many results. I began with a simple 'get' type operation:
For the most part this should make sense (if you know anything about IndexedDB or have been reading my earlier entries). The core sticking point is line 9. You can see I pass the string to the get call. This "felt" wrong to me since the value of tag is an array. I tried it anyway. As I expected, it failed.
I then modified the code to pass an array to the get API:
And voila - it worked... kinda. If I searched for A, it would work for a person that only had A as a tag. It would not match a person with A and B as tags. In fact, I couldn't even search for B. But C did work. So it seemed as if I couldn't find a match in the second element or match against a person that had more than one item at all. If you want to see this for yourself, point your Firefox browser at http://www.raymondcamden.com/demos/2012/aug/10/test4.html.
So at this point I paused and did something crazy... reread the spec a bit. Specifically the portion pertaining to creating indexes. Turns out there is an option that is specifically there for arrays. Specifically this clause is what helped me figure something out:
An index also contains a multiEntry flag. This flag affects how the index behaves when the result of evaluating the index's key path yields an Array. If the multiEntry flag is false, then a single record whose key is an Array is added to the index. If the multiEntry flag is true, then the one record is added to the index for each item in the Array. The key for each record is the value of respective item in the Array.
Ahah! I updated my index creation code to enable this option:
objectStore.createIndex("tags","tags", {unique:false,multiEntry:true});
I then did more testing. The change above was not quite enough by itself. Now that I knew the index was being stored by item, I removed the code that split my input into an array. Basically I went back to this:
And this got me back to where I was before. A matched a person with an A tag but not anyone with A as one of many tags. I felt like I was on the right track though so I took another tack. What if I did a range search? One of the options for a range search is to match only against one thing - which sounds a bit confusing. But I figured what the hell. In the code below I've switched to a range and used the only operator:
And that worked. I mean, of course it worked. It allowed me to search for A and find both results. It also allowed me to search for B and find the person who had it as a second tag.
I'm not 100% sure I get why the range search was required, but it doesn't feel too entirely wonky to me. For those of you without a modern Firefox, here's a quick screen capture of the display:

And for those of you want to test, hit the demo button below and enjoy. I apologize if this blog post is a bit confusing. Please feel free to ask any questions, or correct any mistakes.
Archived Comments
Can I get a SQL wrapper for that and some CFDUMP? ;) #TGIF
How about a beer instead? ;)
DEAL! :)
To demonstrate power of IndexedDB, I have created a demo app http://dev.yathit.com/demo/... IndexedDB can query several ten thousand records queried under couple of 100ms time using compound index. The query has filters and sorted. For ad-hoc query (without compound index), using key scanning join, it is possible within 4-5 seconds time.
Very click, Kyaw.
How about a beer instead? :-D
It seems IndexedDB API expose all possible with WebSQL except no SQL processor there. Performance is much faster than WebSQL.
All relationships (parent-child, one-to-many, many-to-many) can be model easily and retrived very efficiently.
Most common table joinings are simple, as it turn out.
%LIKE% query is possible, but a lot of work.
So IndexedDB is a way to go, except Safari. I did not see safari teans is active in indexeddb mailing list. It is very likely they are happy with WebSQL and have no interest in IndexedDB API. Only if Google push IndexedDB to webkit code base. It is also a tough thing, since IndexedDB API touch many things like security, performance. I guess webkit code base will not accept easily. just guess.
I think the %LIKE% style search is the biggest concern for me. Mongo supports incredible searching, but IndexedDB is pretty primitive in that regards.
Hey Raymond,
Great post, I was looking for something like this but couldn't find it anywhere else. One question I have is that how to iterate over all the values for a particular key, e.g., give the key 'Ray', how do you get (and store) all the associated tags?
I'm not sure I get you. Is "Ray" the key for the data that *has* tags? If so, you just do a normal get type operation. The tags are part of the data.
I think I may be misreading you though.
I think I answered my own question after a bit of effort. Actually, I was struggling with gettting and setting a key with array as the value. Only your post has a working example (albeit advanced example) of such an arrangement. On top of that, I'm using the IndexedDBShim for cross-browser implementation, so things were getting a little rough. Anyways, here is what I meant and the solution that I came up with - http://stackoverflow.com/qu...
How to bind the indexeddb values to the combobox??? And Based on one combobox selection, how to filter values in another combobox??? I searched for this and didnt found anything in the Internet, I really require this functionality, Pls help, thanks in advance...
"How to bind the indexeddb values to the combobox???"
Given that I've shown how to get elements, then all you do is take the results and set them to the dropdown. This is no different than any other example of using JS to change the values of a dropdown. If you do not know how to do that, I recommend searching for *that*, and do not worry about IndexedDB.
"And Based on one combobox selection, how to filter values in another combobox???"
Again, this isn't an IndexedDB question. You would need to write code that listens for changes to the dropdown. jQuery makes this pretty easy. On that change, you then use code I've already shared to get items from the IndexedDB table.
A great post indeed, very helpful! Cheers Raymond!
Thanks a lot great post.
But, it's not works in IE 11.
Do you have another ideas?
*How* doesn't it work? I can't help you unless you can give me a precise idea of where it is failing.
Hi Raymond,
Great Post. I just wonder is the IN clause available in IndexedDB? Or the same procedure you have outlined above is the only option. Cheers.
Technically no, but this article describes a smart work around for it and provides a function. It also describes other good workarounds as well: https://hacks.mozilla.org/2...
How to search if the tags is an object instead of array?
I don't believe you can do that with IDB. If you can, send me a link to an example please.