C 语言风格 API

另请参阅:WASM 工具注意事项

C 语言风格 API 实际上有两种风格

在这两种情况下,规范的 C API 文档都适用,但 JavaScript 中某些 API 的语义有所扩展,如本文档中所述。

预计大多数 JS 客户端不会想要或需要处理大多数这些 API,因为这样做可能会导致某些常用功能需要仔细注意传递某些数据在 JS 和 WASM 之间(特别是C API 中的指针到指针“输出参数”)。

相反,预计客户端将主要使用oo1Worker 1 API,可能会回退到 C 语言风格 API 以执行诸如 sqlite3_libversion() 之类的无害例程(请注意,C 语言风格 API 无法用于通过 Worker 消息“远程控制”数据库连接的客户端)。

C 语言风格 API 在大多数情况下都像其 C 对应版本一样工作。大多数 API 的行为与其原生 C 变体相同,并且它们的 JS/WASM 绑定没有明确记录,因为C 文档已经满足了这个目的。但是,也存在例外情况,其中 JS 样式和 C 样式参数处理的差异需要略微不同的语义,这需要明确的文档记录。此类情况在下面有记录。

符号常量 (SQLITE_...)

sqlite3 API 大量使用符号常量来表示函数结果代码和标志样式函数参数。所有这些可能由 WASM 绑定的 C 函数所需的符号都在 sqlite3.capi 命名空间对象中定义。例如,sqlite3.capi.SQLITE_ERROR。C 端常量的完整列表位于

www:/c3ref/constlist.html

请注意,只有与 WASM 绑定 API 相关的常量才对 JS 可用。

JS API 使用以下内容扩展了该列表:

对于结果代码常量,函数 sqlite3_js_rc_str() 接受一个整数参数并返回其对应符号名称的 JS 字符串形式,如果未找到匹配项则返回 undefined

即使 SQLITE_... 常量值极不可能发生变化,但由于向后兼容性约束,JS 代码应避免在代码中直接使用任何此类数值。应始终优先使用符号名称。

JavaScript 异常

除了一个例外(实际上),C 级别 API 从不抛出异常,但一些更高级别的 C 语言风格 API 确实抛出异常,并且面向对象的 API 专门使用异常来报告错误。唯一的例外是 sqlite3.wasm.alloc(),如果它无法分配内存(通常可以认为是致命错误),则会抛出 sqlite3.WasmAllocError 异常。

请注意,“C 级别”和“C 级别相邻”之间存在区别。C 级别 API 特指直接从 WASM 导出到 JavaScript 的公共 sqlite3 API 函数。一些旨在与 C 级别 API 配合使用的实用程序代码确实会抛出异常,但它以一种方式这样做,当正确使用时,绝不会允许异常传播回 JS/WASM 边界,因为这样做会对 C 级别代码造成灾难性后果。

SQLite3Error

一个专门用于报告数据库级别错误并使客户端能够明确识别此类异常的 Error 子类。它的构造函数根据其参数的行为不同

注意:内存分配器

在 C 中,始终重要的是以与分配方式相对应的方式释放动态分配的内存。

举例来说,以下两者都是合法的

void * m = malloc(100);
free(m);
m = sqlite3_malloc(100);
sqlite3_free(m);

但是两者都不合法

void * m = malloc(100);
sqlite3_free(m); // NO!
m = sqlite3_malloc(100);
free(m); // NO!

根据 sqlite3_malloc()sqlite3_free() 的定义方式,这可能因 sqlite3 的不同版本而异,它们可能或可能不是 malloc()free() 的简单代理。如果是,则第二个示例将起作用……直到前一对的定义发生变化,在这种情况下,第二个示例会导致未定义的行为。

根据 sqlite3.js 的构建方式sqlite3_malloc() 可能是模块的默认分配器。该项目的规范构建从 2022 年 11 月 30 日起都使用 sqlite3 的分配器作为模块范围的默认分配器,并在所有未来版本中保证这样做。

但是,为不同 WASM 环境创建自定义构建的客户端可能会更改这一点,在这种情况下,为这种构建编写的任何 JavaScript 代码都需要谨慎使用动态分配的内存,以确保永远不会混合使用不同的内存管理 API。也就是说,对 malloc()free()realloc() 的调用绝不能sqlite3_malloc()sqlite3_free()sqlite3_realloc() 混合使用。混合使用会导致未定义的结果。

有关此区别至关重要的 sqlite3 API 的实际示例,请参阅sqlite3_deserialize()

C 语言风格 API 中的参数和结果类型转换

另请参阅:api-wasm.md#wasm-type-conversion

许多 sqlite3.capi.sqlite3_...() 例程可能会对其参数或返回值执行某种 JS 到 WASM 的转换。本节介绍这些内容。

64 位整数 (BigInt)

简而言之,当使用 BigInt 支持构建时,JS API 透明地(或大部分如此)将 C 级别 int64 值视为 JS BigInt 值。在某些情况下,客户端需要知道他们正在使用 BigInt 值,因为并非所有数值运算都支持 Number 和 BigInt 值的混合,例如按位运算。

有关详细信息,请参阅构建说明

字符串转换

位于 sqlite3.capi 命名空间中的 C API 的 JS 绑定被配置为自动在 WASM/JS 之间转换字符串参数。因此

const v = capi.sqlite3_vfs_find("foo");

将在调用 WASM 绑定函数之前将 "foo" 转换为 C 语言风格字符串,并在返回之前释放关联的内存。类似地,C 语言风格字符串的结果值将转换为 JS

console.log(capi.sqlite3_libversion());

在这些 API 接受 C 字符串参数的地方,可以传递JS 字符串或 WASM 端字符串指针,请注意,此类用法必须以 NUL 结尾,否则结果未定义。JS/WASM 兼容性粘合剂将把字符串参数的数字参数视为 WASM 指针。以这种方式使用时,调用者有责任确保他们传递了有效的指针。与 C 编译器(在传递指针时至少具有一定程度的类型安全性)不同,WASM 唯一对指针的理解等同于 C 中的 void*。也就是说,指针完全缺乏任何类型信息,很容易“搬起石头砸自己的脚”,正如俗话所说。

