segunda-feira, julho 05, 2010

Considerations on Mozilla's IndexedDB

I have read Mozilla's proposal for IndexedDB, and while I am no defender of SQLite on the client-side, I also disliked a lot the proposed syntax.

It is just too verbose and convoluted to do a simple thing as a join, as their example showed. It requires 2~3 more code than their own WebDatabase counter-example, and is hard to wrap your brain around the usage.

I would prefer a document-based storage method which can simply save JS objects to disk, and is able to fetch them back or operate with them, similar to couchDB. The documents would all be required to have an id (primary key in SQL terms).

I will build those examples on top of the kids/candy database used by the mozilla blog post.

var db = indexedDB.open("somedb"); //sets the db for reading 


db.async = true|false // sets (a)synchronous execution
db.autosave = true|false // if set to true, there is no need to call the save() function to commit the changes to the storage, as they happen automatically.


var onekid = db.filter(id="id"); // prepares a single document to be fetched from the storage 
onekid.get(); // fetches the onekid data as a JS object.
onekid.age = 12; // adds or changes some information on the document
onekid.save(); // saves to the storage
OR we could do:
onekid.save().onsuccess = function(event){
    alert("saved "+event.objects[0]+" successfully."); // the objects property of the event would store all ids of all documents affected by the transaction.
});


db.add({name:"john"}); // add an object to the storage
db.save();


var kidstable = db.filter({"name":"john","age":"36"}) // prepares to fetch multiple property+value pairs
kidstable = db.filter(["name","age"]) // prepares to fetch multiple properties
kidstable = db.filter("name") // prepares to fetch all documents with a name property
kidslist = kidstable.get(); //this would happen synchronously and return the actual documents as JS objects to be assigned to kidslist


kidstable.set("gender"); /* sets a gender property on all documents represented by the filter */
kidstable.set.onfailure = function(event){alert("could not set property "+event.key);}
kidstable.set({gender:"male"}); /* sets the gender property to value 'male' on all documents represented by kidstable */


result = kidstable.set({age:12}); /* forces synchronous execution. returns true on success, false on failure */
data = kidstable.set({age:10}).get(); /* sets the data, then gets the modified data; */
kidstable.set({age:10,gender:"female"}); /* multiple properties at once */
kidstable.unset(["gender","age"]); /*removes properties. */


kidstable.del(); //deletes objects


set, unset and del functions could happen on memory-space and vaporize if save() is not called (unless autosave is true). Variable scope applies.


var thekids = db.filter(key.name).sort() /* prepares to fetch all documents with a name attribute (kids). also sorting ascending; the parameter 'desc' could be used to reverse the sort order. */
var sales =  db.filter(key.candysales).sort() /*prepares to search for all sales */
var kidsPurchases = db.filter(candysales.kidId = thekids.id) /* prepares the search for all candysales. the query 'thekids' did not have to be executed to be used in the 'kidspurchases' query. */
var kidsPurchasesSubset = db.filter(candysales.kidId = thekids.id).keys(thekids.name, candysales.candyname,candysales.date); /* prepares to fetch only certain properties from the data storage */


kidsPurchasesSubset.each = function (doc) {
    var element = createElement("div");


    element.textContent = "Name: "+doc.name+" - Candy: "+doc.candyname+" - Date: "+doc.date
document.getElementById("kidList").appendChild(element);   
} /*runs for each item getted/setted when a query is executed.*/


db.index("candyname",false); /* creates an index to accelerate searching by the candyname property. */




The second parameter in the index() function is for uniqueness. The false value indicates the index will allow for duplicate values, but will still create the index as a mean to accelerate partitioning and searching a specific subset of data.

All operations would be atomic by default, but the data handling functions get, set, filter, unset, del, add, save and abort would allow for a transaction='string' parameter.
this parameter would cause the changes to not be commited even if the autosave() setting is true. save(transaction='transid') would commit the transaction. abort(transaction='transid') would rollback all pending changes.