W3C

WebSimpleDB API

W3C Working Draft 29 September 2009

This Version:
http://www.w3.org/TR/2009/WD-WebSimpleDB-20090929/
Latest Version:
http://www.w3.org/TR/WebSimpleDB/
Editor:
Nikunj R. Mehta, Oracle Corp <nikunj.mehta@oracle.com>

Abstract

This document defines APIs for storing and retrieving ordered key-value pairs in a transactional database.

Status of this Document

This section describes the status of this document at the time of its publication. Other documents may supersede this document. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports index at http://www.w3.org/TR/.

This document is the 29 September 2009 First Public Working Draft of the WebSimpleDB API specification. Please send comments about this document to public-webapps@w3.org (archived) with “[WebSimpleDB]” at the start of the subject line.

The latest stable version of the editor's draft of this specification is always available on the W3C CVS server. Change tracking for this document is available at the following location:

If you wish to make comments regarding this document, please send them to public-webapps@w3.org (subscribe, archives) or submit them using our public bug database.

The Web Applications Working Group, is the W3C Working Group responsible for this specification's progress along the W3C Recommendation track.

This is one of the proposals being considered for standardization in the area of local, persistent storage in user agents.

Publication as a Working Draft does not imply endorsement by the W3C Membership. This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than work in progress.

This document was produced by a group operating under the 5 February 2004 W3C Patent Policy. W3C maintains a public list of any patent disclosures made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An individual who has actual knowledge of a patent which the individual believes contains Essential Claim(s) must disclose the information in accordance with section 6 of the W3C Patent Policy.

Table of Contents

1. Introduction

This section is non-normative.

User agents need to store large numbers of objects locally in order to satisfy off-line data requirements of Web applications. [WebStorage] is useful for storing pairs of keys and their corresponding values. However, it does not provide in-order retrieval of keys, efficient searching over values, or storage of duplicate values for a key.

This specification provides a concrete API to perform advanced key-value data management that is at the heart of most sophisticated query processors. It does so by using transactional databases to store keys and their corresponding values (one or more per key), and providing a means of traversing keys in a deterministic order. This is often implemented through the use of persistent B-tree data structures that are considered efficient for insertion and deletion as well as in-order traversal of very large numbers of data items.

Example

Here is an example of a script using this API. First, a function prepareDatabase() is defined. This function tries to create the database if necessary, giving it one entity store called "docids" with two columns ("id" and "name"). If it is successful, or if the table doesn't need creating, it calls the function that does the actual work, in this case showDocCount().

ECMAScript
function prepareDatabase(ready, error) {
  return openDatabase('documents', '1.0', 'Offline document storage', false,
    function(db) {
      db.transaction(function(txn) {
        txn.createEntityStore('docids', 'id');
        txn.changeVersion(db.version, '1.0');
      }, error);
      db.transaction(ready);
    });
}

function showDocCount(txn, span) {
  var store = txn.getEntityStore('docids');
  var entities = store.entities();
  var count = 0;
  for (var item = entities.nextNoDup(); item !== null; item = entities.nextNoDup()) {
    count += entities.count();
  }
  span.textContent = count;
}

prepareDatabase(function(txn) {
  // got database
  var span = document.getElementById('doc-count');
  showDocCount(txn, span);
}, function (e) {
  // error getting database
  alert(e.message);
});

A script can efficiently find items in an entity store that come closest to the required value provided the value is stored in either a primary or a secondary key. In the following example, the 'books' entity store holds data about books which are stored by their 'isbn' attribute. Additionally, an index is maintained on the 'author' attribute of the objects stored in the entity store. This index can be used to look up books for a given author. If an exact match is not found, the next matching author is located.

ECMAScript
var db = openDatabase('books', '1.0', 'Book store', false,
    function(db) {
      db.transaction(function(txn) {
        var store = txn.createEntityStore('books', 'isbn');
        store.createIndex('BookAuthor', 'author', Index.MANY_TO_ONE);
        txn.changeVersion(db.version, '1.0');
      });
    });
db.transaction(function(txn) {
  var cursor = txn.getEntityStore('books').getIndex('BookAuthor').entities(author);
  var book = cursor.first();
  if (book.author == author)
    report(book.isbn, book.name, book.author);
  else
    report(null);
});
Note
This specification is one of the proposals being considered by the WebApps WG for client-side data storage.

2. Conformance Requirements

Everything in this specification is normative except for diagrams, examples, notes and sections marked as being informative.

The keywords “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “RECOMMENDED”, “MAY” and “OPTIONAL” in this document are to be interpreted as described in Key words for use in RFCs to Indicate Requirement Levels [RFC2119].

This specification defines one class of products:

Conforming user agent

A user agent must behave as described in this specification in order to be considered conformant.

User agents may implement algorithms given in this specification in any way desired, so long as the end result is indistinguishable from the result that would be obtained by the specification's algorithms.

A conforming WebSimpleDatabase user agent must also be a conforming implementation of the IDL fragments of this specification, as described in the “Web IDL” specification. [WEBIDL]

Note
This specification uses both the terms "conforming user agent(s)" and "user agent(s)" to refer to this product class.

2.1. Dependencies

This specification relies on several other underlying specifications.

HTML5
Many fundamental concepts from HTML5 are used by this specification. [HTML5]
WebWorkers
This specification adds capabilities to WebWorkers and uses certain concepts defined in this specification. [WebWorkers]

3. Simple Database API

Each origin has an associated set of databases. Each database has a name and a current version. There is no way to enumerate or delete the databases available for an origin from this API.