仅限 UTF-8!

参数和结果中的 C 语言风格字符串始终假定为 UTF-8 编码,如果任何 WASM 绑定 sqlite3 API 传递或返回编码不同的字符串,则结果未定义。这与 JS 字符串不同,JS 字符串使用 JS 本地的 16 位编码(即UTF-16 或 UCS-2另请参阅))。

尽管 sqlite3 C API 有用于处理 UTF-16 的例程,但此 API 仅针对 Web,而 UTF-8 是 Web 的编码。目前没有计划通过 JS 接口公开 UTF-16 API。

“灵活字符串”

某些 WASM 绑定 API(在明确说明的情况下)具有其他字符串类型参数转换,俗称为“灵活字符串”。此类字符串参数可以采用以下任何形式

此支持通常保留给期望 SQL 字符串的参数,因为此类字符串通常很大并且经常来自外部来源,例如从本地文件加载的字节数组、通过XHR 请求或使用fetch()。使用文件名字符串以及类似的“小”字符串的函数不使用此功能。

函数指针

许多 sqlite3 API 将函数指针作为参数。尽管可以从 JS 代码创建和管理此类指针,但这通常很繁琐,因此大多数此类绑定将自动将 Function 类型的值转换为函数指针以用于此类 API(例如,sqlite3_exec())。

此类转换很方便,但也存在一些注意事项。

需要明确的是:所有此类泄漏都不反映 sqlite3 核心库的任何缺陷,而是从 JS 原生类型到 WASM/C 等效类型的自动转换的副作用。

当传递指针时,此类 API 不会进行转换 - 它们按原样将指针传递给 C API。它们仅通过安装与 JS 函数关联的 WASM 可见函数指针来将 JS 函数“转换为”WASM。

使用一次性回调的函数(如sqlite3_exec())仅在调用期间安装此类转换,并在返回之前卸载它们。

更持久的绑定,例如使用 sqlite3_create_function() 变体创建的自定义 SQL 函数或使用 sqlite3_create_collation() 安装的排序规则,以可以跟踪和清理绑定方式绑定,当 (A) 它们被后续对安装它们的函数的调用替换或卸载或 (B) 使用 sqlite3_close_v2() 的 JS 绑定关闭数据库句柄时。

小注意事项:此类跟踪仅通过安装在sqlite3.capi命名空间中的这些函数的 JS 绑定进行。如果调用较低级别的sqlite3.wasm.exports绑定,则会绕过此类跟踪。

如果客户端希望执行他们自己的 JS 到 WASM 函数转换处理,则客户端代码需要

  1. 使用 wasm.installFunction() 创建一个充当给定 JS 函数代理的 WASM 绑定函数。此步骤包括为函数分配 WASM 端函数指针。
  2. 将该指针传递给相应的 C API。
  3. 如有必要,最终将第一步中的指针传递给 wasm.uninstallFunction() 以将其从 WASM 环境中移除。如果未执行此操作,则函数绑定会有效泄漏。

如果这听起来很繁琐,那是因为它确实很繁琐!好消息是提供的绑定为所有常见情况处理了这个问题,因此客户端代码通常不需要处理此类细节。

在少数情况下,JS 绑定无法清理此类自动转换,因为它们是在 C API 深处处理的,远离 JS 绑定的视野。当前已知的“问题案例”是

结构体类型指针

某些 C 级结构如何暴露给 JavaScript,以及客户端代码如何与它们交互和操作它们,在 其自己的文档 中详细介绍。以下部分总结了可能针对函数参数发生的自动类型转换。

不会对结构指针结果值执行任何自动转换。它们始终作为 WASM 指针返回到 JS。

sqlite3*

声明为采用sqlite3*参数的函数可能会执行以下参数类型转换

sqlite3_stmt*

声明为采用sqlite3_stmt*参数的函数可能会执行以下参数类型转换

sqlite3_vfs*

声明为采用sqlite3_vfs*参数的函数可能会执行以下参数类型转换

与 C 对应 API 不同的 WASM API

为了保持一致性和能够将 C 级文档 用于 WASM API,我们尽一切努力使 C API 的 WASM 绑定尽可能地接近其 C 对应部分。但是,某些 API 非常受益于添加自定义包装器,这些包装器简化了在 JS 中客户端代码级别处理起来过于繁琐的特定情况。

本节描述了sqlite3.capi成员 API,这些 API 的语义根据其使用方法可能与其 C 对应部分不同。

除非另有特别说明,否则为了与 C API 保持一致,C API 的所有 JS 自定义绑定都需要与它们的 C 对应部分相同的参数数量,如果传递任何其他参数数量,则将返回capi.SQLITE_MISUSE

在所有情况下,当传递 JS 特定自定义中未考虑的参数时,它们会按原样将参数传递给底层 C API 并按 C 文档中的说明执行。但是,当传递某些 JS 类型时,它们的行为可能会不同,具体取决于上下文,可能会获得或失去功能。

受影响的 API 列在下面。

这些 API 的某些更一般的方面,例如如何处理来自 JS 的输出指针,在 WASM 实用程序文档 中进行了详细说明。

sqlite3_bind_blob()

C 参考:www:/c3ref/bind_blob.html

sqlite3_bind_blob()的工作方式与其 C 对应部分完全相同,除非其第 3 个参数是以下之一

在所有这些情况下,最终参数(文本析构函数)都会被忽略,并假定为 SQLITE_WASM_DEALLOC

第 3 个参数为null将被视为 0 的 WASM 指针。

如果第 3 个参数既不是 WASM 指针也不是上述类型之一,则返回capi.SQLITE_MISUSE

sqlite3_bind_text()

C 参考:www:/c3ref/bind_blob.html

