W3C

WebSimpleDB API

W3C Editor’s Draft 3 November 2009

Latest Published Version:
http://www.w3.org/TR/WebSimpleDB/
Latest Editor’s Draft:
http://dev.w3.org/2006/webapi/WebSimpleDB/
Previous Version:
http://www.w3.org/TR/2009/WD-WebSimpleDB-20090929/
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 3 November 2009 Editor’s Draft of the WebSimpleDB API specification. If you wish to make comments regarding this document, please send them to public-webapps@w3.org (subscribe, archives) with “[WebSimpleDB]” at the start of the subject line, or submit them using our public bug database.

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:

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 an Editor’s 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

A script can efficiently find items in an object 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' object 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 object 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 upgrade = 
  function(changes, db) {
    changes.createObjectStore('books', 'isbn');
    changes.createIndex('BookAuthor', 'Book', 'author', Index.MANY_TO_ONE);
    // now db.version === '1.0'
  };
var db = new DatabaseSync('books', '1.0', 'Book store', upgrade);
var index = db.getIndex('BookAuthor', 'Book', true);
var author = ...
var matching = index.get(author);
if (matching)
  report(matching.isbn, matching.name, matching.author);
else
  report(null);

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 object store called "docids" with the primary key "id". 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
var request = null;
function prepareDatabase(ready, error) {
  request = new DatabaseRequest();
  request.onsuccess = ready;
  request.onerror = error;
  var upgrade = 
    function(changes, db) {
      changes.createObjectStore('docids', 'id');
      // now db.version === '1.0'
    };
  request.open('documents', '1.0', 'Offline document storage', upgrade);
}

function showDocCount(db, span) {
  var storeRequest = new ObjectStoreRequest(db);
  storeRequest.onsuccess = 
    function() {
      var store = storeRequest.store, total = 0;
      var cursorRequest = new CursorRequest(store);
      cursorRequest.onsuccess = 
        function() {
          span.textContent = total;
        };
      cursorRequest.open(function(item, cursor) { 
          total += cursor.count; 
        }, Cursor.NEXT_NO_DUPLICATE);
    };
  storeRequest.open('docids', true);
};

prepareDatabase(function(evt) {
  // got database
  var span = document.getElementById('doc-count');
  showDocCount(request.database, span);
}, function (evt) {
  // error getting database
  var error = request.error;
  alert(error.message);
});
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
The terms and algorithms document base URL, event handler attributes, event handler event type, Function, origin, same origin, task, task source, and queue a task are defined by the HTML 5 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. A database persists keys and values in an ordered manner. Each database comprises:

Each database has a name and a human readable description. 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.

Each database also has a current version.

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.

When a database is opened, that creates a connection. There can be multiple connections to a given database. A connection that is attempting to read a given piece of data in a database is called a reader and one that is attempting to write that piece of data is called a writer.

3.1. Database

There is no way to enumerate the databases available for an origin from this API. A Database object represents a connection to a database.

IDL
interface Database {  
  readonly attribute DOMString     name;
  readonly attribute DOMString     description;  
  readonly attribute DOMString     version;
  readonly attribute DOMStringList objectStores;
  readonly attribute Transaction   currentTransaction;
};
name
On getting, this attribute must return the name of the database connected to this object.
description
On getting, this attribute must return the description of the database connected to this object.
version
On getting, this attribute must return the current version of the database connected to this object (as opposed to the expected version of the Database object).
objectStores
On getting, this attribute must return a list of names of the object stores currently in this database database connected to this object.
currentTransaction
On getting, this attribute must return a Transaction object corresponding to the transaction that is currently active in the database connected to this object.

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

3.2. Synchronous APIs

This API can only be used in a Worker [WebWorkers].

3.2.1. Opening a database connection

Example

In the following example, we open a database synchronously.

ECMAScript
var db = new DatabaseSync('AddressBook', '1', 'Address Book', function(txn, db) {...});

The synchronous API for databases blocks the calling thread until a DatabaseSync object is ready to return. Moreover, this API also throws a DatabaseException if an error occurs while the database is being opened.

IDL
[Constructor(in          DOMString       name, 
             in          DOMString       version, 
             in          DOMString       displayName, 
             in optional UpgradeCallback upgrade)]
