This is a snapshot of an early working draft and has therefore been superseded by the HTML standard.

This document will not be further updated.

HTML 5

Call For Comments — 27 October 2007

4.11. Client-side database storage

4.11.1. Introduction

...

4.11.2. Databases

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 a domain from this API.

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.

The openDatabase() method returns a Database object. The method takes four arguments: a database name, a database version, a display name, and an estimated size, in bytes, of the data that will be stored in the database.

If the database version provided is not the empty string, and the database already exists but has a different version, then the method must raise an INVALID_STATE_ERR exception.

Otherwise, if the database provided is the empty string, or if the database doesn't yet exist, or if the database exists and the version provided to the openDatabase() method is the same as the current version associated with the database, then the method must return a Database object representing the database associated with the origin of the active document of the browsing context of the Window object on which the method was called that has the name that was given. If no such database exists, it must be created first.

All strings including the empty string are valid database names. Database names are case-sensitive.

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.

User agents are expected to use the display name and the estimated database size to optimise the user experience. For example, a user agent could use the estimated size to suggest an initial quota to the user. This allows a site that is aware that it will try to use hundreds of megabytes to declare this upfront, instead of the user agent prompting the user for permission to increase the quota every five megabytes.

interface Database {
  readonly attribute DOMString version;
  void changeVersion(in DOMString oldVersion, in DOMString newVersion, in SQLTransactionCallback callback, in SQLTransactionErrorCallback errorCallback);
  void transaction(in SQLTransactionCallback callback);
  void transaction(in SQLTransactionCallback callback, in SQLTransactionErrorCallback errorCallback);
};

interface SQLTransactionCallback {
  void handleEvent(in SQLTransaction transaction);
};

interface SQLTransactionErrorCallback {
  boolean handleEvent(in SQLError error);
};

The transaction() method takes one or two arguments. When called, the method must immediately return and then asynchronously run the transaction steps with the transaction callback being the first argument, the error callback being the second argument, if any, and with no preflight operation or postflight operation.

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.

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

The changeVersion() method allows scripts to atomically verify the version number and change it at the same time as doing a schema update. When the method is invoked, it must immediately return, and then asynchronously run the transaction steps with the transaction callback being the third argument, the error callback being the fourth argument, the preflight operation being the following:

  1. Check that the value of the first argument to the changeVersion() method exactly matches the database's actual version. If it does not, then the preflight operation fails.

...and the postflight operation being the following:

  1. Change the database's actual version to the value of the second argument to the changeVersion() method.
  2. Change the Database object's expected version to the value of the second argument to the changeVersion() method.

4.11.3. Executing SQL statements

The transaction() and changeVersion() methods invoke callbacks with SQLTransaction objects.

typedef sequence<Object> ObjectArray;

interface SQLTransaction {
  void executeSql(in DOMString sqlStatement, in ObjectArray arguments);
  void executeSql(in DOMString sqlStatement, in ObjectArray arguments, in SQLStatementCallback callback);
  void executeSql(in DOMString sqlStatement, in ObjectArray arguments, in SQLStatementCallback callback, in SQLStatementErrorCallback errorCallback);
};

interface SQLStatementCallback {
  void handleEvent(in SQLTransaction transaction, in SQLResultSet resultSet);
};

interface SQLStatementErrorCallback {
  boolean handleEvent(in SQLTransaction transaction, in SQLError error);Or should these arguments be the other way around? Either way we're inconsistent with _something_. What should we be consistent with?
};