sqlite3_bind_text()的工作方式与其 C 对应部分完全相同,除非其第 3 个参数是以下之一

在每种情况下,最终参数(文本析构函数)都会被忽略,并假定为 SQLITE_WASM_DEALLOC

第 3 个参数为null将被视为 0 的 WASM 指针。

如果第 3 个参数既不是 WASM 指针也不是上述类型之一,则返回capi.SQLITE_MISUSE

如果客户端代码需要绑定部分字符串,则需要在传递到这里之前先打包字符串,或者必须为第 3 个参数传递 WASM 指针和有效的第 4 个参数值,注意不要传递截断多字节 UTF-8 字符的值。当传递 WASM 格式字符串时,重要的是最终参数有效,否则可能会导致意外内容,或者如果应用程序读取超过 WASM 堆边界,甚至会导致崩溃。

sqlite3_close_v2()

sqlite3_close_v2()与其原生对应部分的区别仅在于它在调用原生实现之前尝试执行某些 JS 绑定相关的清理。简而言之,它努力清理任何代表正在关闭的数据库句柄自动安装的WASM 函数表条目。

以下数据库绑定条目由sqlite3_close_v2()清理

有关这些清理为何有用的详细信息,请参阅 有关自动函数指针转换的部分

sqlite3_config()

sqlite3_config()的 JS 绑定仅包装一小部分配置操作,省略任何对该环境没有意义或被认为对该环境过于底层的操作。仅接受下面列出的调用表单。任何其他配置选项都将返回capi.SQLITE_NOTFOUND。传递少于 2 个参数将触发返回capi.SQLITE_MISUSE

重大使用注意事项:在库“启动”后,不能合法地调用sqlite3_config(),如果调用则返回SQLITE_MISUSE。JS 绑定的初始化必然需要以使稍后对sqlite3_config()的调用非法的方式使用库。客户端可以使用sqlite3_shutdown()来“取消初始化”库,以便再次使sqlite3_config()的使用合法化,但这可能会导致某些 JS API 功能消失,例如 JS 注册的 VFS。

sqlite3_config(X, int):

sqlite3_config(X, int, int):

sqlite3_config(X, int64):

请注意,后者需要 BigInt 支持

sqlite3_create_collation() 及其相关函数

C 参考:www:/c3ref/create_collation.html

sqlite3_create_collation()sqlite3_create_collation_v2()的工作方式与其 C 端等效项完全相同,除了

  1. 如果第 3 个参数包含任何与编码相关的除capi.SQLITE_UTF8之外的值,则返回capi.SQLITE_FORMAT。不支持其他编码。作为特殊情况,如果该参数的最低 4 位为 0,则假定为SQLITE_UTF8

  2. 它们接受 JS 函数作为其函数指针参数,它们将为其安装 WASM 绑定代理。如果后续对该函数的调用替换了代理(使用 NULL 或不同的函数)或包含的数据库关闭时,代理会自动卸载。

成功时返回 0,错误时返回非 0,在这种情况下,其sqlite3*参数的错误状态可能包含更多信息。

sqlite3_create_function() 及其相关函数

C 参考:www:/c3ref/create_function.html

下面使用的参数名称直接引用原生 C 文档中的参数名称

int sqlite3_create_function(
  sqlite3 *db,
  const char *zFunctionName,
  int nArg,
  int eTextRep,
  void *pApp,
  void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
  void (*xStep)(sqlite3_context*,int,sqlite3_value**),
  void (*xFinal)(sqlite3_context*)
);
int sqlite3_create_function_v2(
  sqlite3 *db,
  const char *zFunctionName,
  int nArg,
  int eTextRep,
  void *pApp,
  void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
  void (*xStep)(sqlite3_context*,int,sqlite3_value**),
  void (*xFinal)(sqlite3_context*),
  void(*xDestroy)(void*)
);
int sqlite3_create_window_function(
  sqlite3 *db,
  const char *zFunctionName,
  int nArg,
  int eTextRep,
  void *pApp,
  void (*xStep)(sqlite3_context*,int,sqlite3_value**),
  void (*xFinal)(sqlite3_context*),
  void (*xValue)(sqlite3_context*),
  void (*xInverse)(sqlite3_context*,int,sqlite3_value**),
  void(*xDestroy)(void*)
);

sqlite3_create_function()sqlite3_create_function_v2()sqlite3_create_window_function() 与其原生对应版本仅在以下方面有所不同

  1. 第四个参数 (eTextRep) 必须不指定除 sqlite3.capi.SQLITE_UTF8 之外的任何编码。JS API 目前不支持任何其他编码,并且可能永远不会支持。如果提供了任何其他编码标志,则返回 sqlite3.capi.SQLITE_FORMAT。为了简化使用,任何假值都被解释为 SQLITE_UTF8。有关 SQLITE_RESULT_SUBTYPE 标志的重要注意事项,请参阅 sqlite3_result_subtype()

  2. 任何函数指针参数可以是 WASM 指针(假设为函数指针)或 JS 函数。在后一种情况下,每个函数都会使用 sqlite3.wasm.installFunction() 绑定到 WASM,并且该包装器将传递给原生实现。

生成的 JS 包装器的语义为

请注意

对于 xFunc()xStep()xInverse()xFinal()

如果任何 JS 端绑定的函数抛出异常,这些异常将被拦截并转换为数据库端错误,但 xDestroy() 除外:来自它的任何异常都被忽略,可能会生成 console.error() 消息。析构函数不得抛出异常。

安装后,目前无法从 WASM 中卸载自动转换的 WASM 绑定 JS 函数。它们可以根据 C API 中的文档从数据库中卸载,但此包装器目前没有到位的基础设施来释放 WASM 绑定的 JS 包装器,如果客户端卸载了 UDF,实际上会导致内存泄漏。在实践中,删除客户端安装的 UDF 很少见。如果此因素与特定客户端相关,他们可以自己创建 WASM 绑定的 JS 函数,保留其指针,并将指针传递到此处。稍后,他们可以释放这些指针(使用 wasm.uninstallFunction() 或等效方法)。

