Copyright © 2009 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C liability, trademark and document use rules apply.
This document defines APIs for storing and retrieving ordered key-value pairs in a transactional database.
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.
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.
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()
.
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.
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);
});
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:
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]
This specification relies on several other underlying specifications.
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.
[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:
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.
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.
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.
openDatabase()
method: queue a task to to invoke the callback with result
as its only argument.
openDatabaseSync()
method: invoke the callback with result as its only argument.
If the callback throws an exception, rethrow that exception and abort these steps.
All strings including the empty string are valid database names. Database names must be compared in a case-sensitive manner.
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.
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.
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.
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.
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.
Transaction
object.
Let transaction be this object.
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.
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:
Transaction
object.
Let transaction be the newly created
Transaction
object.
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:
DatabaseException
exception
that represents the error that caused these substeps to be run.
Transaction
object
When the user agent is to create a
Transaction
object for a
transaction, it must run the following steps:
Transaction
object that represents that transaction. The user agent may wait for
appropriate locks to become available.
DatabaseException
exception and abort these steps.
(Error code 32.)
Transaction
object.
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.
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.
In the following example, a database can be initially setup with an entity store and a sequence used in that entity store.
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.
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.
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.
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.
In the following example, the put operation will store an object.
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.
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.
Continuing with the earlier example, the second put operation will replace the object stored by the first put operation.
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.
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.
Contrasted with the previous examples, the entity store below is designed
to use a Sequence
object.
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.
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.
Continuing with the previous examples, the entity store below is configured with an index on the 'name' attribute.
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.
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"]
});
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.
DatabaseException
exception (Error code 3)
and terminate these steps.
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.
DatabaseException
exception (Error code 4)
and terminate these steps.
Sequence
object
for the sequence name
DatabaseException
exception (Error code 5)
and terminate these steps.
DatabaseException
exception (Error code 3)
and terminate these steps.
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.
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.
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.
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:
DatabaseException
exception (Error code 4) is
thrown.
Nullify
The referencing object can be modified to change its referring property to null.Cascade
The referencing object can be deleted.Abort
The delete action can be aborted so that no dangling references are left.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.
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.
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.
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.
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.
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.
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.
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.
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.
Errors in the asynchronous database API are reported using
callbacks that have a
DatabaseError
object as one of their arguments.
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.
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.
Constant | Code | Situation |
---|---|---|
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. |