...
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:
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:
changeVersion() method.
Database object's
expected version to the value of the second argument to the changeVersion() method.
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.)
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.)
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]
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.
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.
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.)
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.
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.
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.
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.
Open a new SQL transaction to the database, and create a SQLTransaction object that represents
that transaction.
If an error occured in the opening of the transaction, jump to the last step.
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.)
Invoke the transaction callback with the
aforementioned SQLTransaction
object as its only argument.
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.
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.
If the statement is marked as bogus, jump to the "in case of error" steps below.
Execute the statement in the context of the transaction. [SQL]
If the statement failed, jump to the "in case of error" steps below.
Create a SQLResultSet
object that represents the result of the statement.
Invoke the statement's result set callback with
the SQLTransaction object
as its first argument and the new SQLResultSet object as its second
argument.
If the callback was invoked and raised an exception, jump to the last step in the overall steps.
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:
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.
If the error callback returns false, then move on to the next statement, if any, or onto the next overall step otherwise.
Otherwise, the error callback did not return false, or there was no error callback. Jump to the last step in the overall steps.
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.)
Commit the transaction.
If an error occured in the committing of the transaction, jump to the last step.
End these steps. The next step is only used when something goes wrong.
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.
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.
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.
Authors are strongly recommended to make use of the ? placeholder feature of the executeSql() method, and to never construct
SQL statements on the fly.