希望对其绑定有更多控制的客户端可以使用 sqlite3.wasm.installFunction() 创建自己的 WASM 绑定 JS 函数。以下小节中描述的辅助方法可以帮助实现这一点,并且作为所有三个创建函数的属性公开,以协助此类实现。

pCtx 参数包含到 JS 绑定的包装器中是一个痛苦的决定,但归根结底是 API 一致性问题。总的来说,该参数对于 JS 绑定来说是不相关的,因此对于大多数客户端 UDF 来说只是噪音。但是,如果省略它,则更高级的 UDF 用法将更难以编写,迫使用户编写自己的 WASM 绑定包装器来获取该参数。将其包含在 JS 回调接口中可确保该接口与 C 回调接口更一致(不包括 SQL 端参数传递方式),并能够开发高级用户用例。但是,预计人们经常会忘记添加它,这会导致他们的 UDF 参数列表“少一个”。希望很快将不得不向客户端 UDF 签名添加这个看似无关紧要的参数变成编写 UDF 的人的第二天性,就像 Python 程序员必须向他们的面向对象方法添加显式的 self 参数一样(而 C++、JavaScript 和大多数其他语言使该参数隐式)。

udfConvertArgs()

此函数是 sqlite3_values_to_js()已弃用别名,并且早于该函数添加到 API 中。

udfSetError()

此函数是 sqlite3_result_error_js()已弃用别名,并且早于该函数添加到 API 中。

udfSetResult()

此函数是 sqlite3_result_js()已弃用别名,并且早于该函数添加到 API 中。

sqlite3_db_config()

sqlite3_db_config() 与其 C 对应版本仅在一个小的技术细节上有所不同:C 函数是可变参数的,此类函数无法直接绑定到 WASM,但其手写的 JS 端包装器支持所有 C API 的参数组合,如 www:/c3ref/c_dbconfig_defensive.html 中所述。

sqlite3_deserialize()

C 参考:www:/c3ref/deserialize.html

sqlite3_deserialize() 在本质上与 C 端对应版本没有区别,但在内存分配方面有一些细微的注意事项。

关于分配器的文档以更抽象的术语涵盖了这个问题,但 sqlite3_deserialize() 提供了一个具体的示例,说明在该示例中了解环境的 C 级内存分配器的定义至关重要。

根据 sqlite3_deserialize() 的使用方式,传递给它的内存可能需要来自 sqlite3_malloc()sqlite3_realloc() 或其 64 位对应版本。

可以将 sqlite3.capi.SQLITE_DESERIALIZE_RESIZEABLE 标志传递给此函数,以根据需要启用新内存数据库的自动增长,在这种情况下,传递给 sqlite3_deserialize() 的内存必须来自 sqlite3_malloc()sqlite3_realloc()(或其 64 位对应版本),否则结果未定义

例如,让我们考虑以下代码段,它下载远程数据库并对其进行反序列化

// Do not copy/paste without reading the docs below!
const db = new sqlite3.oo1.DB();
await fetch('my.db')
  .then(response => response.arrayBuffer())
  .then(arrayBuffer => {
    const bytes = new Uint8Array(arrayBuffer);
    const p = sqlite3.wasm.allocFromTypedArray(bytes);
    db.onclose = {after: function(){sqlite3.wasm.dealloc(p)}};
    const rc = sqlite3.capi.sqlite3_deserialize(
      db.pointer, 'main', p, bytes.length, bytes.length,
      0
    );
    db.checkRc(rc);
  });

使用 sqlite3_deserialize() 是合法的,但仅因为它定义了一个由 sqlite3 管理的固定大小的内存区域。在这种情况下,sqlite3 是客户端应用程序拥有的内存的只读用户。

如果在上述反序列化调用的最终参数中使用了 SQLITE_DESERIALIZE_FREEONCLOSE 和/或 SQLITE_DESERIALIZE_RESIZEABLE 标志,则结果从抽象意义上讲将是未定义的行为(但请参阅下文了解原因)。

从抽象意义上讲sqlite3.wasm.allocFromTypedArray() 使用系统级分配器,并且将从 sqlite3.wasm.alloc()(即从 C 的 malloc())分配的内存传递给 sqlite3_free()sqlite3_realloc() 在语义上是不合法的。为了支持上述示例中提到的标志,需要将对 allocFromTypedArray() 的调用替换为使用 sqlite3_alloc() 分配内存的等效函数。但是,截至 2022 年 11 月 30 日,此项目的规范构建使用 sqlite3_malloc() 函数系列作为模块范围的分配器,这意味着在规范构建中这不是问题,但在自定义构建中可能是一个问题,具体取决于它们的构建方式。

sqlite3_exec()

C 参考:www:/c3ref/exec.html

sqlite3_exec(),如果以非函数对象作为其第三个参数调用

sqlite3_exec(myDbPtr, "drop table x", 0, 0, 0);

与所有其他 WASM 绑定函数的工作方式完全相同,包括其第二个参数(SQL)的自动转换,该转换支持 灵活的字符串转换

但是,如果以 JS 函数作为其第三个参数调用它,则它执行以下转换...

sqlite3_exec() 调用期间,它会安装一个 WASM 端函数,该函数将提供的回调挂接到 WASM 中。该绑定在 sqlite3_exec() 返回之前立即删除。

回调的 C 签名为

callback(void *, int colCount, char** pColValues, char** pColNames)

但回调的 WASM 绑定代理以如下方式调用它

callback(array colValues, array colNames)

即它将 C 风格的字符串数组转换为 JS 字符串数组。省略了初始 void 指针参数,因为 JS 闭包可以将此类状态绑定到回调中(如果需要),并且列数可通过两个数组参数的 length 属性获得。

