W3C

Web SQL 数据库

W3C 工作组备忘录 2010年11月18日

本版本
https://w3org.cn/TR/2010/NOTE-webdatabase-20101118/
最新发布版本
https://w3org.cn/TR/webdatabase/
最新编辑草案
http://dev.w3.org/html5/webdatabase/
历史版本
https://w3org.cn/TR/2009/WD-webdatabase-20091222/
https://w3org.cn/TR/2009/WD-webdatabase-20091029/
https://w3org.cn/TR/2009/WD-webstorage-20090423/
编辑
Ian Hickson, Google 公司

摘要

本规范定义了一种用于在数据库中存储数据的 API,这些数据库可以使用 SQL 的变体进行查询。

本文档状态

请注意。本规范已不再处于活跃维护状态,Web 应用工作组(Web Applications Working Group)无意对其进行进一步维护。

本节描述本文件在发布时的状态。其他文件可能会取代本文件。W3C 当前的发布列表以及本技术报告最近期的正式发布版本,可以在 W3C 技术报告索引(https://w3org.cn/TR/)中找到。

本文档是 2010 年 11 月 18 日发布的 Web SQL 数据库工作组备忘录。作为工作组备忘录发布并不意味着得到 W3C 成员的认可。这是一份草案文件,可能随时会被其他文件更新、替换或废弃。将其作为进展中的工作之外的引用是不恰当的。W3C Web 应用工作组是负责本文件的 W3C 工作组。

本文档曾处于 W3C 推荐标准轨道上,但规范工作已经停止。该规范陷入了僵局:所有感兴趣的实现者都使用了相同的 SQL 后端 (Sqlite),但我们需要多个独立的实现来推进标准化路径。

Web 应用工作组将继续致力于另外两项与存储相关的规范:Web StorageIndexed Database API

实现者应当意识到本规范尚不稳定。不参与讨论的实现者很可能会发现规范在他们不知情的情况下发生了不兼容的改变。有兴趣实现本规范的供应商应该加入上述邮件列表并参与讨论。

如果您希望针对本文档提出意见,请发送至 public-webapps@w3.org (订阅, 归档)whatwg@whatwg.org (订阅, 归档),或使用 我们的公共 bug 数据库 提交。欢迎所有反馈。

本规范编辑草案的最新稳定版本始终可在 W3C CVS 服务器上找到。本文档的变更追踪位于以下位置

本规范是根据 HTML5 规范源文件中相应部分自动生成的,托管在 WHATWG Subversion 仓库中。HTML5 所有部分的详细变更历史(包括构成此规范的部分)可以在以下位置找到

本文件由依据 2004 年 2 月 5 日 W3C 专利政策 运作的工作组制作。W3C 维护一份 公开专利披露列表,列出与工作组交付成果相关的所有专利披露;该页面还包含专利披露的说明。若个人实际了解某项专利并认为其中包含 关键权利要求,须按照 W3C 专利政策第 6 节的要求进行披露。

目录

  1. 1 简介
  2. 2 合规性要求
    1. 2.1 依赖项
  3. 3 术语
  4. 4 API
    1. 4.1 数据库
    2. 4.2 解析和处理 SQL 语句
    3. 4.3 异步数据库 API
      1. 4.3.1 执行 SQL 语句
      2. 4.3.2 处理模型
    4. 4.4 同步数据库 API
      1. 4.4.1 执行 SQL 语句
    5. 4.5 数据库查询结果
    6. 4.6 错误和异常
  5. 5 Web SQL
  6. 6 磁盘空间
  7. 7 隐私
    1. 7.1 用户追踪
    2. 7.2 数据的敏感性
  8. 8 安全
    1. 8.1 DNS 欺骗攻击
    2. 8.2 跨目录攻击
    3. 8.3 实现风险
    4. 8.4 SQL 与用户代理
    5. 8.5 SQL 注入
  9. 引用

1 简介

本节是非规范性的。

本规范引入了一套使用 SQL 操作客户端数据库的 API。

该 API 是异步的,因此作者会发现匿名函数(Lambda)在使用此 API 时非常有用。

以下是一个使用此 API 的脚本示例。首先,定义一个函数 prepareDatabase()。该函数返回一个数据库句柄,并在必要时首先创建数据库。然后,示例调用该函数来执行实际工作,在本例中为 showDocCount()

function prepareDatabase(ready, error) {
  return openDatabase('documents', '1.0', 'Offline document storage', 5*1024*1024, function (db) {
    db.changeVersion('', '1.0', function (t) {
      t.executeSql('CREATE TABLE docids (id, name)');
    }, error);
  });
}

function showDocCount(db, span) {
  db.readTransaction(function (t) {
    t.executeSql('SELECT COUNT(*) AS c FROM docids', [], function (t, r) {
      span.textContent = r.rows[0].c;
    }, function (t, e) {
      // couldn't read database
      span.textContent = '(unknown: ' + e.message + ')';
    });
  });
}

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

executeSql() 方法拥有一个参数,旨在允许变量代入语句中,而不会冒 SQL 注入漏洞的风险。

db.readTransaction(function (t) {
  t.executeSql('SELECT title, author FROM docs WHERE id=?', [id], function (t, data) {
    report(data.rows[0].title, data.rows[0].author);
  });
});

有时,可能存在任意数量的变量需要代入。即使在这种情况下,正确的解决方案也是仅使用“?”字符构建查询,然后将变量作为第二个参数传入。

function findDocs(db, resultCallback) {
  var q = "";
  for each (var i in labels)
    q += (q == "" ? "" : ", ") + "?";
  db.readTransaction(function (t) {
    t.executeSql('SELECT id FROM docs WHERE label IN (' + q + ')', labels, function (t, data) {
      resultCallback(data);
    });
  });
}

2 合规性要求

本规范中的所有图表、示例和注释均为非规范性内容,明确标记为非规范性的章节亦如此。本规范中的所有其他内容均为规范性内容。

本文档规范性部分中的关键词“MUST”(必须)、“MUST NOT”(不得)、“REQUIRED”(需要)、“SHOULD”(应当)、“SHOULD NOT”(不应当)、“RECOMMENDED”(推荐)、“MAY”(可以)和“OPTIONAL”(可选)应按照 RFC2119 中的描述进行解释。为了易读起见,这些词在本规范中并未以全大写字母显示。[RFC2119]

作为算法一部分的祈使语气要求(例如“去除任何前导空格字符”或“返回 false 并中止这些步骤”)应根据引入算法时使用的关键词(“必须”、“应该”、“可以”等)进行解释。

某些合规性要求以属性、方法或对象的形式表述。这类要求应解释为对用户代理的要求。

以算法或具体步骤表述的一致性要求可以以任何方式实现,只要最终结果等效即可。(特别说明,本规范中定义的算法旨在易于遵循,而非旨在达到高性能。)

本规范定义的唯一一致性类别是用户代理。

用户代理可能会对本来无限制的输入施加实现特定的限制,例如防止拒绝服务攻击、避免内存耗尽,或规避平台特有的限制。

当某个功能的支持被禁用时(例如作为缓解安全问题的紧急措施,或为了辅助开发,或出于性能原因),用户代理必须表现得好像完全不支持该功能,并且该功能未在本规范中提及一样。例如,如果某个功能是通过 Web IDL 接口中的属性访问的,那么该属性本身就应该从实现该接口的对象中省略——仅将属性保留在对象上但使其返回 null 或抛出异常是不够的。

2.1 依赖项

本规范依赖于其他若干基础规范。

HTML

本规范使用了 HTML 中的许多基本概念。[HTML]

WebIDL

本规范中的 IDL 块使用 WebIDL 规范的语义。[WEBIDL]

3 术语

构造 “a Foo object”(其中 Foo 实际上是一个接口)有时会被用来代替更准确的 “实现接口 Foo 的对象”。

术语“DOM”用于指代 Web 应用中脚本可用的 API 集,并不一定意味着存在 DOM Core 规范中定义的实际 Document 对象或任何其他 Node 对象。[DOMCORE]

当 IDL 属性的值被获取(例如由作者脚本)时,称之为“正在获取”(getting);当向其赋新值时,称之为“正在设置”(setting)。

术语“JavaScript”用于指代 ECMA262,而不是官方术语 ECMAScript,因为术语 JavaScript 更广为人知。[ECMA262]

4 API

4.1 数据库

每个(origin)都有一个关联的数据库集。每个数据库都有一个名称和当前版本。此 API 无法枚举或删除源可用的数据库。

每个数据库在同一时间只有一个版本;数据库不能同时以多个版本存在。引入版本是为了允许作者以增量和非破坏性的方式管理模式变更,且无需冒旧代码(例如在另一个浏览器窗口中)尝试以不正确的假设写入数据库的风险。

[Supplemental, NoInterfaceObject]
interface WindowDatabase {
  Database openDatabase(in DOMString name, in DOMString version, in DOMString displayName, in unsigned long estimatedSize, in optional DatabaseCallback creationCallback);
};
Window implements WindowDatabase;

[Supplemental, NoInterfaceObject]
interface WorkerUtilsDatabase {
  Database openDatabase(in DOMString name, in DOMString version, in DOMString displayName, in unsigned long estimatedSize, in optional DatabaseCallback creationCallback);
  DatabaseSync openDatabaseSync(in DOMString name, in DOMString version, in DOMString displayName, in unsigned long estimatedSize, in optional DatabaseCallback creationCallback);
};
WorkerUtils implements WorkerUtilsDatabase;

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

WindowWorkerUtils 接口上的 openDatabase() 方法,以及 WorkerUtils 接口上的 openDatabaseSync() 方法,接受以下参数:数据库名称、数据库版本、显示名称、预计大小(以字节为单位,存储在数据库中的数据量),以及一个可选的回调函数(若数据库尚未创建,则调用此回调)。如果提供了回调,其意图是用于调用 changeVersion();无论给定的数据库版本如何,回调调用时数据库的版本均为字符串空值。如果不提供回调,则数据库将以给定的数据库版本作为其版本创建。

当被调用时,这些方法必须运行以下步骤,除最后两个步骤外,所有步骤必须原子化地运行

  1. 如果请求违反了策略决定(例如,如果用户代理配置为不允许页面打开数据库),用户代理可以抛出 SECURITY_ERR 异常,而不是返回一个 Database 对象。

  2. 对于 Window 对象上的方法:令 origin 为调用该方法的 Window 对象所属的浏览上下文活动文档

    对于 WorkerUtils 对象上的方法:令 origin 为 worker 中脚本的

  3. 如果 origin 不是 scheme/host/port 元组,则抛出 SECURITY_ERR 异常并中止这些步骤。

  4. 如果提供的数据库版本不是空字符串,且源 origin 已存在同名数据库,但数据库版本与提供的不一致,则抛出 INVALID_STATE_ERR 异常并中止这些步骤。

  5. 如果源 origin 不存在同名数据库,则创建数据库并将 created 设为 true。如果方法传递了回调函数,则将新数据库的版本设为空字符串。否则,将新数据库的版本设为给定的数据库版本。

    否则,如果已存在同名数据库,则将 created 设为 false。

  6. 对于 openDatabase() 方法:令 result 为新构建的 Database 对象,表示来自源 origin 的给定数据库名称的数据库。

    对于 openDatabaseSync() 方法:令 result 为新构建的 DatabaseSync 对象,表示来自源 origin 的给定数据库名称的数据库。

  7. 如果 created 为 false 或者未向方法传递回调函数,则跳过此步骤。否则

    对于 openDatabase() 方法:排入一个任务result 作为其唯一参数调用回调函数。

    对于 openDatabaseSync() 方法:以 result 作为其唯一参数调用回调函数。如果回调抛出异常,则重新抛出该异常并中止这些步骤。

  8. 返回 result

所有字符串(包括空字符串)均为有效的数据库名称。数据库名称必须以区分大小写的方式进行比较。

实现者可以通过将数据库名称映射(例如使用哈希算法)到支持的名称集,来支持仅支持数据库名称字符串子集的环境。

打开数据库时使用的版本即为该 DatabaseDatabaseSync 对象的 预期版本。它可以是空字符串,在这种情况下没有预期的版本——任何版本都可以。

用户代理应利用显示名称和估计的数据库大小来优化用户体验。例如,用户代理可以使用估计的大小向用户建议初始配额。这允许预知将使用数百兆字节的网站提前声明,而不必让用户代理每五兆字节就提示用户授权增加配额。

4.2 解析和处理 SQL 语句

当用户代理要预处理 SQL 语句 sqlStatement 和参数数组 arguments 时,它必须运行以下步骤

  1. sqlStatement 解析为 SQL 语句,区别在于 U+003F 问号字符 (?) 可用于语句中代替 SQL 字面量。[SQL]

  2. 将每个 ? 占位符绑定到 arguments 数组中位置相同的参数值。(因此第一个 ? 占位符绑定到 arguments 数组中的第一个值,依此类推,第 n? 占位符绑定到 arguments 数组中的第 n 个值。)

    绑定 ? 占位符是在字面量级别完成的,而不是通过字符串拼接,因此这提供了一种动态插入参数到语句中的方法,而不会产生 SQL 注入攻击的风险。

    结果即为 该语句

  3. 如果创建 SQLTransactionSQLTransactionSync 对象的 Database 对象具有一个预期版本,该版本既不是空字符串,也不是数据库的实际版本,则将 该语句 标记为伪造。(错误代码 2。)

  4. 否则,如果 sqlStatement 的语法无效(使用 ? 字符代替字面量的情况除外),或者语句使用了不受支持的功能(例如出于安全原因),或者 arguments 数组中的项数与语句中的 ? 占位符数量不相等,或者由于其他原因无法解析语句,则将 该语句 标记为伪造。(错误代码 5。)

    用户代理必须将使用 BEGINCOMMITROLLBACK SQL 功能的语句视为不受支持(因此会将它们标记为伪造),以免这些语句干扰数据库 API 本身管理的显式事务。

  5. 否则,如果用于创建 SQLTransactionSQLTransactionSync 对象的模式是只读的,但语句的主要动词可以修改数据库,则将该语句标记为伪造。(错误代码 5。)

    此处仅考虑语句的主要动词(例如 UPDATESELECTDROP)。因此,像“UPDATE test SET id=0 WHERE 0=1”这样的语句,为了本步骤的目的,将被视为可能修改数据库,即使它实际上永远不会产生任何副作用。

  6. 返回 该语句

用户代理的表现必须仿佛数据库托管在一个完全空的环境中,没有任何资源。例如,读取或写入文件系统的尝试将失败。

本规范的未来版本可能会更详细地定义所需的准确 SQL 子集。

4.3 异步数据库 API

interface Database {
  void transaction(in SQLTransactionCallback callback, in optional SQLTransactionErrorCallback errorCallback, in optional SQLVoidCallback successCallback);
  void readTransaction(in SQLTransactionCallback callback, in optional SQLTransactionErrorCallback errorCallback, in optional SQLVoidCallback successCallback);

  readonly attribute DOMString version;
  void changeVersion(in DOMString oldVersion, in DOMString newVersion, in optional SQLTransactionCallback callback, in optional SQLTransactionErrorCallback errorCallback, in optional SQLVoidCallback successCallback);
};

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

[Callback=FunctionOnly, NoInterfaceObject]
interface SQLTransactionCallback {
  void handleEvent(in SQLTransaction transaction);
};

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

transaction()readTransaction() 方法接受一到三个参数。调用时,这些方法必须立即返回,然后异步运行事务步骤,其中事务回调为第一个参数,错误回调为第二个参数(如有),成功回调为第三个参数(如有),且无预处理操作后处理操作

对于 transaction() 方法,模式必须是读/写。对于 readTransaction() 方法,模式必须是只读。

获取时,version 属性必须返回数据库的当前版本(与 Database 对象的预期版本相对)。

changeVersion() 方法允许脚本在执行模式更新的同时,原子地验证版本号并对其进行更改。当调用该方法时,它必须立即返回,然后异步运行事务步骤,其中事务回调为第三个参数,错误回调为第四个参数,成功回调为第五个参数,预处理操作如下

  1. 检查 changeVersion() 方法的第一个参数值是否与数据库的实际版本完全匹配。如果不匹配,则预处理操作失败。

...后处理操作如下

  1. 将数据库的实际版本更改为 changeVersion() 方法的第二个参数值。
  2. Database 对象的预期版本更改为 changeVersion() 方法的第二个参数值。

...且模式为读/写。

如果省略了任何可选参数,则必须将它们视为 null。

4.3.1 执行 SQL 语句

transaction()readTransaction()changeVersion() 方法使用 SQLTransaction 对象调用回调。

typedef sequence<any> ObjectArray;

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

[Callback=FunctionOnly, NoInterfaceObject]
interface SQLStatementCallback {
  void handleEvent(in SQLTransaction transaction, in SQLResultSet resultSet);
};

[Callback=FunctionOnly, NoInterfaceObject]
interface SQLStatementErrorCallback {
  boolean handleEvent(in SQLTransaction transaction, in SQLError error);
};

当调用 executeSql(sqlStatement, arguments, callback, errorCallback) 方法时,用户代理必须运行以下算法。(该算法相对简单,因为它实际上不执行任何 SQL——大部分工作实际上是作为事务步骤的一部分完成的。)

  1. 如果该方法不是在执行 SQLTransactionCallbackSQLStatementCallbackSQLStatementErrorCallback 期间调用的,则抛出 INVALID_STATE_ERR 异常。(因此,从 SQLTransactionErrorCallback 内部调用会抛出异常。SQLTransactionErrorCallback 处理程序仅在事务失败后调用,且不能向已失败的事务中添加 SQL 语句。)

  2. 预处理作为方法第一个参数给出的 SQL 语句(sqlStatement),使用方法的第二个参数作为 arguments 数组,以获得 该语句

    如果第二个参数被省略或为 null,则将 arguments 数组视为为空。

  3. 该语句排入事务中,连同第三个参数(如有,作为语句的结果集回调)和第四个参数(如有,作为错误回调)。

4.3.2 处理模型

事务步骤如下。这些步骤必须异步运行。这些步骤在调用时带有事务回调、可选的错误回调、可选的成功回调、可选的预处理操作、可选的后处理操作,以及要么是读/写要么是只读的模式

  1. 打开一个新的数据库 SQL 事务,并创建一个表示该事务的 SQLTransaction 对象。如果模式是读/写,则该事务必须拥有整个数据库的独占写锁。如果模式是只读,则该事务必须拥有整个数据库的共享读锁。用户代理应等待适当的锁可用。

  2. 如果打开事务时发生错误(例如,用户代理在适当延迟后未能获得适当的锁),则跳转到最后一步。

  3. 如果为此事务步骤实例定义了预处理操作,则运行该操作。如果它失败,则跳转到最后一步。(这本质上是 changeVersion() 方法的一个钩子。)

  4. 如果事务回调不为 null,则排入一个任务以刚才提到的 SQLTransaction 对象作为其唯一参数调用事务回调,并等待该任务运行。

  5. 如果回调抛出了异常,则跳转到最后一步。

  6. 当事务中还有排队的语句时,对事务中每个排队的语句执行以下步骤,先执行最旧的。每个语句都有一个语句体、可选的结果集回调和可选的错误回调。

    1. 如果语句被标记为伪造,则跳转到下方的“发生错误时”步骤。

    2. 在事务上下文中执行该语句。[SQL]

    3. 如果语句失败,则跳转到下方的“发生错误时”步骤。

    4. 创建一个表示语句结果的 SQLResultSet 对象。

    5. 如果语句有一个不为 null 的结果集回调,则排入一个任务SQLTransaction 对象作为其第一个参数,以新的 SQLResultSet 对象作为其第二个参数调用它,并等待该任务运行。

    6. 如果回调被调用并抛出了异常,则跳转到总步骤的最后一步。

    7. 移至下一条语句(如有),否则移至下一步总步骤。

    发生错误时(更具体地说,如果上述子步骤指出要跳转到“发生错误时”步骤),运行以下子步骤

    1. 如果语句有关联且不为 null 的错误回调,则排入一个任务SQLTransaction 对象和表示导致运行这些子步骤的错误的新构建的 SQLError 对象作为两个参数调用该错误回调,并等待任务运行。

    2. 如果错误回调返回 false,则移至下一条语句(如有),否则移至下一步总步骤。

    3. 否则,如果错误回调没有返回 false,或者没有错误回调,则跳转到总步骤的最后一步。

  7. 如果为此事务步骤实例定义了后处理操作,则:作为一项原子操作,提交事务,如果成功,则运行后处理操作。如果提交失败,则跳转到最后一步。(这本质上是 changeVersion() 方法的一个钩子。)

    否则:提交事务。如果提交事务时发生错误,则跳转到最后一步。

  8. 排入一个任务调用成功回调(如果它不为 null)。

  9. 结束这些步骤。下一步仅在出现问题时使用。

  10. 排入一个任务调用事务的错误回调(如果它不为 null),并带有一个新构建的 SQLError 对象,该对象表示此事务中发生的最后一个错误。回滚事务。事务中任何仍处于挂起状态的语句将被丢弃。

这些任务任务源数据库访问任务源

4.4 同步数据库 API

interface DatabaseSync {
  void transaction(in SQLTransactionSyncCallback callback);
  void readTransaction(in SQLTransactionSyncCallback callback);

  readonly attribute DOMString version;
  void changeVersion(in DOMString oldVersion, in DOMString newVersion, in optional SQLTransactionSyncCallback callback);
};

[Callback=FunctionOnly, NoInterfaceObject]
interface SQLTransactionSyncCallback {
  void handleEvent(in SQLTransactionSync transaction);
};

transaction()readTransaction() 方法必须运行以下步骤

  1. 如果该方法是 transaction() 方法,则创建一个 SQLTransactionSync 对象以进行读/写事务。否则,创建一个 SQLTransactionSync 对象以进行只读事务。在任何情况下,如果抛出异常,则重新抛出它并中止这些步骤。否则,令 transaction 为新创建的 SQLTransactionSync 对象。

  2. 如果第一个参数为 null,则回滚事务,抛出 SQLException 异常,并中止这些步骤。(错误代码 0。)

  3. 调用第一个参数给出的回调,将 transaction 对象作为其唯一参数传递给它。

  4. SQLTransactionSync 对象标记为 过时(stale)。

  5. 如果回调被异常终止,则回滚事务,重新抛出该异常,并中止这些步骤。

  6. 提交事务。

  7. 如果提交事务时发生错误,则回滚事务,抛出 SQLException 异常,并中止这些步骤。

获取时,version 属性必须返回数据库的当前版本(与 DatabaseSync 对象的预期版本相对)。

changeVersion() 方法允许脚本在执行模式更新的同时,原子地验证版本号并对其进行更改。当调用该方法时,它必须运行以下步骤

  1. 创建一个 SQLTransactionSync 对象以进行读/写事务。如果抛出异常,则重新抛出它并中止这些步骤。否则,令 transaction 为新创建的 SQLTransactionSync 对象。

  2. 检查 changeVersion() 方法的第一个参数值是否与数据库的实际版本完全匹配。如果不匹配,则抛出 SQLException 异常并中止这些步骤。(错误代码 2。)

  3. 如果第三个参数不为 null,则调用第三个参数给出的回调,将 transaction 对象作为其唯一参数传递给它。

  4. SQLTransactionSync 对象标记为 过时(stale)。

  5. 如果回调被异常终止,则回滚事务,重新抛出该异常,并中止这些步骤。

  6. 提交事务。

  7. 如果提交事务时发生错误,则回滚事务,抛出 SQLException 异常,并中止这些步骤。

  8. 将数据库的实际版本更改为 changeVersion() 方法的第二个参数值。
  9. Database 对象的预期版本更改为 changeVersion() 方法的第二个参数值。

当用户代理要创建一个 SQLTransactionSync 对象以进行读/写或只读事务时,它必须运行以下步骤

  1. 打开一个新的数据库 SQL 事务,并创建一个表示该事务的 SQLTransactionSync 对象。如果模式是读/写,则该事务必须拥有整个数据库的独占写锁。如果模式是只读,则该事务必须拥有整个数据库的共享读锁。用户代理应等待适当的锁可用。

  2. 如果打开事务时发生错误(例如,用户代理在适当延迟后未能获得适当的锁),则抛出 SQLException 异常并中止这些步骤。

  3. 返回新创建的 SQLTransactionSync 对象。

4.4.1 执行 SQL 语句

transaction()readTransaction()changeVersion() 方法调用传递了 SQLTransactionSync 对象的回调。

// typedef sequence<any> ObjectArray;

interface SQLTransactionSync {
  SQLResultSet executeSql(in DOMString sqlStatement, in optional ObjectArray arguments);
};

SQLTransactionSync 对象最初是 新鲜的(fresh),但一旦提交或回滚,它将被标记为 过时(stale)。

当调用 executeSql(sqlStatement, arguments) 方法时,用户代理必须运行以下算法

  1. 如果 SQLTransactionSync 对象是 过时的,则抛出 INVALID_STATE_ERR 异常。

  2. 预处理作为方法第一个参数给出的 SQL 语句(sqlStatement),使用方法的第二个参数作为 arguments 数组,以获得 该语句

    如果第二个参数被省略或为 null,则将 arguments 数组视为为空。

  3. 如果语句被标记为伪造,则抛出 SQLException 异常。

  4. 在事务上下文中执行该语句。[SQL]

  5. 如果语句失败,则抛出 SQLException 异常。

  6. 创建一个表示语句结果的 SQLResultSet 对象。

  7. 返回新创建的 SQLResultSet 对象。

4.5 数据库查询结果

executeSql() 方法使用 SQLResultSet 对象作为参数来调用其回调。

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

如果语句插入了一行,insertId 属性必须返回 SQLResultSet 对象的 SQL 语句插入数据库中的行的行 ID。如果语句插入了多行,则必须返回最后一行对应的 ID。如果语句没有插入行,则该属性必须抛出 INVALID_ACCESS_ERR 异常。

rowsAffected 属性必须返回受 SQL 语句更改的行数。如果语句未影响任何行,则该属性必须返回零。对于“SELECT”语句,此属性返回零(查询数据库不会影响任何行)。

rows 属性必须返回一个表示所返回行的 SQLResultSetRowList,顺序由数据库决定。每次必须返回同一个对象。如果没有返回任何行,则该对象为空(其 length 将为零)。

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

对于异步 API,为了获得更好的响应速度,建议实现者在构造 SQLResultSetRowList 对象时(在调用结果集回调之前)预取所有数据,而不是按需获取。对于同步 API,则建议采用按需懒评估的实现策略,以获得更好的性能。

SQLResultSetRowList 对象有一个 length 属性,必须返回它所表示的行数(数据库返回的行数)。这就是 length

获取 length 可能开销较大,因此建议作者尽可能避免使用它(或枚举该对象,因为这会隐式使用它)。

对象的支持属性索引是范围从零到 length-1 之间的数字,除非 length 为零,在这种情况下,没有支持属性索引

item(index) 属性必须返回给定索引 index 处的行。如果没有这样的行,该方法必须返回 null。

每一行必须由原生的有序字典数据类型表示。在 JavaScript 绑定中,这必须是 Object。每个行对象必须为每列拥有一个属性(或字典条目),这些属性按数据库返回这些列的顺序枚举。每个属性必须具有列的名称和单元格的值,正如它们由数据库返回的那样。

4.6 错误和异常

异步数据库 API 中的错误通过使用 SQLError 对象作为其参数之一的回调来报告。

interface SQLError {
  const unsigned short UNKNOWN_ERR = 0;
  const unsigned short DATABASE_ERR = 1;
  const unsigned short VERSION_ERR = 2;
  const unsigned short TOO_LARGE_ERR = 3;
  const unsigned short QUOTA_ERR = 4;
  const unsigned short SYNTAX_ERR = 5;
  const unsigned short CONSTRAINT_ERR = 6;
  const unsigned short TIMEOUT_ERR = 7;
  readonly attribute unsigned short code;
  readonly attribute DOMString message;
};

code IDL 属性必须返回下表中最为合适的代码。

message IDL 属性必须返回描述所遇错误的错误消息。消息应根据用户的语言进行本地化。


同步数据库 API 中的错误使用 SQLException 异常报告。

exception SQLException {
  const unsigned short UNKNOWN_ERR = 0;
  const unsigned short DATABASE_ERR = 1;
  const unsigned short VERSION_ERR = 2;
  const unsigned short TOO_LARGE_ERR = 3;
  const unsigned short QUOTA_ERR = 4;
  const unsigned short SYNTAX_ERR = 5;
  const unsigned short CONSTRAINT_ERR = 6;
  const unsigned short TIMEOUT_ERR = 7;
  unsigned short code;
  DOMString message;
};

code IDL 属性必须返回下表中最为合适的代码。

message IDL 属性必须返回描述所遇错误的错误消息。消息应根据用户的语言进行本地化。


错误代码如下

常量代码情况
UNKNOWN_ERR 0 事务失败的原因与数据库本身无关,且未被任何其他错误代码覆盖。
DATABASE_ERR 1 语句失败的原因与数据库有关,且未被任何其他错误代码覆盖。
VERSION_ERR 2 操作失败,因为实际的数据库版本与应有的不符。例如,语句发现实际的数据库版本不再匹配 DatabaseDatabaseSync 对象的预期版本,或者向 Database.changeVersion()DatabaseSync.changeVersion() 方法传递的版本与实际数据库版本不匹配。
TOO_LARGE_ERR 3 语句失败,因为数据库返回的数据太大。SQL“LIMIT”修饰符可能有助于减小结果集的大小。
QUOTA_ERR 4 语句失败,因为剩余存储空间不足,或者达到了存储配额,且用户拒绝给予数据库更多空间。
SYNTAX_ERR 5 语句失败,因为存在语法错误,或者参数数量与语句中的 ? 占位符数量不匹配,或者语句尝试使用不允许的语句(例如 BEGINCOMMITROLLBACK),或者语句尝试使用可以修改数据库的动词,但事务是只读的。
CONSTRAINT_ERR 6 由于约束失败,INSERTUPDATEREPLACE 语句失败。例如,因为正在插入一行,而主键列给定的值与现有行的值重复。
TIMEOUT_ERR 7 在合理的时间内无法获得事务锁。

5 Web SQL

用户代理必须实现 Sqlite 3.6.19 支持的 SQL 方言。

在将绑定参数转换为 SQL 数据类型时,必须应用 JavaScript ToPrimitive 抽象操作以获得要处理的原始值。[ECMA262]

6 磁盘空间

用户代理应限制允许数据库使用的总空间量。

用户代理应防范站点在其他关联站点的源下存储数据,例如在 a1.example.com、a2.example.com、a3.example.com 等处存储达到上限,从而规避主要 example.com 的存储限制。

当达到配额时,用户代理可以提示用户,允许用户授予站点更多空间。例如,这使得站点能够将许多用户创建的文档存储在用户的计算机上。

用户代理应允许用户查看每个域正在使用多少空间。

建议每个设置一个大致为五兆字节的任意限制。欢迎提供实现反馈,并将用于在未来更新此建议。

7 隐私

7.1 用户追踪

第三方广告商(或任何能够将内容分发到多个站点的实体)可以使用存储在其客户端数据库中的唯一标识符在多个会话中追踪用户,构建用户的兴趣档案,从而实现高度针对性的广告投放。结合了解用户真实身份的站点(例如需要认证凭据的电子商务站点),这可能使压迫性团体能够比在纯匿名 Web 使用环境中更准确地针对个人进行定位。

有许多技术可以用来降低用户跟踪的风险:

拦截第三方存储

用户代理可以限制脚本对数据库对象的访问,仅允许源自顶层文档的浏览上下文域名的脚本进行访问,例如禁止运行在 iframe 中的其他域名的页面访问该 API。

存储数据的过期

如果用户进行了相应配置,用户代理可以在一段时间后自动删除已存储的数据。

这可以限制站点追踪用户的能力,因为站点随后只能在用户与站点本身进行身份验证时(例如通过购买或登录服务)才能在多个会话中追踪用户。

然而,这也降低了 API 作为长期存储机制的有用性。如果用户没有完全理解数据过期带来的影响,它还可能使用户的数据面临风险。

将持久化存储视为 Cookie

如果用户试图通过清除 Cookie 而不清除相关数据库中存储的数据来保护其隐私,站点可以通过将这两个功能互为冗余备份来挫败这些尝试。用户代理呈现清除这些数据的界面方式应有助于用户理解这种可能性,并使他们能够同时删除所有持久存储功能中的数据。[COOKIES]

数据库访问的站点特定白名单

用户代理可能要求用户在站点使用该功能之前,先授权其访问数据库。

已存储数据的源追踪

用户代理可以记录包含第三方源内容的、导致数据存储的站点

如果随后使用此信息呈现持久存储中当前数据的视图,将允许用户就修剪持久存储的哪些部分做出明智的决定。结合黑名单(“删除此数据并防止此域再次存储数据”),用户可以将持久存储的使用限制在他信任的站点上。

共享黑名单

用户代理可以允许用户共享其持久存储域黑名单。

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

虽然这些建议防止了该 API 被用于琐碎的用户跟踪,但它们并没有完全阻止这种情况。在单一域名内,站点可以继续在会话期间跟踪用户,然后可以将所有这些信息与站点获取的任何识别信息(姓名、信用卡号、地址)一起传递给第三方。如果第三方与多个站点合作以获取此类信息,仍然可以创建用户档案。

然而,用户追踪在某种程度上即使在用户代理完全不合作的情况下也是可能的,例如通过在 URL 中使用会话标识符,这是一种已经普遍用于无害目的,但很容易被重新利用以用于用户追踪(甚至是追溯性地)的技术。此信息随后可以与其他站点共享,利用访问者的 IP 地址和其他特定于用户的数据(例如用户代理标头和配置设置)将单独的会话组合成连贯的用户档案。

7.2 数据的敏感性

用户代理应将持久存储的数据视为潜在的敏感信息;电子邮件、日历预约、健康记录或其他机密文档很有可能存储在此机制中。

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

8 安全

8.1 DNS 欺骗攻击

由于 DNS 欺骗攻击的潜力,无法保证声称在特定域名的主机确实来自该域名。为了缓解这种情况,页面可以使用 TLS。使用 TLS 的页面可以确信,只有同样使用 TLS 且拥有标识其为来自相同域名证书的页面才能访问其数据库。

8.2 跨目录攻击

共享同一主机名的不同作者,例如在 geocities.com 上托管内容的用户,都共享同一套数据库。没有功能可以限制按路径名进行的访问。因此建议共享主机上的作者避免使用这些功能,因为其他作者读取数据并覆盖它将是轻而易举的。

即使提供了路径限制功能,通常的 DOM 脚本安全模型也会使得绕过此保护并从任何路径访问数据变得非常简单。

8.3 实现风险

实现这些持久化存储功能时,两个主要的风险是:允许恶意站点从其他域名读取信息,以及允许恶意站点写入稍后可被其他域名读取的信息。

允许第三方站点读取不应该从其域中读取的数据会导致信息泄露。例如,一个域上的用户购物愿望清单可能会被另一个域用于定向广告;或者一个文字处理站点存储的用户工作中的机密文档可能会被竞争公司的站点审查。

允许第三方站点向其他域的持久存储写入数据可能会导致信息欺骗,这同样危险。例如,敌意站点可能会将项目添加到用户的愿望清单中;或者敌意站点可以将用户的会话标识符设置为一个已知 ID,敌意站点随后可以使用该 ID 来追踪用户在受害站点上的操作。

因此,严格遵循本规范中描述的模型对于用户安全非常重要。

8.4 SQL 与用户代理

强烈建议用户代理实现者对其所有支持的 SQL 语句进行安全审计,以检查是否存在安全影响。例如,LOAD DATA INFILE 很可能构成安全风险,并且几乎没有理由支持它。

通常,建议用户代理不要支持控制数据库如何存储在磁盘上的功能。例如,几乎没有理由允许 Web 作者控制磁盘数据表示中使用的字符编码,因为 JavaScript 中的所有数据隐式地都是 UTF-16。

8.5 SQL 注入

强烈建议作者利用 executeSql() 方法的 ? 占位符功能,且永远不要即时构建 SQL 语句。

引用

所有参考文献均为规范性参考文献,除非标记为“非规范性”。

[COOKIES]
HTTP 状态管理机制,A. Barth. IETF。
[DOMCORE]
文档对象模型 (DOM) Level 3 核心规范,A. Le Hors, P. Le Hegaret, L. Wood, G. Nicol, J. Robie, M. Champion, S. Byrnes. W3C。
[ECMA262]
ECMAScript 语言规范。ECMA。
[HTML]
HTML,I. Hickson. WHATWG。
[RFC2119]
用于 RFC 以指示需求级别的关键词,S. Bradner. IETF。
[SQL]
精确的方言尚未指定。
[WEBIDL]
Web IDL,C. McCormack. W3C。