Note
Each database has one version at a time; a database can't exist in multiple versions at once. Versions are intended to allow authors to manage schema changes incrementally and non-destructively, and without running the risk of old code (e.g. in another browser window) trying to write to a database with incorrect assumptions.

3.1. Opening a database

IDL
[Supplemental] 
interface Environment {
  Database openDatabase(in DOMString name, 
                        in DOMString version, 
                        in DOMString displayName, 
                        in optional boolean readonly, 
                        in optional DatabaseCallback upgradeCallback);
};

Window implements Environment;

[Callback=FunctionOnly, NoInterfaceObject]
interface DatabaseCallback {
  void handleEvent(in Database database);
};

[Supplemental]
interface EnvironmentSync {
  DatabaseSync openDatabaseSync(in DOMString name, 
                        in DOMString version, 
                        in DOMString displayName, 
                        in optional boolean readonly, 
                        in optional DatabaseCallback upgradeCallback);
};

WorkerUtils implements Environment;
WorkerUtils implements EnvironmentSync;

The openDatabase() and the openDatabaseSync() methods take the following arguments: a database name, a database version, a display name, a read only flag, and optionally a callback to be invoked if the database has not yet been created. The callback, if provided, is intended to be used to call changeVersion(); the callback is invoked with the database having the empty string as its version regardless of the given database version. If the callback is not provided, the database is created with the given database version as its version.

When invoked, these methods must run the following steps, with all but the last two steps being run atomically:

  1. For the method on the Window object: let origin be the origin of the active document of the browsing context of the Window object on which the method was invoked.

    For the methods on the WorkerUtils object: let origin be the origin of the scripts in the worker.

  2. If the database version provided is not the empty string, and there is already a database with the given name from the origin origin, but the database has a different version than the version provided, then throw an INVALID_STATE_ERR exception and abort these steps.
  3. If no database with the given name from the origin origin exists, then create the database and let created be true. Otherwise, let created be false.

    If a callback was passed to the method, then let the database's version be the empty string. Otherwise, let its version be the given database version.

  4. For the openDatabase() method: let result be a newly constructed Database object representing the database with the given database name from the origin origin.

    For the openDatabaseSync() method: let result be a newly constructed DatabaseSync object representing the database with the given database name from the origin origin.

  5. If created is false or if no callback was passed to the method, skip this step. Otherwise:
    • For the openDatabase() method: queue a task to to invoke the callback with result as its only argument.
    • For the openDatabaseSync() method: invoke the callback with result as its only argument. If the callback throws an exception, rethrow that exception and abort these steps.
  6. Return result.

All strings including the empty string are valid database names. Database names must be compared in a case-sensitive manner.

Note
Implementations can support this even in environments that only support a subset of all strings as database names by mapping database names (e.g. using a hashing algorithm) to the supported set of names.

The version that the database was opened with is the expected version of this Database or DatabaseSync object. It can be the empty string, in which case there is no expected version — any version is fine.

User agents are expected to use the display name to optimize the user experience.

3.2. Database

Two variants of databases are available in this API. The asynchronous version Database is available to both Window and WorkerUtils objects. The synchronous version DatabaseSync is available only to the WorkerUtils object.

IDL
interface AbstractDatabase {
  readonly attribute DOMString version;
  readonly attribute boolean mutable;
};

On getting, the version attribute must return the current version of the database (as opposed to the expected version of the AbstractDatabase object).

On getting, the mutable attribute must return whether the database can be modified. This is set at the time the database is opened.

3.2.1. Asynchronous Database API

IDL
interface Database : AbstractDatabase {
  transaction(in TransactionCallback txnCallback,
              in optional TransactionErrorCallback errorCallback,
              in optional DatabaseVoidCallback txnCallback,
              in optional Transaction parent,
              in optional unsigned short isolationLevel,
              in optional unsigned int timeout);
};

[Callback=FunctionOnly, NoInterfaceObject]
interface TransactionCallback {
  void handleEvent(in Transaction txn);
};

[Callback=FunctionOnly, NoInterfaceObject]
interface DatabaseVoidCallback {
  void handleEvent();
};

[Callback=FunctionOnly, NoInterfaceObject]
interface DatabaseTransactionErrorCallback {
  void handleEvent(in DatabaseError error);
};

When creating the transaction, three configuration parameters can be specified - a parent transaction, an isolation level for this transaction, and a timeout duration (in milliseconds) for acquiring locks required in this transaction.

If a parent transaction is specified, the new transaction is committed only if the parent transaction commits. If the parent transaction rolls back, all its child transactions are also rolled back even if they had been committed.

An isolation level can be either of READ_UNCOMMITTED, READ_COMMITTED, or SERIALIZABLE as specified in the Transaction interface.

To perform a transaction within an asynchronous database, the application should provide a callback within which the steps of the transaction are performed. The transaction() method produces a read-only or read-write transaction depending on whether the database is mutable or not.

When called, the method transaction() method must immediately return and then asynchronously perform the transaction steps.

3.2.1.1. Processing model