如果回调抛出异常或返回任何非 0 值,代理会捕获该异常并将其转换为 C 结果,原生 sqlite3_exec() 实现将将其转换为 SQLITE_ABORT 结果。如果它不抛出异常,则其返回值将被强制转换为 32 位整数。因此,隐式返回(undefined 值)等效于结果代码 0(即成功)。

示例

const db = new sqlite3.oo1.DB().exec(
  "create table t(a); insert into t(a) values(3),(6),(9)"
);
sqlite3.capi.sqlite3_exec(
  db, // resolves to db.pointer, the underlying (sqlite3*)
  "select a from t",
  (vals,names)=>{
    console.log(names[0],vals[0]);
  },
  0, 0
);

将输出

a 3
a 6
a 9

请注意,为了与 C API 保持一致,sqlite3_exec() 需要所有 5 个参数,尽管最后两个几乎总是 0。

如果特定用例真正需要回调的 C 风格签名,可以通过 手动将回调安装到 WASM 中(使用签名字符串 "i(pipp)"),然后将其指针传递给 sqlite3_exec(),如下所示

const pCb = wasm.installFunction('i(pipp)', function(pVoid,nCols,aVals,aCols){
  // wasm.cArgvToJs() can be used to convert aVals and aCols to arrays:
  const vals = wasm.cArgvToJs(nCols, aVals);
  // Noting that the aCols list is constant across each sqlite3_exec()
  // invocation and can be cached _if_ this function will only ever
  // be used with a single specific query.

  // Alternately, individual name and value columns can be converted
  // to JS strings like:
  const strAt = (ndx)=>{
    return ndx<nCols
      ? wasm.cstrToJs(wasm.peekPtr(aVals + (ndx * wasm.ptrSizeof)))
      : undefined;
  };
  const val0 = strAt(0), val1 = strAt(1), val2 = strAt(2);
  // To coerce them to numbers, simply prefix the JS strings with a plus
  // sign, e.g. +strAt(...).
  return 0;
});
try {
  let rc = capi.sqlite3_exec(db, "select a, a*2 from foo", pCb, 0, 0);
  ...
}finally{
  // If the callback will be used repeated, skip this part:
  wasm.uninstallFunction(pCb);
}

sqlite3_prepare_v2()sqlite3_prepare_v3()

C 参考:www:/c3ref/prepare.html

与 C API 中一样,sqlite3_prepare_v2() 只是 sqlite3_prepare_v3() 的代理,它将一组默认标志传递给该函数。由于 JS 和 C 之间的差异,sqlite3_prepare_v3() 具有两种截然不同的用法

作为参考,这些函数的原生签名为

int sqlite3_prepare_v2(
  sqlite3 *db,            /* Database handle */
  const char *zSql,       /* SQL statement, UTF-8 encoded */
  int nByte,              /* Maximum length of zSql in bytes. */
  sqlite3_stmt **ppStmt,  /* OUT: Statement handle */
  const char **pzTail     /* OUT: Pointer to unused portion of zSql */
);
int sqlite3_prepare_v3(
  sqlite3 *db,            /* Database handle */
  const char *zSql,       /* SQL statement, UTF-8 encoded */
  int nByte,              /* Maximum length of zSql in bytes. */
  unsigned int prepFlags, /* Zero or more SQLITE_PREPARE_ flags */
  sqlite3_stmt **ppStmt,  /* OUT: Statement handle */
  const char **pzTail     /* OUT: Pointer to unused portion of zSql */
);

sqlite3_prepare_v3() 绑定处理两种具有不同 JS/WASM 语义的不同用法

  1. 对于 SQL 参数为 JS 字符串的情况
    int sqlite3_prepare_v3(pDb, sqlString, -1, prepFlags, ppStmt , null)
    SQL 的长度参数(第三个参数)必须始终为负数,因为它必须是字节长度,并且从 JS 中计算该值成本很高(在 JS 中,只有字符串的字符长度易于获取)。出于代码/文档兼容性的原因,它保留在此 API 的接口中,但目前始终被忽略,除非在下面有所说明。

  2. 对于 SQL 参数是 WASM 指针指向 C 字符串的情况。
    int sqlite3_prepare_v3(pDb, sqlPointer, sqlByteLen, prepFlags, ppStmt, sqlPointerToPointer)
    第三个参数按原样使用,但 C 样式的输入字符串(第二个参数)以 0 字节结尾至关重要。

在用法 (1) 中,如果第二个参数必须是灵活字符串而非WASM 指针。如果是,则sqlite3_prepare_v3() 假设情况 (1) 并调用底层 C 函数,其等效于

(pDb, sqlAsString, -1, prepFlags, ppStmt, null)

最后一个参数pzTail 在这种情况下被忽略,因为当通过字符串类型值时,其结果毫无意义:该字符串会经过另一层内部转换以满足 WASM 的要求,并且结果指针将引用该临时转换的内存,而不是传入的字符串。

如果 SQL 是 Uint8Array 或 Int8Array,则假定它包含 UTF-8 字符串,将其转换为 C 字符串,并将字节数组的长度用作sqlByteLen 参数。

如果 SQL 参数不是字符串或上述数组类型之一,则它必须是指针,指向在 WASM 内存中分配的以 NUL 结尾的字符串(例如,使用wasm.alloc()、来自 C 栈或等效方法)。在这种情况下,最后一个参数可以是 0/null/undefined,或者必须是指针,编译后的 SQL 的“尾部”将写入该指针,如 C 端sqlite3_prepare_v3() 中所述。在情况 (2) 中,底层 C 函数将调用等效于

(pDb, sqlAsPointer, sqlByteLen, prepFlags, ppStmt, pzTail)

它返回其结果和编译后的语句,如 C API 中所述。

获取输出指针(第五和第六个参数)需要使用wasm.peek()(或等效方法 - 参见下文),并且pzTail 将指向相对于sqlAsPointer 值的地址。

如果传递了无效的第二个参数类型,则此函数将返回SQLITE_MISUSE,并且capi.sqlite3_errmsg() 将包含描述问题的字符串。

