另请参阅:JS中的C结构体
虚拟表 是sqlite最强大的功能之一,它为客户端提供了一种通过SQL访问几乎任意数据的方式。但是,强大的功能也伴随着非凡的复杂性。
虚拟表(又名vtab)API是库的一个高级特性,关于它有大量的文档,并且有示例程序演示它。这些文档没有尝试教用户如何编写自己的虚拟表,而是仅关注JS中这样做的特定组件。
JS虚拟表API与C API几乎是一一对应的,唯一的区别是增加了一些辅助例程来访问和操作API使用的C端状态。此处描述的API对于在JS中定义虚拟表不是必需的,但仅是为了简化流程而提供。如果客户端愿意,可以使用仅C风格的API来实现虚拟表。
管理指向结构体的指针的生命周期
sqlite3_module
接口对sqlite3_vtab
和sqlite3_vtab_cursor
对象有明确的生命周期规则,其工作方式如下
sqlite3_vtab
:xCreate()
或xConnect()
分配一个实例并将它的指针写入该函数的输出指针参数中。- 其他几个方法接收该指针作为参数。
xDestroy()
或xDisconnect()
传递相同的指针,并期望释放其内存。
sqlite3_vtab_cursor
:xOpen()
分配一个实例并将它的指针写入该函数的输出指针参数中。- 其他几个方法接收该指针作为参数。
xClose()
传递相同的指针,并期望释放其内存。
JS代码无法对原始指针做太多操作,需要包装对象才能访问和操作它们,如C结构体文档中所述。类似地,JS端实例不能像C代码那样“扩展”这些类型,但它们可以将任何实现特定的状态附加到JS实例(因此,实际上,比它们的C对应物更容易自定义其状态)。
sqlite3.vtab
对象提供API来简化JS中这些对象的生命周期管理和指针映射。这些API对sqlite3_vtab
和sqlite3_vtab_cursor
的工作方式相同,但在sqlite3_module
接口的不同位置使用。
API对象分别命名为vtab.xVtab
和vtab.xCursor
,用于sqlite3_vtab
和sqlite3_vtab_cursor
类型,它们的方法将在下面描述。
主要的sqlite3 JS单元测试有一些示例虚拟表,演示了这些API的使用。
create()
传递来自适当sqlite3_module
方法(见下文)的输出指针参数,这将创建一个新的结构体实例,将它的pointer
值写入给定的输出指针,并返回该对象。如果结构体实例的分配失败,则会抛出异常。
用法
sqlite3.vtab.xVtab.create()
:要从sqlite3_module::xConnect()
或xCreate()
实现中调用。sqlite3.vtab.xCursor.create()
:要从xOpen()
中调用。
get()
传递来自适当sqlite3_module
方法(见下文)的结构体指针,这将返回为该指针创建并由create()
返回的相同结构体实例包装器。
用法
sqlite3.vtab.xVtab.get()
:要从接受(sqlite3_vtab*
)指针的sqlite3_module
方法中调用,除了xDestroy()
/xDisconnect()
,在这种情况下使用unget()
或dispose()
。sqlite3.vtab.xCursor.get()
:要从任何接受sqlite3_vtab_cursor*
参数的sqlite3_module
方法中调用,除了xClose()
,在这种情况下使用unget()
或dispose()
。
要记住的规则:永远不要对该函数返回的实例调用dispose()
。延迟到调用...之前。
unget()
和dispose()
unget()
与get()
相同,但它还会断开给定指针与返回的结构体对象之间的映射,以便将来使用相同指针调用此函数或get()
将返回undefined
值。调用者有责任在封闭函数返回之前对返回的对象调用theStruct.dispose()
。
dispose()
的工作方式与unget()
类似,但它会调用theStruct.dispose()
而不是返回对象。调用unget()
会强制调用者在完成对返回的对象的操作后调用dispose()
。
用法
sqlite3.vtab.xVtab.unget/dispose()
:要从sqlite3_module::xDisconnect()
或xDestroy()
实现中调用,或者在失败的xCreate()
或xConnect()
的错误处理中调用。sqlite3.vtab.xCursor.unget/dispose()
:要从xClose()
或在失败的xOpen()
的清理过程中调用。
sqlite3_module
方法
sqlite3_module
类继承自核心C结构体类型并扩展了层次结构...
setupModule()
sqlite3_module setupModule(options)
设置此模块对象。
一个帮助程序,用于初始化和设置sqlite3_module()对象,以便稍后使用sqlite3_create_module()
安装到各个数据库中。需要一个具有以下属性的对象
methods
:一个对象,包含一个映射,将C端sqlite3_module方法的名称(例如xCreate、xBestIndex等)映射到这些函数的JS实现。执行某些特殊情况处理,如下所述。catchExceptions
(默认值为false):如果为真,则给定的方法不会按原样映射,而是包装在包装器中,这些包装器将异常转换为SQLITE_ERROR或SQLITE_NOMEM的结果代码,具体取决于异常是否为sqlite3.WasmAllocError。对于xConnect和xCreate方法,异常处理程序还会将输出错误字符串设置为异常的错误字符串。
如果catchExceptions
为false,则客户端必须确保没有异常从方法中逃逸,因为这样做会将它们通过C API移动,从而导致未定义的行为。(VtabHelper.xError()旨在帮助报告此类异常)。可选
struct
:一个sqlite3_module
实例。如果未设置,则会自动创建一个。如果当前的this
是一个sqlite3_module
,则会无条件地使用它来代替struct
。可选
iVersion
:如果设置,则它必须是整数类型,并且会被分配到结构体对象的$iVersion
成员中。如果未设置,则此函数会尝试根据它拥有的方法列表为该属性定义一个值。
某些方法可能引用相同的实现。为了简化此类方法的定义
如果
methods.xConnect
为true
,则使用methods.xCreate
的值来代替它,反之亦然。如果xConnect/xCreate函数完全相同(相同指针值),则sqlite会对它们进行特殊处理。如果
methods.xDisconnect
为true,则使用methods.xDestroy
的值来代替它,反之亦然。
这是为了便于在传递的对象中内联创建这些方法,而无需客户端显式获取其中一个的引用以将其分配给另一个。
已安装的catchExceptions
处理程序将考虑对上述函数的相同引用,并为两者安装相同的包装器函数,以便底层库看到这两个函数的相同指针值。
期望给定的方法返回整数类型,如C API所期望的那样。如果catchExceptions
为真,则包装函数的返回值将按原样使用,如果函数返回假值(例如,如果没有显式返回),则将其转换为0。如果catchExceptions
未激活,则方法实现必须显式返回整数类型。否则会导致失败,特别是在错误情况下,因为这些错误将不会被报告。
在发生错误时抛出异常。成功时,返回一个sqlite3_module
对象:this
或opt.struct
或一个新的sqlite3_module
实例,具体取决于它的调用方式。
sqlite3_index_info
sqlite3_index_info
对象用于在sqlite3_module::xBestIndex()
实现和sqlite3引擎之间传递信息。JS代码不管理这些对象的生命周期,并且不得将指针保留在这些对象上,时间长于接收指针作为参数的函数调用。JS代码需要使用capi.sqlite3_index_info
包装器对象来与C级结构体状态进行交互。
示例
const xBestIndex = function(pVtab, pIdxInfo){
const vtab = sqlite3.vtab.xVtab.get(pVtab); // documented above
const sii = new sqlite3.capi.sqlite3_index_info(pIdxInfo);
...
sii.dispose(); // see notes below
return 0;
};
当C结构体包装器使用指针参数构造时,如上所示,它们不会分配新的实例,而是提供对现有实例的访问。在这种情况下,调用它们的dispose()
方法并非严格必要,但通常养成一个好习惯,以避免在确实需要的情况下出现内存泄漏。
除了所有JS绑定C结构体都可以使用的与结构体相关的API之外,sqlite3_index_info
还在其原型中安装了以下辅助方法,所有实例都可以使用这些方法
nthConstraint(n, asPtr=false)
如果n
>=0且小于this.$nConstraint
,则此函数要么返回指向this.$aConstraint
中第n个(从0开始)条目的WASM指针(如果传递了真值作为第二个参数),要么返回一个包装该地址的sqlite3_index_info.sqlite3_index_constraint
对象(如果传递了假值或没有第二个参数)。如果n
超出范围,则返回假值。nthConstraintUsage(n, asPtr=false)
工作方式与nthConstraint()
相同,但返回this.$aConstraintUsage
中的状态,因此如果未传递第二个参数或传递了假值作为第二个参数,则返回一个sqlite3_index_info.sqlite3_index_constraint_usage
实例。nthOrderBy(n, asPtr=false)
如果n
>=0且小于this.$nOrderBy
,则此函数要么返回指向this.$aOrderBy
中第n个(从0开始)条目的WASM指针(如果传递了真值作为第二个参数),要么返回一个包装该地址的sqlite3_index_info.sqlite3_index_orderby
对象(如果传递了假值或没有第二个参数)。如果n
超出范围,则返回假值。
这些都已在主要的单元测试脚本中进行了演示。