The transaction steps are as follows. These steps must be run asynchronously. These steps are invoked with a transaction callback, optionally an error callback, and optionally a success callback.

  1. Create a new Transaction object. Let transaction be this object.
  2. If an error occurred in the creating of the Transaction object (e.g. if the user agent failed to obtain an appropriate lock), jump to the last step.
  3. Queue a task to invoke the transaction callback with transaction as its only argument, and wait for that task to be run.
  4. If the callback couldn't be called (e.g. it was null), or if the callback was invoked and raised an exception, jump to the last step.
  5. For each call on transaction, perform the following steps
    1. If the call mutates the database, but transaction is on a read-only database, jump to the last step in the overall steps.
    2. Execute the call in the context of the transaction.
    3. If the call failed, jump to the last step in the overall steps.
  6. Otherwise: if the transaction is not already committed, then commit the transaction. If an error occurred in the committing of the transaction, jump to the last step.
  7. Queue a task to invoke the success callback.
  8. End these steps. The next step is only used when something goes wrong.
  9. Queue a task to invoke the error callback with a newly constructed DatabaseError object that represents the last error to have occurred in this transaction. Rollback the transaction.

The task source for these tasks is the database access task source.

3.2.2. Synchronous Database API

IDL
interface DatabaseSync : AbstractDatabase {
  Transaction transaction(in optional Transaction parent,
              in optional unsigned short isolationLevel,
              in optional unsigned int timeout);
};

The transaction() method must run the following steps:

  1. Create a Transaction object. Let transaction be the newly created Transaction object.
  2. Return transaction.
  3. For each call on transaction, perform the following steps
    1. If the call mutates the database, but transaction is on a read-only database, jump to the "in case of error" steps below.
    2. Execute the call in the context of the transaction.
    3. If the call failed, jump to the "in case of error" steps below.

    In case of error (or more specifically, if the above sub-steps say to jump to the "in case of error" steps), run the following substeps:

    1. If the call caused an error then throw a newly constructed DatabaseException exception that represents the error that caused these substeps to be run.

3.2.3. Creating a Transaction object