旁注:如果给定空字符串,或仅包含注释或空 SQL 表达式的字符串,则返回 0,但结果输出指针将为NULL

sqlite3_randomness()

C 参考:www:/c3ref/randomness.html

用法

如果传递单个参数,该参数似乎是面向字节的 TypedArray(Int8Array 或 Uint8Array),则此函数将该 TypedArray 视为输出目标,获取theArray.byteLength 字节的随机数,并用它填充整个数组。作为特例,如果数组的长度为 0,则此函数的行为就像传递了 (0,0) 一样。当以这种方式调用时,它返回其参数,否则返回undefined 值。

如果使用任何其他参数调用,则这些参数将按原样传递给 C API。如果传递任何不兼容的值,则结果未定义。

sqlite3_result_subtype()

C 参考:www:/c3ref/result_subtype.html

此函数与其 C 对应部分没有实质性区别,但带有一个小警告

规范的 WASM 构建包含SQLITE_STRICT_SUBTYPE 编译时标志,这意味着任何满足以下任何条件的用户定义函数都必须在通过sqlite3_create_function() 及其相关函数定义它们时包含SQLITE_RESULT_SUBTYPE 标志

请参阅 C API 文档以了解完整详细信息。

sqlite3_set_authorizer()

C 参考:www:/c3ref/set_authorizer.html

sqlite3_set_authorizer() 的工作方式与 C API 中一样,除非将其第二个参数作为 JS 函数传递,在这种情况下,它将安装一个代理,该代理将在将它们传递给客户端提供的函数之前将 C 字符串(指针)参数转换为 JS 字符串。也就是说,C 本机调用具有此签名

int (*xAuth)(void*,int,const char*,const char*,const char*,const char*)

此函数以这种形式将它包装在 JS 调用中

int func(void*, int, string, string, string, string)

如果 JS 函数返回虚假值(例如,通过隐式返回undefined 值),则将其强制转换为整数 0。如果 JS 函数抛出异常,则会捕获该异常并将其转换为数据库级别的错误。

如果传递非函数第二个参数,例如 WASM 指针或null,则将其按原样传递到底层绑定。希望在他们的 JS 授权回调中接收 C 样式字符串参数的客户端可以使用wasm.installFunction() 安装他们的回调,并将从该函数返回的函数指针传递到此函数中,在这种情况下,不会安装额外的类型转换/异常处理代理层。在这种情况下,重要的是此类代理不要抛出/传播异常,请注意,任何失败的内存分配都可能触发sqlite3.WasmAllocError

自动扩展 API

C 参考:www:/c3ref/auto_extension.html

自动扩展 API 的行为与其 C 对应部分基本相同,只有以下细微差别

出于实际目的,这些差异意味着以下内容

会话和变更集 API

会话 API 绑定与其C 对应部分的不同之处仅在于

  1. 它们可以选择为其函数指针参数接受 JS 函数。它们将自动将 JS 函数类型参数转换为函数指针,如本文档其他地方所述
  2. sqlite3session_delete() 将清理通过sqlite3session_table_filter() 添加的任何自动安装的函数指针

大多数函数指针参数类型仅接受与 WASM 兼容的值,这些值将按原样传递给 JS 函数。但是,任何具有以下本机签名的函数在调用提供的 JS 函数之前都会进行参数转换

int (*)(void *, const char *)

这适用于以下函数的xFilter 回调参数

那里发生的唯一参数转换是第二个参数在传递给 JS 函数之前转换为 JS 字符串。如果出于某些特殊情况,客户端代码反而需要 C 字符串指针,则可以通过手动安装回调函数,然后将其指针(由安装过程提供)传递给 sqlite3 API 而不是传递 JS 函数来实现。这将抑制任何自动转换。对于安装,请使用签名字符串"i(ps)"

JS 安装的代理仅在调用期间安装,除了sqlite3session_table_filter(),它与传递给该函数的sqlite3_session 对象相关联地安装。sqlite3session_delete() 将清理由sqlite3session_table_filter() 安装的任何此类代理。

会话 API 扩展

这些分别是围绕sqlite3changeset_new()sqlite3changeset_old() 的精简包装器,它们从第二个参数指定的列中获取sqlite3_value*,然后返回将其传递给sqlite3_value_to_js() 的结果。这两个函数在发生错误时都会抛出异常,包括底层函数返回非 0 时。如果sqlite3changeset_new() 返回 0 但将输出值设置为 NULL,则此函数返回undefined 值,该值永远不是从sqlite3_value 的有效转换,因此是明确的。

钩子 API

C 参考

sqlite3_commit_hook()sqlite3_rollback_hook()sqlite3_update_hook() 函数以及sqlite3_preupdate_hook() 函数系列与其 C 对应部分在根本上没有区别,但sqlite3_update_hook()sqlite3_preupdate_hook() 参与自动 JS 到 WASM 函数转换,并且此类转换在sqlite3_close_v2() 中会进行清理。请注意,sqlite3_update_hook()sqlite3_preupdate_hook() 需要BigInt 支持

如果将 JS 函数传递给sqlite3_update_hook()sqlite3_preupdate_hook(),则它将为两个 C 字符串参数进行参数转换。也就是说,这些回调的本机签名为

// sqlite3_update_hook() callback:
void (*)(void *, int, char const *, char const *, sqlite3_int64)
// sqlite3_preupdate_hook() callback:
void (*)(void *, sqlite3*, int, char const *, char const *, sqlite3_int64, sqlite3_int64)

而 JS 签名为

// sqlite3_update_hook() callback:
void (void *, int, string, string, BigInt)
// sqlite3_preupdate_hook() callback:
void (void *, sqlite3*, int, string, string, BigInt, BigInt)

挂钩回调不得传播任何异常。它们不会被此层自动捕获,因为无法通过 sqlite3 从回滚和(预)更新挂钩中报告错误。

更新前钩子扩展

