Saturday, August 4, 2012

IndexedDB: Keys: efficiently retrieving data from a database

In order to retrieve data efficiently from an IndexedDB database, each record is organized by its key. The only condition for the key is that it is a valid key. A valid key can be one of the following types:

  • Array (Is only valid when every item inside the array is a valid key and the array doesn’t contains it self. Also all non-numeric properties are ignored and will not affect whether the array is a valid key)
  • DOMString
  • Date (The Primitivevalue (internal property) of the date object can’t be NaN)
  • Float (The key can’t be NaN)

When comparing keys, the order in the list above applies. Array is greater than all DOMStrings, DOMString is greater than …

Comparing arrays is done by comparing the values inside the array (as long that both arrays got values on the position). If the values on the same position differ, then the outcome of that comparison will determine which array is greater. If the values on each position are the same, the length of the array will determine the which one is greater or equal if the arrays have the same length.

Complex Keys

The IndexedDB API also support complex keys. This are keys who refer to properties nested inside an object. This way you are able to filter on data that is available are properties of a nested object. In the example below we have an person object. Besides a name and firstname property it contains an address property. This address property is also an object containing properties like city, street, … If we want to filter data on its city we can do this by defining a key like this “address.city”.

   1: var person = {
   2:     name: "name",
   3:     firstname: "firstname",
   4:     address: {
   5:         street: "street",
   6:         city: "city"    
   7:     }
   8: }

Since Linq2IndexedDB 1.0.11 this is also supported for all filters, even for filters that don’t take advantage of the IndexedDB API. For this reason it is advised to use the linq2IndexedDB.prototype.utilities.getPropertyValue method to determine the value by it’s property name. Two arguments need to be provided:



  • first argument is the object containing the data
  • second argument is the propertyName string. For example “address.city”

The return value of the method is the value at this location if it exists, otherwise undefined will be returned.


Note that this nesting only works for objects. If the property contains an array of objects, you won’t be able to use this way to filter your data.


Object store keys


Inside an object store, keys even have a more special reason of existence. In here keys are used to uniquely identify an object. This means you can never add an object twice with the same key, but this enables you to update and delete objects when you know it’s key. For an object store, there are 2 ways you can provide a key:



  • Inline key: the key is present inside the object (A KeyPath must be defined on the object store)
  • External key: the key is provided separately (in most cases the value of the key/value will be a primary type like a string or float)

In both cases you can choose for the option to let the key be generated by the IndexedDB API. You can do this by setting the object store autoIncrement attribute on true when creating the object store. In this case the key will have a numeric value.


Index keys


The keys in for an index are always defined by a KeyPath. These keys can be complex keys as defined in the section “Complex keys” above. Every object in the object store keeping the index that has a value for the key path a record will be added. The key will be the value of the property defined by the key path. For example we have a keyPath “firstname”. In the example below, the key in the index record would be “Kristof” and the value the person object.



   1: var person = {
   2:     name: "Degrave",
   3:     firstname: "Kristof"
   4: }

In the index you can also have 2 special cases. The first one is the unique attribute. This will make sure, that a value for this keyPath can only be defined once in a value of the object store. This constraint will be checked when you add data to an object store. If there is an object present with the same value for that property, an ConstraintError will be thrown.


A second attribute is the multiEntry. This allows to have multiple values for only one key. In stead of adding a new record for every object with the same value, the key is only added once and all the objects that have same value are added to a collection.


Conclusion


Now you should have insight how the keys work inside the IndexedDB API. With the complex keys you will be able to filter on data with nested object, this way you don’t need to provide an object store for every object you want to store. Also the Linq2IndexedDB framework also supports these complex keys and you can even use them in your custom filters.

2 comments:

  1. hey, what if the key for objectStore or index is present in the array? in the below example

    var person = {
    name: "name",
    fristname: "firstname",
    address: [{
    id: 1,
    street: "street",
    city: "city"
    }, {
    id: 2,
    street: "street1",
    city: "city1"
    }
    ]
    }

    i want to create index out of the id's in the address. what should be the key path? i tried doing something like this "address[0].id" which failed. i dont want to create another objectstore for address.

    ReplyDelete
    Replies
    1. For now this isn't possible. Not in the native API and not in my framework. I'll try to find a solution for your problem asap.

      greetings, Kristof

      Delete