interface DatabaseSync : Database {
  ObjectStoreSync getObjectStore(in          DOMString      name,
                                 in optional unsigned short mode) 
                                 raises      DatabaseException;
  IndexSync       getIndex(      in          DOMString name, 
                                 in          DOMString storeName) 
                                 raises      DatabaseException;
  TransactionSync transaction(   in optional DOMStringList storeNames, 
                                 in optional unsigned int  timeout) 
                                 raises      DatabaseException;
};

The DatabaseSync constructor takes the following arguments: a database name, a database version, a display name, and optionally an upgrade callback and isolation level. When this constructor is invoked, the user agent must run the following steps:

  1. Perform the database opening steps, and call its result as result.
  2. If the steps were aborted due to an error, then throw a newly constructed DatabaseException exception with the code of that error and abort these steps.
  3. Return result.

When the user agent is to perform database opening steps, with a database name, a database version, a display name, and optionally an upgrade callback, it must perform the following steps with all but the first two and the last steps being run atomically:

  1. Pick the appropriate steps
    If this method is invoked from an active document
    Let origin be the origin of the active document from which the method was invoked.
    If this method is invoked from a worker
    Let origin be the origin of the scripts in the worker.
  2. If there is already a database with the given name from the origin origin, then let db be that database.
  3. If the database version provided is not the empty string, and the db's version is different than the version provided to these steps, then set the needs upgrade flag.
  4. If no database with the given name from the origin origin exists, then
    1. Create the database db with the name and display name passed to these steps, and let db's version be the empty string.
    2. Set the needs upgrade flag.
  5. If needs upgrade flag is set and no upgrade callback is passed to these steps, then abort these steps with INVALID_STATE_ERR.
  6. Pick the appropriate steps:
    If these steps are performed to synchronously open a database
    Let result be a newly constructed DatabaseSync object representing a connection to db.
    If these steps are performed to asynchronously open a database
    Let result be a newly constructed Database object representing a connection to db.
  7. If needs upgrade is true, then perform the following steps:
    1. Create an exclusive transaction for result.
    2. If a system-level timeout duration is exceeded when creating the transaction, then abort these steps with TIMEOUT_ERR.
    3. Create a new Upgrade object called upgrade for the transaction just created.
    4. Queue a task to invoke the upgrade callback with upgrade and result as its arguments.

      If the callback throws an exception, abort these steps with UNKNOWN_ERR.

    5. Wait for task to be completed.
    6. Commit the transaction for upgrade.
    7. Store the verson passed to this method in db.
  8. Return result.
getObjectStore()

This method returns an ObjectStoreSync object to access the object store identified by the given name in the database to which this DatabaseSync object is connected. The mode passed to this method is used to create the

If an object store with the given name, compared in a case-sensitive manner, does not already exist in the database, then this method will throw a newly constructed DatabaseException exception with code NOT_FOUND_ERR.

getIndex()

This method returns an IndexSync object representing the named index on the given object store in this database.

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 with code NOT_FOUND_ERR.

transaction()
Perform the transaction creation steps with the array of database objects for acquiring locks required in this transaction and an optional timeout duration, and return the resulting TransactionSync object.

3.2.2. Upgrading databases

Example

In the following example, a database can be initially setup with an object store where the id property on stored objects is used as the key.

ECMAScript
var dbReq = new DatabaseRequest();
var upgrade = 
  function(changes, db) {
    changes.createObjectStore('Contact', 'id');
    // after this - db.version === '1'          
  };
dbReq.open('AddressBook', '1', 'Address Book', upgrade);

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

ECMAScript
var upgrade = 
  function(changes, db) {
    // db.version === '1'
    changes.createIndex('ContactName', 'Contact', 'name', Index.MANY_TO_ONE);
    // after this - db.version === '2'
  };
var db = new DatabaseSync('AddressBook', '2', 'Address Book', upgrade);
IDL
interface Upgrade {
  void createObjectStore(in          DOMString name, 
                         in          DOMString primaryKeyPath, 
                         in optional boolean   autoIncrement) 
                         raises DatabaseException;
  void createIndex(      in          DOMString      name, 
                         in          DOMString      storeName,
                         in          DOMString      keyPath, 
                         in optional unsigned short cardinality) 
                         raises DatabaseException;

  void removeObjectStore(in DOMString storeName) raises DatabaseException;
  void removeIndex(     in DOMString indexName, 
                        in DOMString storeName) 
                        raises DatabaseException;
};