这些分别是围绕sqlite3_preupdate_new()sqlite3_preupdate_old() 的精简包装器,它们从第二个参数指定的列中获取sqlite3_value*,然后返回将其传递给sqlite3_value_to_js() 的结果。这两个函数在发生错误时都会抛出异常,包括底层函数返回非 0 时。

WASM 特定的 C 语言风格函数

本节记录存在于sqlite3.capi 命名空间中但特定于 WASM/JS API 的 C 样式函数。请注意,sqlite3.wasm API 有一些类似 C 的函数,此处记录这些函数,因为它们不是公共 API 的一部分,并且不打算在此项目自己的代码之外使用。

sqlite3_column_js()

mixed sqlite3_column_js(pStmt, iCol [,throwIfCannotConvert=true])

返回将sqlite3_column_value(pStmt,iCol) 的结果传递给sqlite3_value_to_js() 的结果。此函数的第三个参数被此函数忽略,除非将其作为sqlite3_value_to_js() 的第二个参数传递。如果sqlite3_column_value() 返回 NULL(例如,因为列索引超出范围),则此函数返回undefined,无论第三个参数是什么。如果第三个参数为虚假且转换失败,则将返回undefined

sqlite3_js_aggregate_context()

pointer sqlite3_js_aggregate_context(pCtx, n)

围绕sqlite3.capi.sqlite3_aggregate_context() 的精简包装器,其行为相同,除非该函数返回 0并且n 为真,则抛出WasmAllocError。如果n 为假,则如果该函数返回 0,则它只返回 0。此行为旨在帮助开发xFinal() 实现。

示例,使用 OO1 API

const capi = sqlite3.capi,
  wasm = sqlite3.wasm,
  sjac = capi.sqlite3_js_aggregate_context;
db.createFunction({
  name: 'summer',
  xStep: (pCtx, n)=>{
    const ac = sjac(pCtx, 4);
    wasm.poke32(ac, wasm.peek32(ac) + Number(n));
  },
  xFinal: (pCtx)=>{
    const ac = sjac(pCtx, 0);
    return ac ? wasm.peek32(ac) : 0;
  }
});

当在一个 SQL 语句中多次运行某个聚合函数时,聚合上下文指针(与 pCtx 指针相反)将在调用之间保持稳定,使用户能够保持每次调用的状态彼此分离。例如,如果在一个语句中调用两次,则一组对聚合函数的调用将具有 X 的聚合上下文,另一组将具有 Y 的聚合上下文。该指针可用作客户端查找表中的键,以便将任意复杂的数据映射到聚合函数,但客户端必须确保在 xFinal() 实现中删除映射。

sqlite3_js_db_export()

Uint8Array sqlite3_js_db_export(pDb [, schema=0])

围绕 sqlite3_serialize() 的一个便利包装器,它将给定的 sqlite3* 指针或 sqlite3.oo1.DB 实例序列化为 Uint8Array。

成功时返回一个 Uint8Array。如果模式为空,则返回一个空数组。

schema 是要序列化的模式。它可以是 WASM C 字符串指针或 JS 字符串。如果它是假值,则默认为 "main"

发生错误时,它会抛出一个描述问题的异常。

请注意,此操作受库的构建时最大单次分配限制 (SQLITE_MAX_ALLOCATION_SIZE) 限制,并且无法导出大于该大小大约的数据库。在 3.44 版之前,大约为 536mb。从 3.44 版开始,使用核心库的默认限制 2gb。

sqlite3_js_db_uses_vfs()

boolean sqlite3_js_db_uses_vfs(pDb, vfsName, dbName)

给定一个 sqlite3*、一个 sqlite3_vfs 名称和一个可选的数据库名称(默认为 "main"),如果该数据库句柄使用该 VFS,则返回真值(见下文),否则返回 false。如果 pDb 为假值,则第三个参数将被忽略,并且此函数如果默认 VFS 名称与第二个参数的名称匹配,则返回真值。如果 pDb 为真值但指向无效指针,则结果未定义。第三个参数指定要检查的给定数据库连接的数据库名称,默认为主数据库。

第二个和第三个参数可以是 JS 字符串或 WASM C 字符串。如果第二个参数是 NULL WASM 指针,则假定为默认 VFS。

它返回的真值是指向 sqlite3_vfs 对象的指针。

为了允许从可能通过 C 栈调用的 API(如 SQL UDF)安全地使用此函数,此函数不会抛出异常:如果错误的参数在传递到 wasm 空间时导致转换错误,则返回 false。

sqlite3_js_db_vfs()

pointer sqlite3_js_db_vfs(dbPointer, dbName=0)

给定一个 sqlite3* 和一个数据库名称(JS 字符串或 WASM C 字符串指针,可以为 0),返回一个指向负责它的 sqlite3_vfs 的指针。如果给定的数据库名称为 null/0 或未提供,则假定为 "main"

sqlite3_js_kvvfs_...()

有关 KVVFS 特定函数的详细信息,请参阅 KVVFS 文档

sqlite3_js_db_uses_vfs()

boolean sqlite3_js_db_uses_vfs(pDb,vfsName,dbName=0)

给定一个 sqlite3*、一个 sqlite3_vfs 名称和一个可选的数据库名称(默认为“main”),如果该数据库使用该 VFS,则返回真值(见下文),否则返回 false。如果 pDb 为假值,则第三个参数将被忽略,并且此函数如果默认 VFS 名称与第二个参数的名称匹配,则返回真值。如果 pDb 为真值但指向无效指针,则结果未定义。第三个参数指定要检查的给定数据库连接的数据库名称,默认为主数据库。

第二个和第三个参数可以是 JS 字符串或 WASM C 字符串。如果第二个参数是 NULL WASM 指针,则假定为默认 VFS。如果第三个是 NULL WASM 指针,则假定为“main”。

它返回的真值是指向 sqlite3_vfs 对象的指针。