When the executeSql(sqlStatement, arguments, callback, errorCallback) method is invoked, the user agent must run the following algorithm. (This algorithm is relatively simple and doesn't actually execute any SQL — the bulk of the work is actually done as part of the transaction steps.)

  1. If the method was not invoked during the execution of a SQLTransactionCallback, SQLStatementCallback, or SQLStatementErrorCallback then raise an INVALID_STATE_ERR exception. (Calls from inside a SQLTransactionErrorCallback thus raise an exception. The SQLTransactionErrorCallback handler is only called once a transaction has failed, and no SQL statements can be added to a failed transaction.)

  2. Parse the first argument to the method (sqlStatement) as an SQL statement, with the exception that ? characters can be used in place of literals in the statement. [SQL]

  3. Replace each ? placeholder with the value of the argument in the arguments array with the same position. (So the first ? placeholder gets replaced by the first value in the arguments array, and generally the nth ? placeholder gets replaced by the nth value in the arguments array.)

    The result is the statement.

  4. If the syntax of sqlStatement is not valid (except for the use of ? characters in the place of literals), or the statement uses features that are not supported (e.g. due to security reasons), or the number of items in the arguments array is not equal to the number of ? placeholders in the statement, or the statement cannot be parsed for some other reason, then mark the statement as bogus.

  5. If the Database object has an expected version that is neither the empty string nor the actual version of the database, then mark the statement as bogus. (Error code 2.)

  6. Queue up the statement in the transaction, along with the third argument as the statement's result set callback and the fourth argument (if any) as the error callback.

The user agent must act as if the database was hosted in an otherwise completely empty environment with no resources. For example, attempts to read from or write to the filesystem will fail.

User agents should limit the total amount of space allowed for each origin, but may prompt the user and extend the limit if a database is reaching its quota. User agents should allow users to see how much space each database is using.

A mostly arbitrary limit of five megabytes per origin is recommended. Implementation feedback is welcome and will be used to update this suggestion in future.

SQL inherently supports multiple concurrent connections. Authors should make appropriate use of the transaction features to handle the case of multiple scripts interacting with the same database simultaneously (as could happen if the same page was opened in two different browsing contexts).

User agents must consider statements that use the BEGIN, COMMIT, and ROLLBACK SQL features as being unsupported (and thus will mark them as bogus), so as to not let these statements interfere with the explicit transactions managed by the database API itself.

A future version of this specification will probably define the exact SQL subset required in more detail.

4.11.4. Database query results

The executeSql() method invokes its callback with a SQLResultSet object as an argument.

interface SQLResultSet {
  readonly attribute int insertId;
  readonly attribute int rowsAffected;
  readonly attribute SQLResultSetRowList rows;
};

The insertId attribute must return the row ID of the row that the SQLResultSet object's SQL statement inserted into the database, if the statement inserted a row. If the statement inserted multiple rows, the ID of the last row must be the one returned. If the statement did not insert a row, then the attribute must instead raise an INVALID_ACCESS_ERR exception.

The rowsAffected attribute must return the number of rows that were affected by the SQL statement. If the statement did not affected any rows, then the attribute must return zero. For "SELECT" statements, this returns zero (querying the database doesn't affect any rows).

The rows attribute must return a SQLResultSetRowList representing the rows returned, in the order returned by the database. If no rows were returned, then the object will be empty.

interface SQLResultSetRowList {
  readonly attribute unsigned long length;
  DOMObject item(in unsigned long index);
};

SQLResultSetRowList objects have a length attribute that must return the number of rows it represents (the number of rows returned by the database).

The item(index) attribute must return the row with the given index index. If there is no such row, then the method must raise an INDEX_SIZE_ERR exception.

Each row must be represented by a native ordered dictionary data type. In the ECMAScript binding, this must be Object. Each row object must have one property (or dictionary entry) per column, with those properties enumerating in the order that these columns were returned by the database. Each property must have the name of the column and the value of the cell, as they were returned by the database.

4.11.5. Errors

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

interface SQLError {
  readonly attribute unsigned int code;
  readonly attribute DOMString message;
};

The code DOM attribute must return the most appropriate code from the following table:

Code Situation
0 The transaction failed for reasons unrelated to the database itself and not covered by any other error code.
1 The statement failed for database reasons not covered by any other error code.
2 The statement failed because the expected version of the database didn't match the actual database version.
3 The statement failed because the data returned from the database was too large. The SQL "LIMIT" modifier might be useful to reduce the size of the result set.
4 The statement failed because there was not enough remaining storage space, or the storage quota was reached and the user declined to give more space to the database.
5 The statement failed because the transaction's first statement was a read-only statement, and a subsequent statement in the same transaction tried to modify the database, but the transaction failed to obtain a write lock before another transaction obtained a write lock and changed a part of the database that the former transaction was dependending upon.
6 An INSERT, UPDATE, or REPLACE statement failed due to a constraint failure. For example, because a row was being inserted and the value given for the primary key column duplicated the value of an existing row.

We should define a more thorough list of codes. Implementation feedback is requested to determine what codes are needed.

The message DOM attribute must return an error message, localised to the user's language, describing the error encountered.

4.11.6. Processing model

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

  1. Open a new SQL transaction to the database, and create a SQLTransaction object that represents that transaction.

  2. If an error occured in the opening of the transaction, jump to the last step.

  3. If a preflight operation was defined for this instance of the transaction steps, run that. If it fails, then jump to the last step. (This is basically a hook for the changeVersion() method.)

  4. Invoke the transaction callback with the aforementioned SQLTransaction object as its only argument.

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

  6. While there are any statements queued up in the transaction, perform the following steps for each queued up statement in the transaction, oldest first. Each statement has a statement, a result set callback, and optionally an error callback.

    1. If the statement is marked as bogus, jump to the "in case of error" steps below.

    2. Execute the statement in the context of the transaction. [SQL]

    3. If the statement failed, jump to the "in case of error" steps below.

    4. Create a SQLResultSet object that represents the result of the statement.

    5. Invoke the statement's result set callback with the SQLTransaction object as its first argument and the new SQLResultSet object as its second argument.

    6. If the callback was invoked and raised an exception, jump to the last step in the overall steps.

    7. Move on to the next statement, if any, or onto the next overall step otherwise.

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

    1. If the statement had an associated error callback, then invoke that error callback with the SQLTransaction object and a newly constructed SQLError object that represents the error that caused these substeps to be run as the two arguments, respectively.

    2. If the error callback returns false, then move on to the next statement, if any, or onto the next overall step otherwise.

    3. Otherwise, the error callback did not return false, or there was no error callback. Jump to the last step in the overall steps.

  7. If a postflight operation was defined for his instance of the transaction steps, run that. If it fails, then jump to the last step. (This is basically a hook for the changeVersion() method.)

  8. Commit the transaction.

  9. If an error occured in the committing of the transaction, jump to the last step.

  10. End these steps. The next step is only used when something goes wrong.

  11. Call the error callback with a newly constructed SQLError object that represents the last error to have occured in this transaction. If the error callback returned false, and the last error wasn't itself a failure when committing the transaction, then try to commit the transaction. If that fails, or if the callback couldn't be called (e.g. the method was called with only one argument), or if it didn't return false, then rollback the transaction. Any still-pending statements in the transaction are discarded.

4.11.7. Privacy

In contrast with the globalStorage feature, which intentionally allows data to be accessed across multiple domains, protocols, and ports (albeit in a controlled fashion), this database feature is limited to scripts running with the same origin as the database. Thus, it is expected that the privacy implications be equivalent to those already present in allowing scripts to communicate with their originating host.

User agents are encouraged to treat data stored in databases in the same way as cookies for the purposes of user interfaces, to reduce the risk of using this feature for cookie resurrection.

4.11.8. Security

4.11.8.1. User agents

User agent implementors are strongly encouraged to audit all their supported SQL statements for security implications. For example, LOAD DATA INFILE is likely to pose security risks and there is little reason to support it.

In general, it is recommended that user agents not support features that control how databases are stored on disk. For example, there is little reason to allow Web authors to control the character encoding used in the disk representation of the data, as all data in ECMAScript is implicitly UTF-16.

4.11.8.2. SQL injection

Authors are strongly recommended to make use of the ? placeholder feature of the executeSql() method, and to never construct SQL statements on the fly.