[Callback=FunctionOnly, NoInterfaceObject]
interface UpgradeCallback {
  void upgrade(in Upgrade  txn,
               in Database db);
};
createObjectStore()

This method creates a new object store with the given name. The path of the primary key in objects stored in this object store is required to correctly create a unique identifier for each object. This path cannot be the empty string. If an optional sequence name is also provided, then the named 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 object store with the same name, compared in a case-sensitive manner, already exists, then this method will throw a newly constructed DatabaseException exception with code CONSTRAINT_ERR. If a sequence name is given for generating primary keys but a sequence with that name does not exist, then this method will throw a newly constructed DatabaseException exception with code NOT_FOUND_ERR.

createIndex()

This method is used to define a new index on this object store. An index is created using four required parameters - name, object store name, key path, and cardinality - and two optional parameters - action to be performed when related object is deleted, and the name of the related object.

If an index with the same name, compared in a case-sensitive manner, already exists, then this method will throw a newly constructed DatabaseException exception with code CONSTRAINT_ERR. If the object store with the given name does not exist, then this method will throw a newly constructed DatabaseException exception with code NOT_FOUND_ERR.

removeObjectStore()

This method is used to destroy the object store as well as all associated indices.

If an object 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 with code NOT_FOUND_ERR.

removeIndex()

This method is used to destroy the named index on the given object store.

If an index with the given name does not exist on the named object store, then this method will throw a newly constructed DatabaseException exception with code NOT_FOUND_ERR.

3.2.3. Object Store

An object 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 object store must follow the transaction used to create or obtain the object store.

Each ObjectStore 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 simple data type. They can be extracted from objects being stored or generated from a monotonic sequence.

An object 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

In the following example, we set up an object store to use the property id from objects to store as the key. This object store is designed to auto generate keys for each new object added to Contact.

ECMAScript
var upgrade = function(changes, db) {
                changes.createObjectStore('Contact', 'id', true);
              });

var db = new DatabaseSync('AddressBook', '1', 'Address Book', upgrade);

Using this database, we can store objects in the Contact object store.

ECMAScript
var store = db.getObjectStore('Contact');

var lincoln = {name: 'Lincoln', number: '7012'};
var contact = store.put(lincoln);
// contact.id === 1

A stored value can be retrieved using the same key used for storage.

ECMAScript
var contact = store.get(1);
// contact.name === 'Lincoln'

A second put operation will replace the object stored by the first put operation.

ECMAScript
var abraham = {id: 1, name: 'Abraham', number: '2107'};
store.put(abraham);

Now when the object store is read with the same key, the result is different compared to the object read earlier.

ECMAScript
var contact = store.get(1);
// contact.id === 1 && contact.name === 'Abraham';

Additionally, all the objects of an object store matching a certain range of secondary or primary keys can be retrieved in key order.

ECMAScript
var range = new CursorRange(2, 4, CursorRange.INCLUDE_START & CursorRange.INCLUDE_END);
var count = store.forEach(function(key, cursor, value) {
              // each value is a contact and each key is the id for that  
              // contact whose id is between 2 and 4, both inclusive
              }, range);

An object store arranges stored objects by their key, also referred to as the primary key. Each object contains the value of its primary key. Additionally, object 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 object store.

IDL
interface ObjectStore {  
           const     unsigned short READ_WRITE    = 1;
           const     unsigned short READ_ONLY     = 2;
           const     unsigned short DETACHED_READ = 6;

  readonly attribute DOMString      name;
  readonly attribute DOMString      keyPath;
  readonly attribute DOMStringList  indexNames;
  readonly attribute unsigned short mode;
};

interface ObjectStoreSync : ObjectStore {
  unsigned long long forEach(in          CursorCallback callback,
                             in optional CursorRange    range, 
                             in optional unsigned short direction);

  any                put(    in any obj) raises DatabaseException;
  void               delete( in any key) raises DatabaseException;
  any                get(    in any key) raises DatabaseException;
};

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

On getting, keyPath will provide the path of the key used as the primary key of objects stored in this object 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 object store.