为了允许从可能通过 C 栈调用的 API(如 SQL UDF)安全地使用此函数,此函数不会抛出异常:如果错误的参数在传递到 wasm 空间时导致转换错误,则返回 false。

sqlite3_js_posix_create_file()

(在 3.43 版中添加,作为已弃用的 sqlite3_js_vfs_create_file() 的替代方案。)

int sqlite3_js_posix_create_file(fiename, data[, dataLen = data.byteLength])

如果当前环境支持 POSIX 文件 API,则此例程将使用这些 API 创建(或覆盖)给定的文件。这主要用于基于 Emscripten 的构建中,其中 POSIX API 通过内存中虚拟文件系统透明地代理。它在其他环境中的行为可能有所不同。

第一个参数必须是 JS 字符串或 WASM C 字符串,其中包含文件名。请注意,如果文件名具有目录部分,则此例程不会创建中间目录。

第二个参数可以是有效的 WASM 内存指针、ArrayBuffer 或 Uint8Array。第三个必须是要复制的数据数组的长度(以字节为单位)。如果第二个参数是 ArrayBuffer 或 Uint8Array,并且第三个不是正整数,则第三个默认为数组的 byteLength 值。

如果 data 是 WASM 指针并且 dataLen 超出 data 的范围,则结果未定义。

如果任何参数无效或创建或写入文件失败,则抛出异常。

sqlite3_js_rc_str()

string sqlite3_js_rc_str(int)

给定一个 SQLITE_... 常量值,此函数返回该常量的字符串形式,或者如果找不到匹配项则返回 undefined,请注意,C API 中提供的一些常量未导出到 WASM,因为它们根本没有在那里使用。示例

sqlite3_js_rc_str(sqlite3.capi.SQLITE_ERROR); // ==> "SQLITE_ERROR"

sqlite3_js_sql_to_string()

在 3.44 中添加。

string sqlite3_js_sql_to_string(v)

将 SQL 输入从各种方便的格式转换为纯字符串。

如果 v 是字符串,则按原样返回。如果它是数组,则返回其 join("") 结果。如果它是 Uint8Array、Int8Array 或 ArrayBuffer,则假定它包含 UTF-8 编码的文本,并将其解码为字符串。如果它看起来像 WASM 指针,则返回 wasm.cstrToJs(v)。否则返回 undefined

sqlite3_js_vfs_create_file()

注意:这种方法在 sqlite3 的调试版本中无法正常工作,因为其超出范围的 sqlite3_vfs API 使用会在核心库中触发断言。这不幸的是直到 2023-08-11 才被发现。此函数现已弃用,不应在新的代码中使用。

替代选项

用法

使用适合给定 sqlite3_vfs 的存储创建(或覆盖)文件。第一个参数可以是 VFS 名称(仅限 JS 字符串,不是 WASM C 字符串)、WASM 托管的 sqlite3_vfs*capi.sqlite3_vfs 实例。传递 0(NULL 指针)以使用默认 VFS。如果传递的字符串使用 sqlite3_vfs_find() 未解析,则会抛出异常。(请注意,WASM C 字符串不被接受,因为无法将其与 C 级的 sqlite3_vfs* 区分。)

第二个参数(文件名)必须是 JS 或 WASM C 字符串。

第三个可以是假值、有效的 WASM 内存指针、ArrayBuffer 或 Uint8Array。第四个必须是要复制的数据数组的长度(以字节为单位)。如果第三个参数是 Uint8Array 或 ArrayBuffer,并且第四个不是正整数,则第四个默认为数组的 byteLength 值。

如果 data 为假值,则创建一个文件,其中包含 dataLen 字节未初始化的数据(无论 truncate() 在其中留下什么)。如果 data 不为假值,则创建一个或截断一个文件,并将其填充数据源的前 dataLen 字节。

如果任何参数无效或创建或写入文件失败,则抛出异常。

请注意,大多数 VFS不会自动创建文件名的目录部分,也不是所有 VFS都具有目录的概念。如果给定的文件名对于给定的 VFS 无效,则会抛出异常。此函数主要存在是为了帮助实现文件上传功能,但需要注意的是,客户端必须了解他们想要上传到的 VFS,并且该 VFS 必须支持该操作。

VFS 特定说明

sqlite3_js_vfs_list()

array sqlite3_js_vfs_list()

返回当前所有已注册 sqlite3 VFS 的名称数组。

sqlite3_result_error_js()

void sqlite3_result_error_js(sqlite3_context * pCtx, Error e)

如果 eWasmAllocError,则调用 sqlite3_result_error_nomem(),否则调用 sqlite3_result_error()。在后一种情况下,第二个参数将强制转换为字符串以创建错误消息。

不会抛出异常。

sqlite3_result_js()

void sqlite3_result_js(pCtx, value)

此函数充当其他 sqlite3_result_...() 例程的代理,具体取决于其第二个参数的类型

发生错误时,它会调用 sqlite3_result_error() 并提供问题的描述。

此函数的第一个参数是 (sqlite3_context*)

不会抛出异常。

sqlite3_value_to_js()

mixed sqlite3_value_to_js(sqlite3_value* v, throwIfCannotConvert=true)

给定一个 (sqlite3_value*),此函数尝试将其转换为等效的 JS 值,并尽可能保持保真度并将其返回。

默认情况下,如果无法确定任何合理的转换,它会抛出异常。如果传递了假值的第二个参数,则如果找不到合适的转换,它会返回 undefined。请注意,从 SQL 到 JS 没有导致 undefined 值的转换。如果分配内存用于转换失败,它始终会抛出 WasmAllocError

sqlite3_values_to_js()

array sqlite3_values_to_js(int argc, sqlite3_value* pArgV, throwIfCannotConvert=true)

需要一个 sqlite3_value* 对象的 C 样式数组以及该数组中的条目数。返回一个 JS 数组,其中包含将每个 C 数组条目传递给 sqlite3_value_to_js() 的结果。此函数的第三个参数将作为该函数的第二个参数传递。