When the user agent is to create a Transaction object for a transaction, it must run the following steps:

  1. Open a new transaction to the database, and create a Transaction object that represents that transaction. The user agent may wait for appropriate locks to become available.
  2. If an error occurs in the opening of the transaction (e.g. if the user agent failed to obtain an appropriate lock after the given timeout, throw a DatabaseException exception and abort these steps. (Error code 32.)
  3. Return the newly created Transaction object.

3.3. Transaction

A transaction represents an atomic, consistent, isolated, and durable set of database access and mutation operations. Transactions offer data protection from application or system failures.

  • Atomicity

    Multiple database operations are treated as a single unit of work. Once committed, all write operations performed under the protection of the transaction are saved to the databases. Further, in the event that a transaction is aborted, all write operations performed during the transaction are discarded. In this event, the database is left in the state it was in before the transaction began, regardless of the number or type of write operations that may have been performed during the course of the transaction.

  • Consistency

    Databases will never see a partially completed transaction. This is true even if the application fails while there are in-progress transactions. If the application or system fails, then either all of the database changes appear when the application next runs, or none of them appear.

  • Isolation

    While a transaction is in progress, databases appear to the transaction as if there are no other operations occurring outside of the transaction. That is, operations wrapped inside a transaction will always have a clean and consistent view of the database. They never have to see updates currently in progress under the protection of another transaction. Note, however, that isolation guarantees can be increased and relaxed from the default setting.

  • Durability

    Once committed to a database, modifications will persist even in the event of an application or system failure. Note that like isolation, durability guarantees can be relaxed by a user preference or user agent configuration.

3.3.1. Transaction API

A transaction is required to upgrade a database and add or remove objects from the database. It is also required to access database objects to retrieve or modify data in those objects.

A transaction may be a part of another transaction or completely independent of other transactions. Nested transactions cannot span multiple databases. Transactions are expected to be short lived. Conforming user agents may terminate transactions that take too long to complete in order to free up storage resources that are locked by the long running transaction.

Example

In the following example, a database can be initially setup with an entity store and a sequence used in that entity store.

ECMAScript
var database = window.openDatabase('AddressBook', '1', 'Address Book', false, 
    function(db) {
      db.transaction(function(txn) {
          txn.createSequence('ContactSeq');
          txn.createEntityStore('Contact', 'id', 'ContactSeq');
          txn.changeVersion(database.version, '1');
        });
      });
      
database.transaction(function(txn) {
  //... some transaction processing
});

Later this database can be upgraded to add a secondary key for the contact name.

ECMAScript
database = window.openDatabase('AddressBook', '2', 'Address Book', false,
    function(db) {
      db.transaction(function(txn) {
        var es = txn.getEntityStore('Contact');
        es.createIndex('ContactName', 'name', Index.MANY_TO_ONE);
        // database.version === '1'
        txn.changeVersion(database.version, '2');
      });
    });
  database.transaction(function(txn) {
    // ...
  });

The API for a transaction consists of mechanisms for committing and rolling back the effects of database operations performed in that transaction. However, asynchronous databases also automatically commit a transaction whenever the transaction callback completes. Asynchronous databases also automatically rollback a transaction if an error occurs inside the transaction callback.

Note
Applications must not assume that committing the transaction produces an instantaneously durable result. The user agent may delay flushing data to durable storage until an appropriate time.
IDL
interface Transaction {
    const unsigned short READ_UNCOMMITTED = 0;
    const unsigned short READ_COMMITTED = 1;
    const unsigned short SERIALIZABLE = 2;
    
    readonly attribute unsigned short isolationLevel;
    readonly attribute Transaction parent;
    
    void changeVersion(in DOMString oldVersion, 
                       in DOMString newVersion);  

    EntityStore getEntityStore(in DOMString name);
    EntityStore createEntityStore(in DOMString name, 
                                  in DOMString primaryKeyPath, 
                                  in optional DOMString sequenceName);

    Sequence getSequence(in DOMString name);
    Sequence createSequence(in DOMString name, 
                            in optional long long initial, 
                            in optional long long increment);
    
    Queue getQueue(in DOMString name);
    Queue createQueue(in DOMString name);                             
    

    abort();
    commit();
  };

On getting, isolationLevel will provide the isolation level specified when creating the transaction.

The READ_UNCOMMITTED isolation level allows the transaction access to data that is modified by a different transaction, even if that data may not have been committed by the foreign transaction.

The READ_COMMITTED isolation level allows the transaction access only to the data that is committed by prior transactions even though a foreign transaction may subsequently modify or delete that data before the present transaction completes.

The SERIALIZABLE isolation level allows the transaction access only to the data that is committed by prior transactions and does not allow any such data to be modified by foreign transactions until the present transaction completes.

On getting, parent will provide the parent transaction specified when creating the transaction.

The changeVersion() method allows scripts to atomically verify the version number and change it at the same time as doing a schema update.

The getEntityStore() method returns a EntityStore object to access the objects of the entity store identified by the given name.

If an entity store with the given name, compared in a case-sensitive manner, does not already exist, then this method will throw a newly constructed DatabaseException exception (Error code 3).

The createEntityStore() method creates a new entity store with the given name and returns a EntityStore object to access the objects of the entity store. The path of the primary key in objects stored in this entity store is required to correctly create a unique identifier for each object. The empty string is a valid value for this parameter, and it results in the entire value of objects to be used as the key value. If a path is specified and an optional sequence name is also provided, then the identified sequence is used to populate a value in the object at the key path and that value is stored as the key value for the object.

If an entity store with the same name, compared in a case-sensitive manner, already exists, then this method will throw a newly constructed DatabaseException exception (Error code 4).

The modifyEntityStore() method modifies an existing entity store with the given name and returns a EntityStore object to access the objects of the modified entity store. The path of the primary key in objects stored in this entity store is modified to the one provided in this call. If this path is different from that currently defined on the entity store, then the entity store is reorganized to use the new primary key path. If the entity store uses a sequence to generate primary keys, and this call omits the optional sequence name, then the existing sequence is no longer used to populate a value in the object at the key path. If a different sequence is specified, then the new sequence is used to generate keys.

If an entity store with the given name, compared in a case-sensitive manner, does not already exist, then this method will throw a newly constructed DatabaseException exception (Error code 3).

The getSequence() method returns a Sequence object to access the items of the sequence identified by the given name.

If a sequence with the given name, compared in a case-sensitive manner, does not already exist, then this method will throw a newly constructed DatabaseException exception (Error code 3).

The createSequence() method creates a new sequence with the given name and returns a Sequence object to access the items of the sequence. The optional parameters for initial value and increment size are used to create the sequence. If the increment parameter value is 0, then a newly constructed DatabaseException exception (Error code 5) is thrown. If a negative value is used, the sequence decrements its values.

If a sequence with the same name, compared in a case-sensitive manner, already exists, then this method will throw a newly constructed DatabaseException exception (Error code 4).

The getQueue() method returns a Queue object to access the items of the queue identified by the given name.

If a queue with the given name, compared in a case-sensitive manner, does not already exist, then this method will throw a newly constructed DatabaseException exception (Error code 3).

The createQueue() method creates a new queue with the given name and returns a Queue object to access the items of the queue.

If a queue with the same name, compared in a case-sensitive manner, already exists, then this method will throw a newly constructed DatabaseException exception (Error code 4).

The commit() method is used to signal the normal and satisfactory completion of a transaction to the database. At this point, the database durably stores the result of all the operations performed in this transaction through calls to this Transaction object.

The abort() method is used to signal the need to cancel the effects of database operations performed in a transaction. To perform this method, the database ignores all the changes performed in this transaction through calls to this Transaction object.

Once a transaction is aborted or committed, that Transaction object can no longer be used. If any calls are made on that object, then the database will throw a newly constructed DatabaseException exception (Error code 1). To perform database operations under the control of a new transaction, a fresh Transaction object is needed.

3.4. Entity Store

An entity store is a persistent structure that holds key-value pairs sorted by keys so as to enable fast insertion and look up as well as ordered retrieval. The store provides a mechanism by which to read and write stored objects. Operations on an entity store must follow the transaction used to create or obtain the entity store.

Each EntityStore object provides access to a set of key/value pairs, which are sometimes called items. Values can be any data type supported by the structured clone algorithm [HTML5]. Keys themselves can be of any data type supported by the structured clone algorithm. They can be extracted from objects being stored or generated from a monotonic sequence.

Example

In the following example, the put operation will store an object.

ECMAScript
var lincoln = {id: 1, name: 'Lincoln', number: '7012'};

var database = window.openDatabase('AddressBook', '1', 'Address Book', null, 
  function(Transaction txn) {
    txn.createEntityStore('Contact', 'id');
  });
database.transaction(function(Transaction txn) {
  var store = txn.getEntityStore('Contact');
  var lincolnID = store.put(lincoln);  
  // 1 === lincolnID
});

Objects in the entity store can be read using their key.

ECMAScript
database = window.openDatabase('AddressBook', '1', 'Address Book', true);
database.transaction(function(Transaction txn) {
  var store = txn.getEntityStore('Contact');
  var lincolnContact = store.get(1);
  // lincolnContact.id === 1 && lincolnContact.name === 'Lincoln';
});

An entity store does not allow multiple items to be stored with the same primary. key If the same key is used to store a different item, then it will replace the object currently stored with the same key.

Example

Continuing with the earlier example, the second put operation will replace the object stored by the first put operation.

ECMAScript
var abraham = {id: 1, name: 'Abraham', number: '2107'};

database.transaction(function(Transaction txn) {
  var store = txn.getEntityStore('Contact');
  var abrahamID = store.put(abraham);
  // 1 === abrahamID
});

Now when the entity store is read with the same key, the result is different compared to the object read in the previous example.

ECMAScript
database.transaction(function(Transaction txn) {
  var store = txn.getEntityStore('Contact');
  var abrahamContact = store.get(1);
  // abrahamContact.id === 1 && abrahamContact.name === 'Abraham';
});

If an application does not generate its own keys, it can use keys generated by a Sequence object.

Example

Contrasted with the previous examples, the entity store below is designed to use a Sequence object.

ECMAScript
var lincoln = {name: 'Lincoln', number: '7012'};

var database = window.openDatabase('AddressBook', '1', 'Address Book', null, 
  function(Transaction txn) {
    txn.createSequence('ContactSeq');
    txn.createEntityStore('Contact', 'id', 'ContactSeq');
  });
var lincolnID = null;
database.transaction(function(Transaction txn) {
  var store = txn.getEntityStore('Contact');
  lincolnID = store.put(lincoln);  
  // lincoln["id"] === lincolnID
});

When an object is stored in 'Contact', the 'ContactSeq' sequence is used to produce a key for that object. This key is also stored as part of the stored object as can be seen from the following code.

ECMAScript
database = window.openDatabase('AddressBook', '1', 'Address Book', true);
database.transaction(function(Transaction txn) {
  var store = txn.getEntityStore('Contact');
  var lincolnContact = store.get(lincolnID);
  // lincolnID == lincolnContact["id"] && lincolnContact["name"] == 'Lincoln';
});

An entity store arranges stored objects by their key, also referred to as the primary key. Each object contains the value of its primary key. Additionally, entity stores may also have one or more secondary keys declared for stored objects. Indices store data about secondary keys and refer to a primary key somewhere in the entity store.

Example

Continuing with the previous examples, the entity store below is configured with an index on the 'name' attribute.

ECMAScript
var lincoln = {name: 'Lincoln', number: '7012'};
var abraham = {name: 'Abraham', number: '2107'};

var database = window.openDatabase('AddressBook', '1', 'Address Book', null, 
  function(Transaction txn) {
    txn.createSequence('ContactSeq');
    var store = txn.createEntityStore('Contact', 'id', 'ContactSeq');
    store.createIndex('ContactName', 'name', Index.MANY_TO_ONE);
  });
  
database.transaction(function(Transaction txn) {
  var store = txn.getEntityStore('Contact');
  store.put(lincoln);  
  store.put(abraham);
});

When objects are retrieved from the entity store, they are arranged by the value of the 'id' attribute. On the other hand, when objects are retrieved using the 'ContactName' index, they are arranged by the value of the 'name' attribute of objects.

ECMAScript
database = window.openDatabase('AddressBook', '1', 'Address Book', true);
database.transaction(function(Transaction txn) {
  var store = txn.getEntityStore('Contact');
  var allCursor = store.entities();
  var lCursor = store.getIndex('ContactName').entities('L');
  var l1 = lCursor.next();
  l1 = lCursor.next();
  var l2 = allCursor.next();
  // l1["id"] === l2["id"]
});
IDL
interface EntityStore {  
  readonly attribute DOMString name;
  readonly attribute DOMString keyPath;
  readonly DOMStringList indexNames;
  
  any put(in any object);
  delete(in any key);
  any get(in any key);
  
  Index getIndex(in DOMString name);
  Index createIndex(in DOMString name, 
                    in DOMString keyPath, 
                    in unsigned short cardinality, 
                    in optional unsigned short onRelatedEntityDelete, 
                    in optional DOMString relatedEntity);  

  remove();
  Cursor entities(in optional any from, 
                  in optional bool inclusive, 
                  in optional any to, 
                  in optional bool inclusive, 
                  in optional unsigned short isolationLevel);
  EntityJoin join();
};

On getting, name will provide the name of the entity store.

On getting, keyPath will provide the path of the key used as the primary key of objects stored in this entity store. This path can be the empty string if the object value is used as the key.

On getting, indexNames will provide a list of the names of indexes on objects in this entity store.

The put() method is used to store the given object in the entity store. Insert the object by performing the insertion steps.

The get() method is used to retrieve the object associated with the given key in the entity store. The following steps must be run for retrieving the object with the following parameters: key.

  1. If key is not defined or null, then throw a newly constructed DatabaseException exception (Error code 3) and terminate these steps.
  2. Use the key to look up an object in the entity store. Let value be that object.
  3. If object is not found in the entity store, then throw a newly constructed DatabaseException exception (Error code 3) and terminate these steps.

The delete() method is used to delete the object associated with the given key in the entity store. Delete the object by performing the deletion steps.

The getIndex() method is used to obtain an Index object representing the named index on this entity store.

If an index with the given name, compared in a case-sensitive manner, does not already exist, then this method will throw a newly constructed DatabaseException exception (Error code 3).

The createIndex() method is used to define a new index on this entity store. An index is created using three required parameters - name, key path, and cardinality - and two optional parameters - action to be performed when related entity is deleted, and the name of the related entity.

If an index with the given name, compared in a case-sensitive manner, already exists, then this method will throw a newly constructed DatabaseException exception (Error code 4).

The remove() method is used to destroy the entity store as well as all associated indices.

The entities() method is used to access objects of an entity store. This method produces a Cursor object that is used to iterate over objects arranged by their keys. The parameters of this method are used to limit the range of primary keys over which the Cursor can be iterated. If an isolation level is provided, then it overrides the isolation level of the transaction used to obtain this entity store, and objects are accessed from the cursor with that level of isolation.

If there are two or more secondary indexes set for an entity store, then sets of objects can be retrieved from it based on the intersection of multiple index values using an EntityJoin object. The join() method is used to obtain such an object.

3.4.1. Insertion steps

The insertion steps are as follows. These steps must be run with three parameters: object, key path, and optional sequence name.
  1. Let key be the property of the object at the key path.
  2. If key is defined and not null, then skip the next step.
  3. If the sequence name is defined for the insertion steps, then perform the following steps.
    1. If the database does not contain a sequence whose name matches, compared in a case-sensitive manner, with sequence name, then throw a newly constructed DatabaseException exception (Error code 4) and terminate these steps.
    2. Let sequence be the Sequence object for the sequence name
    3. From the sequence get the next value. Store that value as key.
    4. Store key as the property value for the object at the key path.
  4. If the key is not defined or null, then throw a newly constructed DatabaseException exception (Error code 5) and terminate these steps.
  5. Insert the object with key as its key. If any indexes are maintained for objects in this entity store, then perform the following steps for each index. These steps must be run with three parameters: object, key path, and index.
    1. Let value be the property of the object at the key path.
    2. Let index key path be the key path of index.
    3. Let key be the property of the object at the index key path.
    4. Insert the value with key as its key in the index.
  6. Return the key.

3.4.2. Deletion steps

The deletion steps are as follows. These steps must be run with one parameter: key, key path, and optional sequence name.
  1. If key is not defined or null, then throw a newly constructed DatabaseException exception (Error code 3) and terminate these steps.
  2. Use the key to look up an object in the entity store. Let object be that object.
  3. If any indexes are maintained for objects in this entity store, then perform the following steps for each index. These steps must be run with three parameters: object, key path, and index.
    1. Let index key path be the key path of index.
    2. Let value be the property of the object at the index key path.
    3. Delete the record for value from index.
  4. Delete the object stored with the key key in the entity store.

3.5. Index

Usually database objects are retrieved by means of the object's key. However, the key used for an object will not always contain the information required to provide rapid access to the data that may be needed.

An index can indicate a relationship of objects in one entity store to those in another. A relation has three attributes - the two entities that are related and a cardinality.

Example

For example, if one entity store holds Contact objects, and another holds Account objects, such that each Account object holds a key to one Contact object. In this example, an entity store holding Account objects has a MANY_TO_ONE relationship with an entity store holding Contact objects.

ECMAScript
var contacts = [
  {id: 1, name: 'John', email: 'john@him.com'},
  {id: 2, name: 'Jane', email: 'jane@her.com'},
  {id: 3, name: 'Dan', email: 'dan@him.com'},
  {id: 4, name: 'Kim', email: 'kim@her.com'},              
];

var accounts = [
  {acct: 'x1', owner: 1, type: 'checking'},
  {acct: 'x3', owner: 2, type: 'saving'},
  {acct: 'y1', owner: 1, type: 'brokerage'},
  {acct: 'z4', owner: 4, type: 'saving'}
 ];

When an Index object relates two entity stores, it is important to consider the consequence of the deletion of an object on the related objects. If this is the case, foreign key constraints can be used to control data integrity.

Example

Continuing from the previous example, if an Account is removed, it has no impact on the Contact objects that were referenced in the Account. However, the opposite action would have more interesting consequences. If the contact 'John' were to be removed, then two accounts owned by 'John' are affected.

When a foreign key constraint is declared:

  1. a new secondary key for the object is stored, it is checked to make sure it exists as a primary key for the related entity object. If it does not, then a newly constructed DatabaseException exception (Error code 4) is thrown.
  2. When a related entity is deleted, some action is automatically taken for the entities that refer to this object. The exact action can be one of the following:
    1. Nullify

      The referencing object can be modified to change its referring property to null.
    2. Cascade

      The referencing object can be deleted.
    3. Abort

      The delete action can be aborted so that no dangling references are left.
IDL
interface Index {
  const unsigned short ONE_TO_ONE = 0;
  const unsigned short MANY_TO_ONE = 1;
  const unsigned short ONE_TO_MANY = 2;
  const unsigned short MANY_TO_MANY = 3;

  const unsigned short NULLIFY = 0;
  const unsigned short CASCADE = 1;
  const unsigned short ABORT = 2;

  readonly attribute DOMString name;
  readonly attribute DOMString keyPath;
  readonly attribute unsigned short cardinality;
  readonly attribute unsigned short onRelatedEntityDelete; 
  readonly attribute DOMString relatedEntity;
  
  Cursor entities(in optional any from, 
                  in optional bool inclusive, 
                  in optional any to, 
                  in optional bool inclusive, 
                  in optional unsigned short isolationLevel);
  delete(in any key);

  remove();
};

The ONE_TO_ONE cardinality option indicates that the secondary key is unique to the object. If an object is stored with a secondary key that already exists in the entity store.

The MANY_TO_ONE cardinality option indicates the secondary key may be used for multiple objects in the entity store. That is, the key appears more than once, but for each stored object it can be used only once.

The ONE_TO_MANY cardinality option indicates that the secondary key might be used more than once for a given object. Secondary keys themselves are assumed to be unique, but multiple instances of the secondary key can be used per object.

The MANY_TO_MANY cardinality option indicates there can be multiple keys for any given object, and for any given key there can be many related objects.

The NULLIFY foreign constraint action option indicates that all entities related to the deleted entity are updated so that the pertinent property in objects is nullified.

The CASCADE cardinality option indicates that the secondary key might be used more than once for a given object. Secondary keys themselves are assumed to be unique, but multiple instances of the secondary key can be used per object.

The ABORT foreign constraint action indicates that the delete operation is not allowed. This is the default behavior.

On getting, name will provide the name of the index.

On getting, keyPath will provide the path of the key used as the secondary key of objects stored in this index. This path can be the empty string if the object value is used as the key.

On getting, cardinality will describe whether duplicate objects can be found in the index for a given secondary key. If the cardinality is ONE_TO_ONE or ONE_TO_MANY, duplicates will not occur. For the other cardinality levels, duplicates may occur.

On getting, onRelatedEntityDelete identifies the foreign key constraint action to take when the related entity is deleted.

On getting, relatedEntity identifies the name of the entity store that holds the related foreign entity for enforcing foreign key constraints.

The entities() method is used to access objects of an entity store as arranged in the index. This method produces a Cursor object that is used to iterate over objects arranged by their secondary keys. The parameters of this method are used to limit the range of secondary keys over which the Cursor can be iterated. If an isolation level is provided, then it overrides the isolation level of the transaction used to obtain this entity store, and objects are accessed from the cursor with that level of isolation.

The delete() method is used to delete the object associated with the given secondary key in the entity store. by looking up the primary keys for the given secondary key and performing the deletion steps for each of the primary keys.

The remove() method is used to destroy the index. None of the objects stored in the entity store are affected.

3.6. Cursor

Cursors provide a mechanism by which to iterate over the records in a database. Cursors can be used to get, put, and delete database records. If a database allows duplicate records, then cursors are the only mechanism by which to access anything other than the first duplicate for a given key.

When a cursor is obtained from an index or an entity store, it is not positioned to any value. Before accessing data from a cursor, it is necessary to position the cursor to a particular object in the cursor's range. The cursor can be initialized any methods that move the cursor. If data is accessed from an uninitialized cursor, then it will cause a newly constructed DatabaseException exception (Error code 1) to be thrown.

IDL
interface Cursor {
  any first();
  any last();
  any next();
  any previous();
  any nextDup();
  any nextNoDup();
  any prevNoDup();
  any prevDup();
  
  // these methods do not move the cursor
  unsigned long long count();          
  any current();
  boolean update(any object);
  delete();
};

The count method returns the total number of objects that share the current key.

The first() method is used to position the cursor to the first unique key in the cursor's range.

The last() method is used to position the cursor to the last unique key in the cursor's range.

The next() method is used to position the cursor to the next key in the cursor's range. If the cursor is uninitialized, its position is set to the first key. In the presence of duplicate key values, the value of the key may not change.

The previous() method is used to position the cursor to the next key in the cursor's range. If the cursor is uninitialized, its position is set to the last unique key. In the presence of duplicate key values, the value of the key may not change.

The nextDup() method is used to position the cursor to the next duplicate object for the current key. If no such object exists, this method returns null.

The prevDup() method is used to position the cursor to the previous duplicate object for the current key. If no such object exists, this method returns null.

The nextNoDup() method is used to position the cursor to the next unique key. If no such object exists, this method returns null. If the cursor is uninitialized, its position is set to the first key.

The prevNoDup() method is used to position the cursor to the previous unique key. If no such object exists, this method returns null. If the cursor is uninitialized, its position is set to the last key.

The update() method is used to update the object in the entity store at which the cursor is currently positioned. If the object is updated, then this method returns true. The update method is only allowed if the Cursor is obtained from an entity store.

The delete() method is used to delete the object in the entity store at which the cursor is currently positioned. The cursor can be moved to the next (or the previous) position after deleting an object. If a cursor is positioned on a deleted entity, current() will return null, delete() will return false, and update() will return false.

The current() method is used to obtain the object at the current cursor position.

3.7. Entity Join

IDL
interface EntityJoin {
  addCondition(in Index index, 
               in optional any key);
  Cursor entities(in optional unsigned short isolationLevel);
};

The addCondition() method is used to add an index condition to the join operation. If the parameter index is already added to this join operation, then a newly constructed DatabaseException exception (Error code 1) is thrown. If the parameter index is not on the same entity store as the entity join itself, then also, a newly constructed DatabaseException exception (Error code 1) is thrown. The parameter key is used to set a starting point in the index.

The entities() is used to obtain a cursor positioned to the object in the entity store that satisfies all the conditions specified in the join. If an isolation level is specified, it overrides the level specified in the transaction.

3.8. Sequence

Sequences provide an arbitrary number of persistent objects that return an increasing or decreasing sequence of integers. Implementations may cache blocks of values for a sequence to reduce contention. It is not necessary that the implementation provide consecutive values as long as it maintains the monotonically increasing or decreasing sequence of values. It is necessary that values be spaced at least as far apart as the increment value used at the time of creating the sequence.

Example

If a sequence is created with the initial value 3 and increment of 2, then the first value will be 3. Subsequently, a sequence may offer values of 5, 7, and so on. It is also possible that two consecutive calls to obtain the next value produce non-consecutive odd numbers, e.g., 9 and 15.

IDL
interface Sequence {
  readonly attribute DOMString name;
  readonly attribute long long current;
  
  changeIncrement(long long increment);
  long long next();
  remove();
};

On getting, name will provide the name of the sequence.

On getting, current will provide the present value of the sequence. Unless a value is provided, the initial value of a sequence will be 1.

The changeIncrement() method is used to alter the amount by which the sequence is incremented (or decremented) each time the next() method is called.

The next() method is used to advance the sequence and obtain the next number in the sequence as calculated from its current value and increment. Unless a value is provided, the sequence will increment its value by 1.

The remove() method is used to destroy the sequence.

3.9. Queue

A queue is a simple first-in-first-out store that can hold data sorted by their arrival so as to enable fast insertion and sequential retrieval. Unlike entity stores, queues do not provide externally visible key-based retrieval of values. Nor is it possible to store items with an application specified key.

IDL
interface Queue {  
  readonly attribute DOMString name;

  push(in any object);
  any pop();
  
  remove();
};

On getting, name will provide the name of the queue.

The push() method is used to store the given object at the head of the queue.

The pop() method is used to delete the object that is at the head of the queue as ordered by their insertion in to the queue. The deleted object is also returned as result.

The remove() method is used to destroy the queue.

3.10. Exceptions and Errors

Errors in the asynchronous database API are reported using callbacks that have a DatabaseError object as one of their arguments.

IDL
interface DatabaseError {
  const unsigned short UNKNOWN_ERR = 0;
  const unsigned short NON_TRANSIENT_ERR = 1; 
  const unsigned short VERSION_ERR = 2; 
  const unsigned short NOT_FOUND_ERR = 3;
  const unsigned short CONSTRAINT_ERR = 4;
  const unsigned short DATA_ERR = 5;
  const unsigned short FEATURE_ERR = 6;
  const unsigned short SERIAL_ERR = 11;
  const unsigned short TOO_LARGE_ERR = 12;
  const unsigned short RECOVERABLE_ERR = 21;
  const unsigned shrot TRANSIENT_ERR = 31;
  const unsigned short TIMEOUT_ERR = 32;
  unsigned short code;
  int status;
  DOMString message;
};

The code IDL attribute must return the most appropriate code.

The status IDL attribute must return the detailed error status of the database.

The message IDL attribute must return an error message describing the error encountered. The message should be localized to the user's language.

IDL
exception DatabaseException {
  const unsigned short UNKNOWN_ERR = 0;
  const unsigned short NON_TRANSIENT_ERR = 1; 
  const unsigned short VERSION_ERR = 2; 
  const unsigned short NOT_FOUND_ERR = 3;
  const unsigned short CONSTRAINT_ERR = 4;
  const unsigned short DATA_ERR = 5;
  const unsigned short FEATURE_ERR = 6;
  const unsigned short SERIAL_ERR = 11;
  const unsigned short TOO_LARGE_ERR = 12;
  const unsigned short RECOVERABLE_ERR = 21;
  const unsigned shrot TRANSIENT_ERR = 31;
  const unsigned short TIMEOUT_ERR = 32;
  unsigned short code;
  int status;
  DOMString message;
};

The code IDL attribute must return the most appropriate code.

The status IDL attribute must return the detailed error status of the database.

The message IDL attribute must return an error message describing the exception raised. The message should be localized to the user's language.

The error codes and their meanings are given below.

ConstantCodeSituation
UNKNOWN_ERR 0 The operation failed for reasons unrelated to the database itself and not covered by any other error code.
NON_TRANSIENT_ERR 1 This error occurred because an operation was not allowed on an object. A retry of the same operation would fail unless the cause of the error is corrected.
VERSION_ERR 2 The operation failed because the actual database version was not what it should be. The Transaction.changeVersion() method was passed a version that doesn't match the actual database version.
NOT_FOUND_ERR 3 The operation failed because the requested database object could not be found. For example, an entity store did not exist but was being opened, or a sequence was used to create an entity store, but did not exist.
CONSTRAINT_ERR 4 A mutation operation in the transaction failed due to a because a constraint was not satisfied. For example, an object such as an entity store, sequence, or index already exists and a new one was being attempted to be created. In another example, an object was deleted but was required to satisfy the foreign key constraint on a related entity store.
DATA_ERR 5 Data provided to an operation does not meet requirements, e.g., if the increment value for a sequence is set to 0.
FEATURE_ERR 6 A feature defined in this API was used but is not supported by the underlying implementation.
SERIAL_ERR 11 The operation failed because of the size of the data set being returned or because there was a problem in serializing or deserializing the object being processed.
TOO_LARGE_ERR 12 The operation failed because the data returned from the database was too large.
RECOVERABLE_ERR 21 The operation failed because the database was prevented from taking an action. The operation might be able to succeed if the application performs some recovery steps and retries the entire transaction. For example, there was not enough remaining storage space, or the storage quota was reached and the user declined to give more space to the database.
TRANSIENT_ERR 31 The operation failed because of some temporary problems. The failed operation might be able to succeed when the operation is retried without any intervention by application-level functionality.
TIMEOUT_ERR 32 A lock for the transaction could not be obtained in a reasonable time.
DEADLOCK_ERR 33 The current transaction was automatically rolled back by the database becuase of deadlock or other transaction serialization failures.

4. Privacy

To be written

5. Security

To be written

A. References

A.1. Normative references

[HTML5]
HTML5: A vocabulary and associated APIs for HTML and XHTML, Ian Hickson, editor, W3C Working Draft, August 2009.
[RFC2119]
Key words for use in RFCs to Indicate Requirement Levels, S. Bradner. IETF, March 1997.
[WebIDL]
Web IDL, Cameron McCormack, editor. W3C Working Draft, December 2008.
[WebStorage]
Web Storage, Ian Hickson, editor. W3C Working Draft, September 2009.
[WebWorkers]
Web Workers, Ian Hickson, editor. W3C Working Draft, April 2009.