The put() method is used to store the given object in the object 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 object 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 with code NOT_FOUND_ERR and terminate these steps.
  2. Use the key to look up an object in the object store. Let value be that object.
  3. If object is not found in the object store, then throw a newly constructed DatabaseException exception with code NOT_FOUND_ERR and terminate these steps.

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

3.2.4. 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.

Example

An object can be retrieved using secondary keys, provided these keys are defined for the object store holding such objects. If the upgrade() function above were redefined as below, an index would be maintained on the name property of objects in Contact.

ECMAScript
var upgrade = function(changes, db) {
                changes.createObjectStore('Contact', 'id', true);
                changes.createIndex('ContactName', 'Contact', 'name', Index.MANY_TO_ONE);
              });

For example, the id of an object with name property value as 'Lincoln' can be retrieved using this ContactName index.

ECMAScript
var index = db.getIndex('ContactName', 'Contact');
var id = index.get('Lincoln');
// id === 1

If, however, we wanted to retrieve the object with name property value as 'Lincoln' can be retrieved using this ContactName index.

ECMAScript
var index = db.getIndex('ContactName', 'Contact');
var contact = index.getObject('Lincoln');
// contact.id === 1

Additionally, all the objects of an object store matching a certain range of secondary or primary keys can be retrieved in key order. When objects are retrieved from the Contact object 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.

ECMAScript
var range = new CursorRange('L', 'M');
var count = index.forEachObject(function(key, cursor, value) {
              // each value is a contact and each key is the name for that  
              // contact whose name's first letter is either L or M
              }, range);

If, on the other hand, we only want to names but not the contact objects for a given range, then we can use a different mechanism for that.

ECMAScript
var range = new CursorRange('L', 'M');
var count = index.forEach(function(key, cursor) {
              // each key is the id of a contact  
              // whose name's first letter is either L or M
              }, range);
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;

  readonly attribute DOMString      name;
  readonly attribute DOMString      keyPath;
  readonly attribute unsigned short cardinality;
};

interface IndexSync: Index { 
  unsigned long long forEach(      in          CursorCallback callback,
                                   in optional CursorRange    range, 
                                   in optional unsigned short direction);
  unsigned long long forEachObject(in          CursorCallback callback,
                                   in optional CursorRange    range, 
                                   in optional unsigned short direction);
  any                get(          in          any            key) raises DatabaseException;
  any                getObject(    in          any            key) raises DatabaseException;
  void               delete(       in          any            key) raises DatabaseException;
};

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 object store.

The MANY_TO_ONE cardinality option indicates the secondary key may be used for multiple objects in the object 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.

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.

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

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

3.2.5. Cursor

Cursors are a mechanism by which applications 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. Although storage operations can be performed on a cursor, a cursor itself is a transient object with all the real storage happening in objects that yield the cursor.

The initial position of a cursor is set by specifying the direction in which the cursor should iterate over matching items. Once the cursor is created, it yield items only in that direction.

Example

By default, a cursor walks over objects starting at the first item and ending at the last item including all the duplicates encountered along the way. If the cursor callback returns true, then the iteration is stopped.

ECMAScript
var objects = ...
var cursor = objects.forEach(function(object) {;
  // act on each object and return true to exit the cursor  
});

To start at the last item and end in the first item, the cursor should be created with the direction parameter.

ECMAScript
var objects = ...
var count = objects.forEach(function(object) {;
  // act on each object and return true to exit the cursor  
}, Cursor.PREV);

To start at a certain key and end in the last item, i.e., for a lower-bounded cursor, while skipping duplicates, the cursor should be created with both the required start key and the direction parameter.

ECMAScript
var objects = ...
var range = new CursorRange(CursorRange.INDIVIDUAL, key);
objects.forEach(function(object) {
  // act on each object and return true to exit the cursor  
}, range, Cursor.NEXT_NO_DUPLICATE);

It is also possible to create a bounded cursor, i.e., with application-specified starting and ending points, the cursor should be created with both the required keys. If the range is inclusive of both keys, then additional flags are required. In the following example, all keys with values in the inclusive range (start, end) are returned with all their duplicates, from the beginning of the range to its end.

ECMAScript
var objects = ...
var range = new CursorRange(CursorRange.INCLUDE_START & CursorRange.INCLUDE_END, start, end);
objects.forEach(function(object) {
  // act on each object and return true to exit the cursor  
}, range);
IDL
interface Cursor {
           const     unsigned short     NEXT = 0;
           const     unsigned short     NEXT_NO_DUPLICATE = 1;
           const     unsigned short     PREV = 2;
           const     unsigned short     PREV_NO_DUPLICATE = 3;
  
  readonly attribute unsigned short     direction;
  readonly attribute unsigned long long count;

  void    continue(in optional CursorRange skipTo);
  boolean update(  in          any         obj);
  void    delete();
};

[Constructor(in unsigned short mode, in optional any start, in optional end)]
interface CursorRange {
           const     unsigned short INCLUDE_START = 1;
           const     unsigned short INCLUDE_END = 2;
           const     unsigned short INDIVIDUAL = 4;
  
  readonly attribute any            start;
  readonly attribute any            end;
  readonly attribute unsigned short mode;
};

[FunctionOnly, NoInterfaceObject]
interface CursorCallback {
  bool handle(in          any    key, 
              in          Cursor cursor,
              in optional any    value);
};

On getting count, the user agent returns the total number of objects that share the current key.

On getting direction, the user agent returns the the traversal direction of the cursor.

The forEach() method is used to navigate items in a cursor in the direction identified by the direction parameter, which can be either of NEXT, NEXT_NO_DUPLICATE, PREV, or PREV_NO_DUPLICATE. If direction is unspecified, then the cursor uses the NEXT direction. The parameters inclusiveFrom and inclusiveTo are used to signal whether ranges are open or closed on the range boundaries.

For each object that matches the cursor condition, the user agent calls the CursorCallback. This callback is passed two parameters - the object that matches the cursor condition, and a Cursor object to make changes to the database at the Cursor position. If this callback returns a true value, then the cursor traversal is exited whether or not the range conditions have been satisfied.

The NEXT direction indicates that cursor should yield all values, including duplicates starting from the beginning of the range to its end.

The NEXT_NO_DUPLICATE direction indicates that cursor should return all values, skipping over duplicate objects for every key starting from the beginning of the range to its end. For every key with duplicate values, only the first value is yielded.

The PREV direction indicates that cursor should yield all values, including duplicates starting from the end of the range to its beginning.

The PREV_NO_DUPLICATE direction indicates that cursor should return all values, skipping over duplicate objects for every key starting from the end of the range to its beginning. For every key with duplicate values, only the first value is yielded.

The update() method is used to update the object 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 object store. The key of the object parameter must match the key in the current cursor position. Otherwise, the database will throw a newly constructed DatabaseException exception with code DATA_ERR.

The delete() method is used to delete the object at which the cursor is currently positioned. The delete() method will return false if this method is called more than once inside a CursorCallback.

3.2.6. Transaction

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

A transaction may be used to store multiple data items or to conditionally modify certain data items.

A Database object may have at most one transaction at any given time.

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 a long running transaction.

IDL
interface TransactionSync {  
  void abort()  raises DatabaseException;
  void commit() raises DatabaseException;
};
abort()
This 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.
commit()
This 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 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.

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

  1. Pick the appropriate steps:
    If these steps are not called with an array of database objects
    Acquire a lock on the entire database.
    If these steps are called with a non-empty array of database objects
    1. If any of the objects in this array is not a database object, then set code to NON_TRANSIENT_ERR and jump to the last step.
    2. Acquire appropriate locks on each of the database objects and the objects they depend on in an ordered sequence.
      Note
      If the database does not allow fine grained locking, then a user agent may obtain a shared or exclusive lock on the database.
    If these steps are called with an empty array of database objects
    Do not acquire locks on any database objects now. Locks are obtained as the application attempts to access those objects.
    Note
    Using the database in this style may lead to deadlocks. Programmers must use caution to acquire and release database objects in the same global order to avoid deadlocks.
  2. If a timeout duration is passed to these steps and is exceeded when acquiring locks, then set code to TIMEOUT_ERR and jump to the last step.
  3. Open a new transaction to the database, and create a Transaction object that represents that transaction. Let transaction be this object.
  4. Set the current transaction of this Database object to transaction.
  5. Return transaction. End these steps.
  6. This step is only performed in case of error. If this step is performed asynchronously, then run the database failure steps with the code code. Otherwise, raise a newly constructed DatabaseException exception exception with the code code.

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 with code NON_TRANSIENT_ERR. To perform database operations under the control of a new transaction, a fresh Transaction object is needed.

3.3. Asynchronous APIs

The asynchronous database API returns without blocking the calling thread. This API can only be used in a Window or in a Worker [WebWorkers].

IDL
[NoInterfaceObject]
interface DBRequest {
  void abort();
  
  // states
           const     unsigned short INITIAL = 0;
           const     unsigned short LOADING = 1;
           const     unsigned short DONE = 2;
  
  readonly attribute unsigned short readyState;
  
  readonly attribute DatabaseError  error;
  
  //event handler attributes
           attribute Function       onsuccess;
           attribute Function       onerror;
};

DBRequest implements EventTarget;

3.3.1. Event summary

This section is non-normative.

Events are fired during asynchronous database access as database objects are created and data is consumed from these objects. As requests for database objects are made, the user agent loads information about them into memory and when the required object handle is available, it alerts the application through the firing of events. The events are as follows:

Event nameInterfaceDispatched when...
success Event The database request has been completed and its results are available.
error DatabaseErrorEvent There was an error performing the database request.
commit Event The transaction commit has been completed.
abort Event The transaction abort has been completed.

3.3.2. The DatabaseRequest Interface

This interface provides methods for opening databases and accessing database objects using event handler attributes [DOM3Events].

Example

In the following example, we open a database asynchronously. Various event handlers are registered for responding to various situations.

ECMAScript
var dbReq = new DatabaseRequest();
dbReq.onsuccess = function(evt) {...};
dbReq.onerror = function(evt) {...};
dbReq.open('AddressBook', '1', 'Address Book', function(txn, db) {...});

This API uses an event handler mechanism to obtain database objects. If an error occurs while opening the database, an error handler is invoked. Otherwise, a success handler is invoked.

IDL
interface DatabaseRequest : DBRequest{
  readonly attribute Database database;
  
  void open(in          DOMString       name, 
            in          DOMString       version, 
            in          DOMString       displayName, 
            in optional UpgradeCallback upgrade,
            in optional unsigned short  isolation);
};

The open() method takes the following arguments: a database name, a database version, a display name, an optional upgrade callback, and an optional isolation level. When called, this method must return immediately and asynchronously perform the following steps, with all but the first two and last steps being run atomically:

  1. Queue a task to fire an event with the name success, with no namespace, which does not bubble, is not cancelable, and which uses the DatabaseEvent interface and result at each Window or WorkerUtils object.

The database failure steps performed with an error code are as follows:

  1. Create a DatabaseError object error where code is the error code passed to these steps and status is 0.
  2. Queue a task to fire an event with the name error, with no namespace, which does not bubble, is not cancelable, and which uses the DatabaseErrorEvent interface and error at each Window or WorkerUtils object.

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

3.3.3. Object Store

Example

In the following example, we open the same database as in the earlier example using the asynchronous API. The storeReq is initialized with an open() call.

ECMAScript
var store = storeReq = null;
var dbReq = new DatabaseRequest();
dbReq.onsuccess = 
  function() {
    storeReq = new ObjectStoreRequest(dbReq.database);
    storeReq.open('Contact');
  };
var upgrade = 
  function(upgrade, db) {
    upgrade.createObjectStore('Contact', 'id');
  });
dbReq.open('AddressBook', '1', 'Address Book');

An asynchronous call to put an object in the object store can be performed once the open() call succeeds.

ECMAScript
function saveContact(contact, success) {
  storeReq.onsuccess = success;
  storeReq.put(contact);
};

var lincoln = {id: 1, name: 'Lincoln', number: '7012'};
saveContact(lincoln, function() {
  //storeReq.result.id == 1
});

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

ECMAScript
function loadContact(id, success) {
  storeReq.onsuccess = success;
  storeReq.get(id);
};

loadContact(1, function() {
  // storeReq.result.id === 1 && storeReq.result.name === 'Lincoln';
});
IDL
[Constructor(in Database db)]
interface ObjectStoreRequest : DBRequest {
  readonly attribute any result;
  
  void open(  in          DOMString      name,
              in optional unsigned short mode);
  void put(   in          any            obj);
  void get(   in          any            key);
  void delete(in          any            key);
};

The put() method is used to store the given object in the object 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 object store. The following steps must be run for retrieving the object with the following parameters: key.

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

3.3.4. Index

IDL
[Constructor(in Database db)]
interface IndexRequest : DBRequest {
  readonly attribute any result;
  
  void open(     in DOMString name,
                 in DOMString storeName,
                 in optional boolean forReadonly); 
  void get(      in any key);
  void getObject(in any key);
  void delete(   in any key);
};

3.3.5. Cursor

IDL
[Constructor(in Database db)]
interface CursorRequest : DBRequest {
  readonly attribute unsigned long long count;
           attribute CursorCallback     callback;
           
  void readStore(      in          DOMString      storeName, 
                       in optional CursorRange    range, 
                       in optional unsigned short direction);
  void readIndex(      in          DOMString      storeName, 
                       in          DOMString      indexName,
                       in optional CursorRange    range, 
                       in optional unsigned short direction);
  void readIndexObject(in          DOMString      storeName, 
                       in          DOMString      indexName,
                       in optional CursorRange    range, 
                       in optional unsigned short direction);
};

3.3.6. Transaction

IDL
interface Transaction : TransactionSync {
// events
            attribute Function oncommitted;
            attribute Function onaborted;
};

[Constructor(in Database db)]
interface TransactionRequest : DBRequest {
  readonly attribute Transaction transaction;
  
  void open(in optional DOMStringList storeNames,
            in optional unsigned int  timeout);
};

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

The transaction steps are as follows. These steps are invoked with a transaction callback, and the following optional arguments - a success callback, an error callback, an array of database objects for acquiring locks required in this transaction, and a timeout duration.

  1. Perform the transaction creation steps with the array of database objects and timeout duration passed to these steps. Let the resulting Transaction object be transaction.
  2. Queue a task to invoke the transaction callback with transaction as its only argument, and wait for that task to be run.
  3. 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.
  4. Queue a task to invoke the success callback.
  5. End these steps.
  6. This step is only used when something goes wrong.
    1. If transaction is still not committed or rolled back, then rollback the transaction.
    2. If an error callback was passed to these steps, then queue a task to invoke the it with a newly constructed DatabaseError with code UNKNOWN_ERR.

3.3.7. Events

The following are the event handlers (and their corresponding event handler event types) that must be supported, as IDL attributes, by objects implementing the various interfaces:

Event handler Supported by interfaces Event handler event type
onsuccess success DatabaseRequest
onerror error DatabaseRequest
oncommitted commit TransactionRequest
onaborted abort TransactionRequest

3.4. Algorithms

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 with code CONSTRAINT_ERR 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 with code DATA_ERR and terminate these steps.
  5. Insert the object with key as its key. If any indexes are maintained for objects in this object 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. Retrieval steps

The retrieval steps are as follows. These 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 with code NOT_FOUND_ERR and terminate these steps.
  2. Use the key to look up an object in the object store. Let value be that object.
  3. If object is not found in the object store, then throw a newly constructed DatabaseException exception with code NOT_FOUND_ERR and terminate these steps.

3.4.3. 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 with code NOT_FOUND_ERR and terminate these steps.
  2. Use the key to look up an object in the object store. Let object be that object.
  3. If any indexes are maintained for objects in this object 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 object store.

3.5. 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.

ConstantSituation
UNKNOWN_ERR The operation failed for reasons unrelated to the database itself and not covered by any other error code.
NON_TRANSIENT_ERR 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 The operation failed because the actual database version was not what was expected or no upgrade function was passed even though the requested version was not found or the upgrade function threw an exception.
NOT_FOUND_ERR The operation failed because the requested database object could not be found. For example, an object store did not exist but was being opened, or a sequence was used to create an object store, but did not exist.
CONSTRAINT_ERR A mutation operation in the transaction failed due to a because a constraint was not satisfied. For example, an object such as an object store, sequence, or index already exists and a new one was being attempted to be created.
DATA_ERR Data provided to an operation does not meet requirements, e.g., if the increment value for a sequence is set to 0.
FEATURE_ERR A feature defined in this API was used but is not supported by the underlying implementation.
SERIAL_ERR 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 The operation failed because the data returned from the database was too large.
RECOVERABLE_ERR 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 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 A lock for the transaction could not be obtained in a reasonable time.
DEADLOCK_ERR The current transaction was automatically rolled back by the database becuase of deadlock or other transaction serialization failures.

4. Privacy

4.1. User tracking

A third-party host (or any object capable of getting content distributed to multiple sites) could use a unique identifier stored in its client-side database to track a user across multiple sessions, building a profile of the user's activities. In conjunction with a site that is aware of the user's real idobject (for example an e-commerce site that requires authenticated credentials), this could allow oppressive groups to target individuals with greater accuracy than in a world with purely anonymous Web usage.

There are a number of techniques that can be used to mitigate the risk of user tracking:

Blocking third-party storage
User agents may restrict access to the database objects to scripts originating at the domain of the top-level document of the browsing context, for instance denying access to the API for pages from other domains running in iframes.
Expiring stored data

User agents may automatically delete stored data after a period of time.

This can restrict the ability of a site to track a user, as the site would then only be able to track the user across multiple sessions when he authenticates with the site itself (e.g. by making a purchase or logging in to a service).

However, this also puts the user's data at risk.

Treating persistent storage as cookies

User agents should present the database feature to the user in a way that associates them strongly with HTTP session cookies. [COOKIES]

This might encourage users to view such storage with healthy suspicion.

Site-specific white-listing of access to databases

User agents may require the user to authorize access to databases before a site can use the feature.

Origin-tracking of stored data

User agents may record the origins of sites that contained content from third-party origins that caused data to be stored.

If this information is then used to present the view of data currently in persistent storage, it would allow the user to make informed decisions about which parts of the persistent storage to prune. Combined with a blacklist ("delete this data and prevent this domain from ever storing data again"), the user can restrict the use of persistent storage to sites that he trusts.

Shared blacklists

User agents may allow users to share their persistent storage domain blacklists.

This would allow communities to act together to protect their privacy.

While these suggestions prevent trivial use of this API for user tracking, they do not block it altogether. Within a single domain, a site can continue to track the user during a session, and can then pass all this information to the third party along with any identifying information (names, credit card numbers, addresses) obtained by the site. If a third party cooperates with multiple sites to obtain such information, a profile can still be created.

However, user tracking is to some extent possible even with no cooperation from the user agent whatsoever, for instance by using session identifiers in URLs, a technique already commonly used for innocuous purposes but easily repurposed for user tracking (even retroactively). This information can then be shared with other sites, using using visitors' IP addresses and other user-specific data (e.g. user-agent headers and configuration settings) to combine separate sessions into coherent user profiles.

4.3. Sensitivity of data

User agents should treat persistently stored data as potentially sensitive; it's quite possible for e-mails, calendar appointments, health records, or other confidential documents to be stored in this mechanism.

To this end, user agents should ensure that when deleting data, it is promptly deleted from the underlying storage.

5. Authorization

5.1. DNS spoofing attacks

Because of the potential for DNS spoofing attacks, one cannot guarantee that a host claiming to be in a certain domain really is from that domain. To mitigate this, pages can use SSL. Pages using SSL can be sure that only pages using SSL that have certificates identifying them as being from the same domain can access their databases.

5.2. Cross-directory attacks

Different authors sharing one host name, for example users hosting content on geocities.com, all share one set of databases.

There is no feature to restrict the access by pathname. Authors on shared hosts are therefore recommended to avoid using these features, as it would be trivial for other authors to read the data and overwrite it.

Even if a path-restriction feature was made available, the usual DOM scripting security model would make it trivial to bypass this protection and access the data from any path.

5.3. Implementation risks

The two primary risks when implementing these persistent storage features are letting hostile sites read information from other domains, and letting hostile sites write information that is then read from other domains.

Letting third-party sites read data that is not supposed to be read from their domain causes information leakage, For example, a user's shopping wishlist on one domain could be used by another domain for targeted advertising; or a user's work-in-progress confidential documents stored by a word-processing site could be examined by the site of a competing company.

Letting third-party sites write data to the persistent storage of other domains can result in information spoofing, which is equally dangerous. For example, a hostile site could add items to a user's wishlist; or a hostile site could set a user's session identifier to a known ID that the hostile site can then use to track the user's actions on the victim site.

Thus, strictly following the origin model described in this specification is important for user security.

A. References

A.1. Normative references

[COOKIES]
HTTP State Management Mechanism, A. Barth. IETF, August 2009.
[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.

B. Requirements

Editorial note
Requirements will be written with an aim to verify that these were actually met by the API specified in this document.