虚拟表是注册到打开的 SQLite 数据库连接中的一个对象。从 SQL 语句的角度来看,虚拟表对象看起来就像任何其他表或视图。但在幕后,对虚拟表的查询和更新会调用虚拟表对象的回调方法,而不是读取和写入数据库文件。
虚拟表机制允许应用程序发布接口,这些接口可以从 SQL 语句访问,就好像它们是表一样。SQL 语句几乎可以对虚拟表执行对真实表可以执行的任何操作,但以下情况除外
各个虚拟表实现可能会施加额外的约束。例如,一些虚拟实现可能提供只读表。或者一些虚拟表实现可能允许 INSERT 或 DELETE,但不允许 UPDATE。或者一些虚拟表实现可能限制可以进行的 UPDATE 类型。
虚拟表可以表示内存中的数据结构。或者它可以表示磁盘上数据的视图,这些数据不在 SQLite 格式中。或者应用程序可以按需计算虚拟表的内容。
以下是虚拟表的现有和假定用途
请参阅 虚拟表列表 页面,以获取更长的实际虚拟表实现列表。
虚拟表是使用 CREATE VIRTUAL TABLE 语句创建的。
CREATE VIRTUAL TABLE 语句创建一个名为 table-name 的新表,该表派生自类 module-name。 module-name 是由 sqlite3_create_module() 接口为虚拟表注册的名称。
CREATE VIRTUAL TABLE tablename USING modulename;
还可以为模块提供以逗号分隔的参数,这些参数位于模块名称之后
CREATE VIRTUAL TABLE tablename USING modulename(arg1, arg2, ...);
模块参数的格式非常通用。每个 module-argument 可以包含关键字、字符串文字、标识符、数字和标点符号。每个 module-argument 都会以书面形式(作为文本)传递到 构造函数方法,该方法在创建虚拟表时调用,并且该构造函数负责解析和解释参数。参数语法足够通用,以便虚拟表实现(如果愿意)可以将参数解释为 列定义,就像在普通 CREATE TABLE 语句中一样。实现也可以对参数施加一些其他解释。
创建虚拟表后,可以使用与其他表相同的语法,但要考虑上面提到的异常和特定虚拟表实现施加的限制。虚拟表是使用普通的 DROP TABLE 语法销毁的。
没有“CREATE TEMP VIRTUAL TABLE”语句。要创建临时虚拟表,请在虚拟表名称之前添加“temp”模式。
CREATE VIRTUAL TABLE temp.tablename USING module(arg1, ...);
一些虚拟表在每个注册了其模块的数据库连接的“main”模式中自动存在,即使没有 CREATE VIRTUAL TABLE 语句。此类虚拟表称为“同名虚拟表”。要使用同名虚拟表,只需将模块名称用作表一样使用即可。同名虚拟表仅存在于“main”模式中,因此如果使用其他模式名称作为前缀,它们将无法使用。
dbstat 虚拟表 是同名虚拟表的一个示例。要将 dbstat 虚拟表用作同名虚拟表,只需像普通表一样查询“dbstat”模块名称即可。(请注意,SQLite 必须使用 SQLITE_ENABLE_DBSTAT_VTAB 选项进行编译,才能将 dbstat 虚拟表包含在构建中。)
SELECT * FROM dbstat;
如果虚拟表的 xCreate 方法与 xConnect 方法完全相同,或者如果 xCreate 方法为 NULL,则虚拟表是同名的。 xCreate 方法在首次使用 CREATE VIRTUAL TABLE 语句创建虚拟表时调用。 xConnect 方法在数据库连接附加到模式或重新解析模式时调用。当这两个方法相同时,表示虚拟表没有需要创建和销毁的持久状态。
如果 xCreate 方法为 NULL,则禁止对该虚拟表使用 CREATE VIRTUAL TABLE 语句,并且该虚拟表是“仅同名虚拟表”。仅同名虚拟表可用作 表值函数。
请注意,在 3.9.0 版本(2015-10-14)之前,SQLite 在调用它之前不会检查 xCreate 方法是否为 NULL。因此,如果仅同名虚拟表是在 SQLite 3.8.11.1 版本(2015-07-29)或更早版本中注册的,并且尝试对该虚拟表模块运行 CREATE VIRTUAL TABLE 命令,则会发生跳转到 NULL 指针的情况,从而导致崩溃。
虚拟表实现使用几个新的 C 级对象
typedef struct sqlite3_vtab sqlite3_vtab; typedef struct sqlite3_index_info sqlite3_index_info; typedef struct sqlite3_vtab_cursor sqlite3_vtab_cursor; typedef struct sqlite3_module sqlite3_module;
sqlite3_module 结构定义一个模块对象,用于实现虚拟表。可以将模块视为一个类,可以从中构造具有类似属性的多个虚拟表。例如,可能有一个模块提供对磁盘上逗号分隔值 (CSV) 文件的只读访问。然后,可以使用该模块创建多个虚拟表,其中每个虚拟表都引用不同的 CSV 文件。
模块结构包含 SQLite 调用的方法,以对虚拟表执行各种操作,例如创建虚拟表的新实例或销毁旧实例、读取和写入数据、搜索和删除、更新或插入行。模块结构将在下面详细解释。
每个虚拟表实例都由一个 sqlite3_vtab 结构表示。sqlite3_vtab 结构如下所示
struct sqlite3_vtab { const sqlite3_module *pModule; int nRef; char *zErrMsg; };
虚拟表实现通常会对该结构进行子类化,以添加其他私有字段和实现特定的字段。nRef 字段由 SQLite 内核内部使用,虚拟表实现不应更改它。虚拟表实现可以通过将错误消息字符串放在 zErrMsg 中来向内核传递错误消息文本。必须使用 SQLite 内存分配函数(例如 sqlite3_mprintf() 或 sqlite3_malloc())获取用于保存此错误消息字符串的空间。在将新值分配给 zErrMsg 之前,虚拟表实现必须使用 sqlite3_free() 释放 zErrMsg 中任何先前存在的内容。如果未执行此操作,会导致内存泄漏。SQLite 内核会在将错误消息文本传递给客户端应用程序或销毁虚拟表时释放和清空 zErrMsg 的内容。虚拟表实现仅需要在用新的不同错误消息覆盖内容时担心释放 zErrMsg 内容。
sqlite3_vtab_cursor 结构表示指向虚拟表特定行的指针。sqlite3_vtab_cursor 的外观如下
struct sqlite3_vtab_cursor { sqlite3_vtab *pVtab; };
同样,实际实现可能会对该结构进行子类化,以添加其他私有字段。
sqlite3_index_info 结构用于将信息传递到实现虚拟表的模块的 xBestIndex 方法中和从中传递信息。
在运行 CREATE VIRTUAL TABLE 语句之前,必须将该语句中指定的模块注册到数据库连接中。这可以使用以下任一接口来完成: sqlite3_create_module() 或 sqlite3_create_module_v2()
int sqlite3_create_module( sqlite3 *db, /* SQLite connection to register module with */ const char *zName, /* Name of the module */ const sqlite3_module *, /* Methods for the module */ void * /* Client data for xCreate/xConnect */ ); int sqlite3_create_module_v2( sqlite3 *db, /* SQLite connection to register module with */ const char *zName, /* Name of the module */ const sqlite3_module *, /* Methods for the module */ void *, /* Client data for xCreate/xConnect */ void(*xDestroy)(void*) /* Client data destructor function */ );
sqlite3_create_module() 和 sqlite3_create_module_v2() 例程将模块名称与一个 sqlite3_module 结构和一个单独的客户端数据相关联,该客户端数据特定于每个模块。这两个 create_module 方法之间的唯一区别是 _v2 方法包含一个额外的参数,用于指定客户端数据指针的析构函数。模块结构定义了虚拟表的行为。模块结构如下所示
struct sqlite3_module { int iVersion; int (*xCreate)(sqlite3*, void *pAux, int argc, char *const*argv, sqlite3_vtab **ppVTab, char **pzErr); int (*xConnect)(sqlite3*, void *pAux, int argc, char *const*argv, sqlite3_vtab **ppVTab, char **pzErr); int (*xBestIndex)(sqlite3_vtab *pVTab, sqlite3_index_info*); int (*xDisconnect)(sqlite3_vtab *pVTab); int (*xDestroy)(sqlite3_vtab *pVTab); int (*xOpen)(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor); int (*xClose)(sqlite3_vtab_cursor*); int (*xFilter)(sqlite3_vtab_cursor*, int idxNum, const char *idxStr, int argc, sqlite3_value **argv); int (*xNext)(sqlite3_vtab_cursor*); int (*xEof)(sqlite3_vtab_cursor*); int (*xColumn)(sqlite3_vtab_cursor*, sqlite3_context*, int); int (*xRowid)(sqlite3_vtab_cursor*, sqlite_int64 *pRowid); int (*xUpdate)(sqlite3_vtab *, int, sqlite3_value **, sqlite_int64 *); int (*xBegin)(sqlite3_vtab *pVTab); int (*xSync)(sqlite3_vtab *pVTab); int (*xCommit)(sqlite3_vtab *pVTab); int (*xRollback)(sqlite3_vtab *pVTab); int (*xFindFunction)(sqlite3_vtab *pVtab, int nArg, const char *zName, void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), void **ppArg); int (*xRename)(sqlite3_vtab *pVtab, const char *zNew); /* The methods above are in version 1 of the sqlite_module object. Those ** below are for version 2 and greater. */ int (*xSavepoint)(sqlite3_vtab *pVTab, int); int (*xRelease)(sqlite3_vtab *pVTab, int); int (*xRollbackTo)(sqlite3_vtab *pVTab, int); /* The methods above are in versions 1 and 2 of the sqlite_module object. ** Those below are for version 3 and greater. */ int (*xShadowName)(const char*); /* The methods above are in versions 1 through 3 of the sqlite_module object. ** Those below are for version 4 and greater. */ int (*xIntegrity)(sqlite3_vtab *pVTab, const char *zSchema, const char *zTabName, int mFlags, char **pzErr); };
模块结构定义了每个虚拟表对象的所有方法。模块结构还包含 iVersion 字段,该字段定义了模块表结构的特定版本。目前,iVersion 始终小于或等于 4,但在 SQLite 的未来版本中,模块结构定义可能会扩展,包含更多方法,在这种情况下,最大 iVersion 值将增加。
模块结构的其余部分由用于实现虚拟表各个功能的方法组成。关于每种方法的详细内容将在后续部分提供。
在 SQLite 3.6.17 版本(2009-08-10)之前,虚拟表机制假定每个 数据库连接 都会保存数据库模式的副本。因此,虚拟表机制不能在启用了 共享缓存模式 的数据库中使用。如果启用了 共享缓存模式,sqlite3_create_module() 接口将返回错误。从 SQLite 3.6.17 版本 开始,该限制被放宽。
请按照以下步骤创建您自己的虚拟表
最困难的部分实际上是步骤 1。您可能希望从现有的虚拟表实现开始,并对其进行修改以满足您的需求。 SQLite 源代码树 包含许多适合复制的虚拟表实现,包括
SQLite 源代码树中还有 许多其他虚拟表实现 可以用作示例。通过搜索“sqlite3_create_module”来定位这些其他虚拟表实现。
您可能还想将您的新虚拟表实现为 可加载扩展。
int (*xCreate)(sqlite3 *db, void *pAux, int argc, char *const*argv, sqlite3_vtab **ppVTab, char **pzErr);
xCreate 方法在响应 CREATE VIRTUAL TABLE 语句时被调用以创建虚拟表的新实例。如果 xCreate 方法与 xConnect 方法的指针相同,则虚拟表是 同名虚拟表。如果省略了 xCreate 方法(如果它是一个 NULL 指针),则虚拟表是 仅同名虚拟表。
db 参数是指向执行 CREATE VIRTUAL TABLE 语句的 SQLite 数据库连接 的指针。pAux 参数是客户端数据指针的副本,该指针是注册 虚拟表模块 的 sqlite3_create_module() 或 sqlite3_create_module_v2() 调用的第四个参数。argv 参数是一个包含 argc 个指向以 NULL 结尾的字符串的指针的数组。第一个字符串 argv[0] 是正在调用的模块的名称。模块名称是作为 sqlite3_create_module() 的第二个参数提供的名称,以及作为正在运行的 CREATE VIRTUAL TABLE 语句的 USING 子句的参数提供的名称。第二个 argv[1] 是正在创建新虚拟表的数据库的名称。数据库名称对于主数据库为“main”,对于 TEMP 数据库为“temp”,或者对于附加数据库而言是在 ATTACH 语句末尾给出的名称。数组的第三个元素 argv[2] 是新虚拟表的名称,如 CREATE VIRTUAL TABLE 语句中 TABLE 关键字之后的指定内容。如果存在,argv[] 数组中的第四个及后续字符串将报告 CREATE VIRTUAL TABLE 语句中模块名称的参数。
该方法的工作是构造新的虚拟表对象(一个 sqlite3_vtab 对象),并在 *ppVTab 中返回指向它的指针。
作为创建新的 sqlite3_vtab 结构的任务的一部分,此方法必须调用 sqlite3_declare_vtab() 以告知 SQLite 核心虚拟表中的列和数据类型。 sqlite3_declare_vtab() API 具有以下原型
int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable)
sqlite3_declare_vtab() 的第一个参数必须与此方法的第一个参数相同,即 数据库连接 指针。 sqlite3_declare_vtab() 的第二个参数必须是一个以零结尾的 UTF-8 字符串,其中包含格式良好的 CREATE TABLE 语句,该语句定义了虚拟表中的列及其数据类型。此 CREATE TABLE 语句中的表名以及所有约束都将被忽略。只有列名和数据类型很重要。CREATE TABLE 语句字符串不必保存在持久内存中。一旦 sqlite3_declare_vtab() 例程返回,该字符串就可以被释放和/或重新使用。
xConnect 方法还可以通过对 sqlite3_vtab_config() 接口进行一次或多次调用来可选地为虚拟表请求特殊功能
int sqlite3_vtab_config(sqlite3 *db, int op, ...);
对 sqlite3_vtab_config() 的调用是可选的。但是,为了最大限度地提高安全性,建议虚拟表实现调用“sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY )”如果虚拟表不会在触发器或视图内部使用。
xCreate 方法不必初始化 sqlite3_vtab 对象的 pModule、nRef 和 zErrMsg 字段。SQLite 核心将负责处理这些工作。
如果 xCreate 方法成功创建了新的虚拟表,则应返回 SQLITE_OK;如果未成功,则应返回 SQLITE_ERROR。如果不成功,则不得分配 sqlite3_vtab 结构。如果未成功,则可以可选地在 *pzErr 中返回错误消息。必须使用 SQLite 内存分配函数(如 sqlite3_malloc() 或 sqlite3_mprintf())来分配用于保存错误消息字符串的空间,因为 SQLite 核心将在错误报告到应用程序后尝试使用 sqlite3_free() 来释放该空间。
如果省略了 xCreate 方法(作为 NULL 指针),则虚拟表是 仅同名虚拟表。不能使用 CREATE VIRTUAL TABLE 来创建虚拟表的新实例,并且只能通过其模块名称来使用虚拟表。请注意,SQLite 3.9.0(2015-10-14)之前的版本不理解仅同名虚拟表,如果尝试在仅同名虚拟表上 CREATE VIRTUAL TABLE,则会导致段错误,因为未检查 xCreate 方法是否为 NULL。
如果 xCreate 方法与 xConnect 方法的指针完全相同,则表示虚拟表不需要初始化后备存储。这样的虚拟表可以作为 同名虚拟表 或使用 CREATE VIRTUAL TABLE 作为命名虚拟表,或者两者兼而有之。
如果列数据类型包含特殊关键字“HIDDEN”(以任何大小写字母组合出现),则该关键字将从列数据类型名称中省略,并且该列将在内部被标记为隐藏列。隐藏列在三个方面与普通列不同
例如,如果将以下 SQL 传递给 sqlite3_declare_vtab()
CREATE TABLE x(a HIDDEN VARCHAR(12), b INTEGER, c INTEGER Hidden);
则将创建具有两个隐藏列的虚拟表,其数据类型分别为“VARCHAR(12)”和“INTEGER”。
可以在 FTS3 虚拟表实现中看到隐藏列的示例用法,其中每个 FTS 虚拟表都包含一个 FTS 隐藏列,用于将信息从虚拟表传递到 FTS 辅助函数 以及 FTS MATCH 运算符。
包含 隐藏列 的 虚拟表 可以像表值函数一样在 SELECT 语句的 FROM 子句中使用。表值函数的参数将成为虚拟表隐藏列上的约束。
例如,“generate_series”扩展(位于 ext/misc/series.c 文件中的 源代码树 中)实现了一个具有以下模式的 同名虚拟表
CREATE TABLE generate_series( value, start HIDDEN, stop HIDDEN, step HIDDEN );
此表实现中的 sqlite3_module.xBestIndex 方法检查 HIDDEN 列的相等约束,并将其用作输入参数来确定要生成的整数“value”输出范围。对于任何未约束的列,都将使用合理的默认值。例如,要列出 5 到 50 之间的所有整数
SELECT value FROM generate_series(5,50);
前面的查询等效于以下内容
SELECT value FROM generate_series WHERE start=5 AND stop=50;
虚拟表名称上的参数将按顺序与 隐藏列 匹配。参数的数量可以小于隐藏列的数量,在这种情况下,后面的隐藏列将不受约束。但是,如果参数数量超过虚拟表中的隐藏列数量,则会产生错误。
从 SQLite 3.14.0 版本(2016-08-08)开始,传递到 sqlite3_declare_vtab() 的 CREATE TABLE 语句可以包含 WITHOUT ROWID 子句。这对于虚拟表行难以轻松映射到唯一整数的情况很有用。包含 WITHOUT ROWID 的 CREATE TABLE 语句必须将一个或多个列定义为 PRIMARY KEY。PRIMARY KEY 的每个列都必须单独是 NOT NULL,并且每行的所有列都必须在整体上是唯一的。
请注意,SQLite 不会对 WITHOUT ROWID 虚拟表强制执行 PRIMARY KEY。强制执行由底层虚拟表实现负责。但是,SQLite 假定 PRIMARY KEY 约束有效 - 标识的列确实是 UNIQUE 和 NOT NULL - 并使用该假设来优化针对虚拟表的查询。
在 WITHOUT ROWID 虚拟表(当然)上无法访问 rowid 列。
The xUpdate 方法最初是围绕将 ROWID 作为单个值来设计的。 xUpdate 方法已扩展以适应任意 PRIMARY KEY 代替 ROWID,但 PRIMARY KEY 仍然必须只有一列。 因此,SQLite 会拒绝任何具有多于一列 PRIMARY KEY 且非空 xUpdate 方法的 WITHOUT ROWID 虚拟表。
int (*xConnect)(sqlite3*, void *pAux, int argc, char *const*argv, sqlite3_vtab **ppVTab, char **pzErr);
xConnect 方法与 xCreate 非常相似。 它具有相同的参数并构造一个新的 sqlite3_vtab 结构,就像 xCreate 一样。 它还必须像 xCreate 一样调用 sqlite3_declare_vtab()。 它也应该进行与 xCreate 相同的所有 sqlite3_vtab_config() 调用。
区别在于,xConnect 用于建立与现有虚拟表的连接,而 xCreate 用于从头开始创建新的虚拟表。
只有当虚拟表具有某种必须在首次创建时初始化的存储后端时,xCreate 和 xConnect 方法才不同。 xCreate 方法创建并初始化存储后端。 xConnect 方法只连接到现有的存储后端。 当 xCreate 和 xConnect 相同的时候,表是 同名虚拟表。
例如,考虑一个虚拟表实现,它提供对磁盘上现有的逗号分隔值 (CSV) 文件的只读访问。 这种虚拟表不需要创建或初始化存储后端(因为 CSV 文件已经存在于磁盘上),因此 xCreate 和 xConnect 方法对于该模块将是相同的。
另一个例子是实现全文索引的虚拟表。 xCreate 方法必须创建和初始化数据结构来保存该索引的字典和发布列表。 另一方面,xConnect 方法只需要找到和使用由先前 xCreate 调用创建的现有字典和发布列表。
如果 xConnect 方法成功创建了新的虚拟表,则它必须返回 SQLITE_OK,否则返回 SQLITE_ERROR。 如果不成功,则不得分配 sqlite3_vtab 结构。 如果不成功,可以选择在 *pzErr 中返回错误消息。 使用 SQLite 内存分配函数(如 sqlite3_malloc() 或 sqlite3_mprintf())分配用于保存错误消息字符串的空间,因为 SQLite 核心将尝试使用 sqlite3_free() 在错误报告到应用程序后释放该空间。
每个虚拟表实现都需要 xConnect 方法,尽管 sqlite3_module 对象的 xCreate 和 xConnect 指针可以指向同一个函数,如果虚拟表不需要初始化存储后端。
SQLite 使用虚拟表模块的 xBestIndex 方法来确定访问虚拟表的最佳方式。 xBestIndex 方法的原型如下
int (*xBestIndex)(sqlite3_vtab *pVTab, sqlite3_index_info*);
SQLite 核心通过填充 sqlite3_index_info 结构的某些字段并向 xBestIndex 传递指向该结构的指针作为第二个参数来与 xBestIndex 方法进行通信。 xBestIndex 方法填充该结构的其他字段,这些字段构成回复。 sqlite3_index_info 结构如下所示
struct sqlite3_index_info { /* Inputs */ const int nConstraint; /* Number of entries in aConstraint */ const struct sqlite3_index_constraint { int iColumn; /* Column constrained. -1 for ROWID */ unsigned char op; /* Constraint operator */ unsigned char usable; /* True if this constraint is usable */ int iTermOffset; /* Used internally - xBestIndex should ignore */ } *const aConstraint; /* Table of WHERE clause constraints */ const int nOrderBy; /* Number of terms in the ORDER BY clause */ const struct sqlite3_index_orderby { int iColumn; /* Column number */ unsigned char desc; /* True for DESC. False for ASC. */ } *const aOrderBy; /* The ORDER BY clause */ /* Outputs */ struct sqlite3_index_constraint_usage { int argvIndex; /* if >0, constraint is part of argv to xFilter */ unsigned char omit; /* Do not code a test for this constraint */ } *const aConstraintUsage; int idxNum; /* Number used to identify the index */ char *idxStr; /* String, possibly obtained from sqlite3_malloc */ int needToFreeIdxStr; /* Free idxStr using sqlite3_free() if true */ int orderByConsumed; /* True if output is already ordered */ double estimatedCost; /* Estimated cost of using this index */ /* Fields below are only available in SQLite 3.8.2 and later */ sqlite3_int64 estimatedRows; /* Estimated number of rows returned */ /* Fields below are only available in SQLite 3.9.0 and later */ int idxFlags; /* Mask of SQLITE_INDEX_SCAN_* flags */ /* Fields below are only available in SQLite 3.10.0 and later */ sqlite3_uint64 colUsed; /* Input: Mask of columns used by statement */ };
请注意 "estimatedRows"、"idxFlags" 和 colUsed 字段上的警告。 这些字段分别在 SQLite 版本 3.8.2、3.9.0 和 3.10.0 中添加。 任何读取或写入这些字段的扩展必须首先检查所使用 SQLite 库的版本是否大于或等于适当的版本 - 可能将从 sqlite3_libversion_number() 返回的值与常量 3008002、3009000 和/或 3010000 进行比较。 尝试在由旧版 SQLite 创建的 sqlite3_index_info 结构中访问这些字段的结果是未定义的。
此外,还有一些定义的常量
#define SQLITE_INDEX_CONSTRAINT_EQ 2 #define SQLITE_INDEX_CONSTRAINT_GT 4 #define SQLITE_INDEX_CONSTRAINT_LE 8 #define SQLITE_INDEX_CONSTRAINT_LT 16 #define SQLITE_INDEX_CONSTRAINT_GE 32 #define SQLITE_INDEX_CONSTRAINT_MATCH 64 #define SQLITE_INDEX_CONSTRAINT_LIKE 65 /* 3.10.0 and later */ #define SQLITE_INDEX_CONSTRAINT_GLOB 66 /* 3.10.0 and later */ #define SQLITE_INDEX_CONSTRAINT_REGEXP 67 /* 3.10.0 and later */ #define SQLITE_INDEX_CONSTRAINT_NE 68 /* 3.21.0 and later */ #define SQLITE_INDEX_CONSTRAINT_ISNOT 69 /* 3.21.0 and later */ #define SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70 /* 3.21.0 and later */ #define SQLITE_INDEX_CONSTRAINT_ISNULL 71 /* 3.21.0 and later */ #define SQLITE_INDEX_CONSTRAINT_IS 72 /* 3.21.0 and later */ #define SQLITE_INDEX_CONSTRAINT_LIMIT 73 /* 3.38.0 and later */ #define SQLITE_INDEX_CONSTRAINT_OFFSET 74 /* 3.38.0 and later */ #define SQLITE_INDEX_CONSTRAINT_FUNCTION 150 /* 3.25.0 and later */ #define SQLITE_INDEX_SCAN_UNIQUE 1 /* Scan visits at most 1 row */
使用 sqlite3_vtab_collation() 接口查找用于评估第 i 个约束时的 排序规则 的名称
const char *sqlite3_vtab_collation(sqlite3_index_info*, int i);
当 SQLite 核心编译包含虚拟表的查询时,它会调用 xBestIndex 方法。 换句话说,当 SQLite 核心运行 sqlite3_prepare() 或等效函数时,它会调用该方法。 通过调用该方法,SQLite 核心告诉虚拟表它需要访问虚拟表中的一些行子集,并且它想知道执行该访问的最有效方式。 xBestIndex 方法会回复一些信息,SQLite 核心可以使用这些信息来高效地搜索虚拟表。
在编译单个 SQL 查询时,SQLite 核心可能会在 sqlite3_index_info 中使用不同的设置多次调用 xBestIndex。 SQLite 核心随后将选择看起来性能最佳的组合。
在调用该方法之前,SQLite 核心会初始化一个 sqlite3_index_info 结构的实例,其中包含有关它当前尝试处理的查询的信息。 此信息主要来自查询的 WHERE 子句以及 ORDER BY 或 GROUP BY 子句,但也来自任何 ON 或 USING 子句(如果查询是联接)。 SQLite 核心提供给 xBestIndex 方法的信息存储在标记为 "输入" 的结构部分中。 "输出" 部分初始化为零。
sqlite3_index_info 结构中的信息是临时的,可能会在 xBestIndex 方法返回后立即被覆盖或释放。 如果 xBestIndex 方法需要记住 sqlite3_index_info 结构的任何部分,则它应该制作副本。 必须注意将副本存储在它将被释放的地方,例如在 idxStr 字段中,其中 needToFreeIdxStr 设置为 1。
请注意,xBestIndex 将始终在 xFilter 之前被调用,因为 xBestIndex 的 idxNum 和 idxStr 输出是 xFilter 的必需输入。 但是,无法保证 xFilter 会在 xBestIndex 成功后被调用。
每个虚拟表实现都需要 xBestIndex 方法。
SQLite 核心试图传达给虚拟表的主要内容是可用于限制需要搜索的行数的约束条件。 aConstraint[] 数组包含每个约束条件的一个条目。 该数组中将恰好有 nConstraint 个条目。
每个约束通常对应于 WHERE 子句或 USING 或 ON 子句中的一个项,该项的形式如下
column OP EXPR
其中 "column" 是虚拟表中的一个列,OP 是一个运算符,如 "=" 或 "<",而 EXPR 是一个任意的表达式。 例如,如果 WHERE 子句包含以下内容
a = 5
那么其中一个约束条件将作用于 "a" 列,运算符为 "=",表达式为 "5"。 约束条件不需要具有 WHERE 子句的文字表示形式。 查询优化器可能会对 WHERE 子句进行转换,以便提取尽可能多的约束条件。 例如,如果 WHERE 子句包含以下内容
x BETWEEN 10 AND 100 AND 999>y
查询优化器可能会将其转换为三个独立的约束条件
x >= 10 x <= 100 y < 999
对于每个这样的约束条件,aConstraint[].iColumn 字段表示约束条件左侧出现的列。 虚拟表的第一个列是列 0。 虚拟表的 rowid 是列 -1。 aConstraint[].op 字段表示使用的运算符。 SQLITE_INDEX_CONSTRAINT_* 常量将整型常量映射到运算符值。 列按照它们在 xCreate 或 xConnect 方法中对 sqlite3_declare_vtab() 的调用中定义的顺序出现。 隐藏列在确定列索引时会被计入。
如果定义了虚拟表的 xFindFunction() 方法,并且如果 xFindFunction() 有时返回 SQLITE_INDEX_CONSTRAINT_FUNCTION 或更大的值,那么约束条件也可能是以下形式
FUNCTION( column, EXPR)
在这种情况下,aConstraint[].op 值与 xFindFunction() 为 FUNCTION 返回的值相同。
aConstraint[] 数组包含有关应用于虚拟表的所有约束条件的信息。 但是,由于表在联接中的排序方式,某些约束条件可能不可用。 因此,xBestIndex 方法只需要考虑 aConstraint[].usable 标志为 true 的约束条件。
除了 WHERE 子句约束条件之外,SQLite 核心还会将有关 ORDER BY 子句的信息告诉 xBestIndex 方法。 (在聚合查询中,SQLite 核心可能会将 GROUP BY 子句信息放在 ORDER BY 子句信息的位置,但这对 xBestIndex 方法没有影响。) 如果 ORDER BY 子句的所有项都是虚拟表中的列,则 nOrderBy 将是 ORDER BY 子句中的项数,而 aOrderBy[] 数组将标识每个项的列以及该列是 ASC 还是 DESC。
在 SQLite 版本 3.10.0(2016 年 1 月 6 日)及更高版本中,colUsed 字段可用于指示实际由正在准备的语句使用的虚拟表字段。 如果 colUsed 的最低位被设置,则表示使用第一列。 第二低位对应于第二列。 等等。 如果 colUsed 的最高位被设置,则表示使用除前 63 列之外的一列或多列。 如果 xFilter 方法需要列使用信息,则必须将所需位编码到输出 idxNum 字段或 idxStr 内容中。
对于 LIKE、GLOB、REGEXP 和 MATCH 运算符,aConstraint[].iColumn 值是作为运算符左操作数的虚拟表列。 但是,如果这些运算符用函数调用而不是运算符来表示,则 aConstraint[].iColumn 值引用作为该函数第二个参数的虚拟表列
LIKE(EXPR, column)
GLOB(EXPR, column)
REGEXP(EXPR, column)
MATCH(EXPR, column)
因此,就 xBestIndex() 方法而言,以下两种形式是等效的
column LIKE EXPR
LIKE(EXPR,column)
这种查看函数的第二个参数的特殊行为只发生在 LIKE、GLOB、REGEXP 和 MATCH 函数中。 对于所有其他函数,aConstraint[].iColumn 值引用函数的第一个参数。
但是,LIKE、GLOB、REGEXP 和 MATCH 的此特殊功能不适用于 xFindFunction() 方法。 xFindFunction() 方法始终以 LIKE、GLOB、REGEXP 或 MATCH 运算符的左操作数为键,但以这些运算符的函数调用等效项的第一个参数为键。
当 aConstraint[].op 是 SQLITE_INDEX_CONSTRAINT_LIMIT 或 SQLITE_INDEX_CONSTRAINT_OFFSET 之一时,表示使用虚拟表的 SQL 查询语句上存在 LIMIT 或 OFFSET 子句。 LIMIT 和 OFFSET 运算符没有左操作数,因此当 aConstraint[].op 是 SQLITE_INDEX_CONSTRAINT_LIMIT 或 SQLITE_INDEX_CONSTRAINT_OFFSET 之一时,aConstraint[].iColumn 值毫无意义,不应使用。
可以使用 sqlite3_vtab_rhs_value() 接口尝试访问约束的右操作数。但是,右操作数的值可能在运行 xBestIndex 方法时未知,因此 sqlite3_vtab_rhs_value() 调用可能不会成功。通常情况下,约束的右操作数只有在输入 SQL 中以字面值编码时才对 xBestIndex 可用。如果右操作数以表达式或 主机参数 编码,则可能无法被 xBestIndex 访问。一些运算符,例如 SQLITE_INDEX_CONSTRAINT_ISNULL 和 SQLITE_INDEX_CONSTRAINT_ISNOTNULL 没有右操作数。sqlite3_vtab_rhs_value() 接口对于此类运算符始终返回 SQLITE_NOTFOUND。
综上所述,xBestIndex 方法的任务是找出搜索虚拟表最佳方式。
xBestIndex 方法通过 idxNum 和 idxStr 字段向 xFilter 方法传递索引策略。就 SQLite 核心而言,idxNum 值和 idxStr 字符串内容是任意的,只要 xBestIndex 和 xFilter 对其含义达成一致,就可以具有任何含义。SQLite 核心仅将信息从 xBestIndex 复制到 xFilter 方法,假设通过 idxStr 引用的字符序列以 NUL 结尾。
idxStr 值可以是使用 SQLite 内存分配函数(如 sqlite3_mprintf())获得的字符串。如果是这种情况,则 needToFreeIdxStr 标志必须设置为 true,以便 SQLite 核心知道在完成该字符串后调用 sqlite3_free(),从而避免内存泄漏。idxStr 值也可以是静态常量字符串,在这种情况下,needToFreeIdxStr 布尔值应保持为 false。
estimatedCost 字段应设置为执行此查询以针对虚拟表的估计磁盘访问操作数。SQLite 核心通常会使用不同的约束多次调用 xBestIndex,获取多个成本估算,然后选择给出最低估算的查询计划。SQLite 核心在调用 xBestIndex 之前将 estimatedCost 初始化为一个非常大的值,因此如果 xBestIndex 确定当前参数组合不可取,则可以保持 estimatedCost 字段不变,以阻止使用它。
如果当前版本的 SQLite 是 3.8.2 或更高版本,则 estimatedRows 字段可以设置为对由建议的查询计划返回的行数的估算。如果没有显式设置此值,则使用默认估算值 25 行。
如果当前版本的 SQLite 是 3.9.0 或更高版本,则 idxFlags 字段可以设置为 SQLITE_INDEX_SCAN_UNIQUE,以指示虚拟表仅在给定输入约束的情况下返回零行或一行。SQLite 后续版本可能会理解 idxFlags 字段的附加位。
aConstraintUsage[] 数组包含 sqlite3_index_info 结构输入部分中的每个 nConstraint 约束的元素。xBestIndex 使用 aConstraintUsage[] 数组来告诉核心它如何使用这些约束。
xBestIndex 方法可以将 aConstraintUsage[].argvIndex 条目设置为大于零的值。应该恰好将一个条目设置为 1,另一个设置为 2,另一个设置为 3,依此类推,直到 xBestIndex 方法想要的尽可能多或尽可能少。然后,对应约束的 EXPR 将作为 argv[] 参数传递给 xFilter。
例如,如果 aConstraint[3].argvIndex 设置为 1,则在调用 xFilter 时,传递给 xFilter 的 argv[0] 将具有 aConstraint[3] 约束的 EXPR 值。
默认情况下,SQLite 会生成 字节码,该字节码会对虚拟表的每一行进行双重检查以验证所有约束是否满足。如果虚拟表可以保证约束始终满足,则它可以通过设置 aConstraintUsage[].omit 来尝试抑制该双重检查。但是,除了某些例外情况外,这只是一个提示,不能保证会抑制对约束的冗余检查。要点
仅当约束的 argvIndex 值大于 0 且小于或等于 16 时,才会考虑 omit 标志。对于没有将右操作数传递给 xFilter 方法的约束,永远不会抑制约束检查。当前实现只能抑制传递给 xFilter 的前 16 个值的冗余约束检查,尽管此限制可能会在将来的版本中增加。
对于 SQLITE_INDEX_CONSTRAINT_OFFSET 约束,只要 argvIndex 大于 0,就会始终考虑 omit 标志。在 SQLITE_INDEX_CONSTRAINT_OFFSET 约束上设置 omit 标志表示告诉 SQLite 虚拟表将自行抑制前 N 行输出,其中 N 是 OFFSET 运算符的右操作数。如果虚拟表实现对 SQLITE_INDEX_CONSTRAINT_OFFSET 约束设置了 omit,但随后未能抑制前 N 行输出,则整个查询将导致错误的答案。
如果虚拟表将按 ORDER BY 子句指定的顺序输出行,则可以将 orderByConsumed 标志设置为 true。如果输出不是自动按正确顺序排序,则 orderByConsumed 必须保留在默认的 false 设置中。这将指示 SQLite 核心需要在数据从虚拟表输出后对其进行单独的排序传递。设置 orderByConsumed 是一个优化。如果将 orderByConsumed 保留在其默认值 (0) 上,则查询始终会获得正确的结果。如果设置了 orderByConsumed,则可能会避免不必要的排序操作,从而使查询更快,但错误设置 orderByConsumed 会导致错误的结果。建议新的虚拟表实现最初保留 orderByConsumed 值未设置,然后在确认其他一切正常后,再尝试通过在适当的地方设置 orderByConsumed 来进行优化。
有时,即使虚拟表的输出不是严格按 nOrderBy 和 aOrderBy 指定的顺序排序,也可以安全地设置 orderByConsumed 标志。如果 sqlite3_vtab_distinct() 接口返回 1 或 2,则表示可以放宽排序。有关更多信息,请参阅 sqlite3_vtab_distinct() 的文档。
xBestIndex 方法在成功时应返回 SQLITE_OK。如果发生任何类型的致命错误,应返回适当的错误代码(例如:SQLITE_NOMEM)。
如果 xBestIndex 返回 SQLITE_CONSTRAINT,则不表示错误。相反,SQLITE_CONSTRAINT 表示指定的输入参数组合不足以让虚拟表完成其工作。从逻辑上讲,这与将 estimatedCost 设置为无穷大相同。如果对特定查询计划的每次 xBestIndex 调用都返回 SQLITE_CONSTRAINT,则表示虚拟表无法安全使用,并且 sqlite3_prepare() 调用将因“无查询解决方案”错误而失败。
xBestIndex 中返回的 SQLITE_CONSTRAINT 对于具有必需参数的 表值函数 很有用。如果 aConstraint[].usable 字段对于其中一个必需参数为 false,则 xBestIndex 方法应返回 SQLITE_CONSTRAINT。如果必需字段根本没有出现在 aConstraint[] 数组中,则表示相应的参数已从输入 SQL 中省略。在这种情况下,xBestIndex 应在 pVTab->zErrMsg 中设置错误消息并返回 SQLITE_ERROR。总而言之
必需参数的 aConstraint[].usable 值为 false→返回 SQLITE_CONSTRAINT。
必需参数没有出现在 aConstraint[] 数组中的任何位置→在 pVTab->zErrMsg 中设置错误消息并返回 SQLITE_ERROR
以下示例将更好地说明 SQLITE_CONSTRAINT 作为 xBestIndex 返回值的使用
SELECT * FROM realtab, tablevaluedfunc(realtab.x);
假设“tablevaluedfunc”的第一个隐藏列是“param1”,则上面的查询在语义上等效于以下查询
SELECT * FROM realtab, tablevaluedfunc WHERE tablevaluedfunc.param1 = realtab.x;
查询规划器必须在许多可能的查询实现之间进行选择,但有两个计划值得注意
扫描 realtab 的所有行,对于每一行,查找 param1 等于 realtab.x 的 tablevaluedfunc 中的行
扫描 tablevalued func 的所有行,对于每一行,查找 x 等于 tablevaluedfunc.param1 的 realtab 中的行
将对每个潜在计划调用一次 xBestIndex 方法。对于计划 1,param1 列上 SQLITE_CONSTRAINT_EQ 约束的 aConstraint[].usable 标志将为 true,因为“param1 = ?”约束的右操作数是已知的,因为它由外部 realtab 循环确定。但对于计划 2,“param1 = ?”的 aConstraint[].usable 标志将为 false,因为右操作数由内部循环确定,因此是一个未知量。由于 param1 是表值函数的必需输入,因此 xBestIndex 方法在遇到计划 2 时应返回 SQLITE_CONSTRAINT,指示缺少必需的输入。这将强制查询规划器选择计划 1。
int (*xDisconnect)(sqlite3_vtab *pVTab);
此方法释放对虚拟表的连接。仅销毁 sqlite3_vtab 对象。不会销毁虚拟表,并且与虚拟表关联的任何后备存储都会保留。此方法撤销 xConnect 的操作。
此方法是用于连接到虚拟表的析构函数。将此方法与 xDestroy 进行对比。xDestroy 是针对整个虚拟表的析构函数。
每个虚拟表实现都需要 xDisconnect 方法,尽管如果这对特定虚拟表有意义,则 xDisconnect 和 xDestroy 方法可以是同一个函数。
int (*xDestroy)(sqlite3_vtab *pVTab);
此方法与 xDisconnect 方法一样释放对虚拟表的连接,并且还会销毁底层表实现。此方法撤销 xCreate 的操作。
每当使用虚拟表的数据库连接关闭时,都会调用 xDisconnect 方法。xDestroy 方法仅在针对虚拟表执行 DROP TABLE 语句时调用。
每个虚拟表实现都需要 xDestroy 方法,尽管如果这对特定虚拟表有意义,则 xDisconnect 和 xDestroy 方法可以是同一个函数。
int (*xOpen)(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor);
xOpen 方法创建用于访问(读写)虚拟表的新游标。成功调用此方法将分配 sqlite3_vtab_cursor(或子类)的内存,初始化新对象,并使 *ppCursor 指向新对象。然后,成功调用将返回 SQLITE_OK。
对于对该方法的每次成功调用,SQLite 核心稍后将调用 xClose 方法来销毁分配的游标。
xOpen 方法不需要初始化 sqlite3_vtab_cursor 结构的 pVtab 字段。SQLite 核心将自动完成此任务。
虚拟表实现必须能够支持任意数量的并发打开游标。
当最初打开时,游标处于未定义状态。SQLite 核心将在尝试定位或从游标读取之前调用游标上的 xFilter 方法。
int (*xClose)(sqlite3_vtab_cursor*);
xClose 方法关闭先前由 xOpen 打开的游标。SQLite 核心始终会对使用 xOpen 打开的每个游标调用一次 xClose。
此方法必须释放与对应 xOpen 调用分配的所有资源。即使返回错误,此例程也不会再次被调用。SQLite 核心在关闭游标后不会再使用 sqlite3_vtab_cursor。
int (*xEof)(sqlite3_vtab_cursor*);
如果指定游标当前指向有效的行数据,则 xEof 方法必须返回 false(零),否则返回 true(非零)。此方法在每次 xFilter 和 xNext 调用后立即被 SQL 引擎调用。
int (*xFilter)(sqlite3_vtab_cursor*, int idxNum, const char *idxStr, int argc, sqlite3_value **argv);
此方法开始对虚拟表的搜索。第一个参数是由 xOpen 打开的游标。接下来的两个参数定义了先前由 xBestIndex 选择的特定搜索索引。只要 xFilter 和 xBestIndex 同意含义,idxNum 和 idxStr 的具体含义就不重要。
xBestIndex 函数可能已使用 sqlite3_index_info 结构的 aConstraintUsage[].argvIndex 值请求某些表达式的值。这些值使用 argc 和 argv 参数传递给 xFilter。
如果虚拟表包含匹配搜索条件的一行或多行,则游标必须指向第一行。对 xEof 的后续调用必须返回 false(零)。如果没有匹配的行,则游标必须处于会导致 xEof 返回 true(非零)的状态。SQLite 引擎将使用 xColumn 和 xRowid 方法访问该行内容。 xNext 方法将用于前进到下一行。
如果成功,此方法必须返回 SQLITE_OK,如果发生错误,则返回 sqlite 错误代码。
int (*xNext)(sqlite3_vtab_cursor*);
xNext 方法将 虚拟表游标 前进到由 xFilter 启动的结果集的下一行。如果在调用此例程时游标已指向最后一行,则游标不再指向有效数据,对 xEof 方法的后续调用必须返回 true(非零)。如果游标成功前进到另一个内容行,则对 xEof 的后续调用必须返回 false(零)。
如果成功,此方法必须返回 SQLITE_OK,如果发生错误,则返回 sqlite 错误代码。
int (*xColumn)(sqlite3_vtab_cursor*, sqlite3_context*, int N);
SQLite 核心调用此方法是为了查找当前行的第 N 列的值。N 是从零开始的,因此第一列编号为 0。xColumn 方法可以使用以下接口之一将结果返回给 SQLite
如果 xColumn 方法实现没有调用以上任何函数,则该列的值默认为 SQL NULL。
要引发错误,xColumn 方法应该使用 result_text() 方法之一设置错误消息文本,然后返回适当的 错误代码。xColumn 方法在成功时必须返回 SQLITE_OK。
int (*xRowid)(sqlite3_vtab_cursor *pCur, sqlite_int64 *pRowid);
成功调用此方法将导致 *pRowid 被 虚拟表游标 pCur 当前指向的行 rowid 填充。此方法在成功时返回 SQLITE_OK。在失败时,它返回适当的 错误代码。
int (*xUpdate)( sqlite3_vtab *pVTab, int argc, sqlite3_value **argv, sqlite_int64 *pRowid );
对虚拟表的所有更改都使用 xUpdate 方法完成。此方法可用于插入、删除或更新。
argc 参数指定 argv 数组中的条目数。对于纯删除操作,argc 的值为 1;对于插入、替换或更新,argc 的值为 N+2,其中 N 是表中列的数目。在上一句话中,N 包括所有隐藏列。
每个 argv 条目在 C 中都具有非 NULL 值,但可能包含 SQL 值 NULL。换句话说,始终为真argv[i]!=0对于 i 在 0 和argc-1之间。但是,可能是这种情况sqlite3_value_type(argv[i])==SQLITE_NULL.
argv[0] 参数是要从虚拟表中删除的行的 rowid。如果 argv[0] 是 SQL NULL,则不会发生删除。
argv[1] 参数是将要插入虚拟表的行的 rowid。如果 argv[1] 是 SQL NULL,则实现必须为新插入的行选择 rowid。后续 argv[] 条目包含虚拟表列的值,顺序与在使用 sqlite3_declare_vtab() 调用进行的 xConnect 或 xCreate 方法中声明列的顺序相同。所有隐藏列都包括在内。
在对没有 rowid 的虚拟表进行插入(argc>1,argv[1] 是 SQL NULL)时,在使用 ROWID 的虚拟表上(但不在 WITHOUT ROWID 虚拟表 上),实现必须将 *pRowid 设置为新插入行的 rowid;这将成为 sqlite3_last_insert_rowid() 函数返回的值。在所有其他情况下设置此值是无害的无操作;如果 argc==1 或 argv[1] 不是 SQL NULL,SQLite 引擎将忽略 *pRowid 返回值。
对 xUpdate 的每次调用都将属于下面显示的情况之一。请注意,对 argv[i] 的引用是指 argv[i] 对象中持有的 SQL 值,而不是 argv[i] 对象本身。
- argc = 1
argv[0] ≠ NULLDELETE:删除具有与 argv[0] 相同 rowid 或 PRIMARY KEY 的单行。不会发生插入。
- argc > 1
argv[0] = NULLINSERT:插入一个新行,列值取自 argv[2] 及其后的值。在 rowid 虚拟表中,如果 argv[1] 是 SQL NULL,则会自动生成一个新的唯一 rowid。对于 WITHOUT ROWID 虚拟表,argv[1] 将为 NULL,在这种情况下,实现应从 argv[2] 及其后的适当列中获取 PRIMARY KEY 值。
- argc > 1
argv[0] ≠ NULL
argv[0] = argv[1]UPDATE:使用 argv[2] 及其后的参数中的新值更新具有 rowid 或 PRIMARY KEY argv[0] 的行。
- argc > 1
argv[0] ≠ NULL
argv[0] ≠ argv[1]UPDATE 具有 rowid 或 PRIMARY KEY 更改:使用 argv[1] 中的 rowid 或 PRIMARY KEY 以及 argv[2] 及其后的参数中的新值更新具有 rowid 或 PRIMARY KEY argv[0] 的行。当 SQL 语句更新 rowid 时,这将发生,例如在以下语句中
UPDATE table SET rowid=rowid+1 WHERE ...;
如果且仅当 xUpdate 方法成功时,它必须返回 SQLITE_OK。如果发生故障,xUpdate 必须返回适当的 错误代码。在发生故障时,pVTab->zErrMsg 元素可以选择用使用 sqlite3_mprintf() 或 sqlite3_malloc() 等函数从 SQLite 分配的内存存储的错误消息文本替换。
如果 xUpdate 方法违反了虚拟表的某些约束(包括但不限于尝试存储错误数据类型的值、尝试存储过大或过小的值、或尝试更改只读值),则 xUpdate 必须返回适当的 错误代码。
如果 xUpdate 方法正在执行 UPDATE,则可以使用 sqlite3_value_nochange(X) 来发现虚拟表的哪些列实际上是由 UPDATE 语句修改的。 sqlite3_value_nochange(X) 接口对未更改的列返回 true。在每次 UPDATE 中,SQLite 首先会分别调用 xColumn 来获取表中每个未更改列的值。 xColumn 方法可以通过调用 sqlite3_vtab_nochange() 来检查该列是否在 SQL 级别上未被修改。如果 xColumn 发现该列没有被修改,它应该在不使用 sqlite3_result_xxxxx() 接口之一设置结果的情况下返回。只有在这种情况下,sqlite3_value_nochange() 才会在 xUpdate 方法中返回 true。如果 xColumn 确实调用了一个或多个 sqlite3_result_xxxxx() 接口,则 SQLite 会将其理解为该列的值发生更改,并且 sqlite3_value_nochange() 对 xUpdate 中该列的调用将返回 false。
可能有一个或多个 sqlite3_vtab_cursor 对象在虚拟表实例上甚至在调用 xUpdate 方法时在虚拟表的行上打开并正在使用。xUpdate 的实现必须准备好应对尝试从其他现有游标中删除或修改表的行。如果虚拟表无法容纳此类更改,则 xUpdate 方法必须返回一个 错误代码。
xUpdate 方法是可选的。如果 sqlite3_module 中的 xUpdate 指针为 NULL 指针,则虚拟表为只读。
int (*xFindFunction)( sqlite3_vtab *pVtab, int nArg, const char *zName, void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), void **ppArg );
此方法在 sqlite3_prepare() 期间被调用,以使虚拟表实现有机会重载函数。此方法可以设置为 NULL,在这种情况下不会发生重载。
当一个函数使用虚拟表中的列作为其第一个参数时,会调用此方法来查看虚拟表是否希望重载该函数。前三个参数是输入:虚拟表、函数的参数数目以及函数的名称。如果不需要重载,此方法将返回 0。要重载函数,此方法会将新的函数实现写入 *pxFunc 并将用户数据写入 *ppArg,然后返回 1 或 SQLITE_INDEX_CONSTRAINT_FUNCTION 到 255 之间的数字。
历史上,xFindFunction() 的返回值要么是零,要么是一。零表示该函数没有重载,一表示该函数被重载。从 3.25.0 版本(2018 年 9 月 15 日)开始,添加了返回大于或等于 SQLITE_INDEX_CONSTRAINT_FUNCTION 的值的能力。如果 xFindFunction 返回大于或等于 SQLITE_INDEX_CONSTRAINT_FUNCTION 的值,则意味着该函数接受两个参数,该函数可以在查询的 WHERE 子句中用作布尔值,并且虚拟表能够利用该函数来加速查询结果。当 xFindFunction 返回大于或等于 SQLITE_INDEX_CONSTRAINT_FUNCTION 的值时,返回的值将成为 sqlite3_index_info.aConstraint.op 的值,用于传递到 xBestIndex() 的约束之一。函数的第一个参数是约束的 aConstraint[].iColumn 字段标识的列,函数的第二个参数是将传递到 xFilter() 的值(如果 aConstraintUsage[].argvIndex 值已设置)或从 sqlite3_vtab_rhs_value() 返回的值。
Geopoly 模块 是一个虚拟表的示例,它利用 SQLITE_INDEX_CONSTRAINT_FUNCTION 来提高性能。Geopoly 的 xFindFunction() 方法对 geopoly_overlap() SQL 函数返回 SQLITE_INDEX_CONSTRAINT_FUNCTION,对 geopoly_within() SQL 函数返回 SQLITE_INDEX_CONSTRAINT_FUNCTION+1。这允许对以下查询进行搜索优化:
SELECT * FROM geopolytab WHERE geopoly_overlap(_shape, $query_polygon); SELECT * FROM geopolytab WHERE geopoly_within(_shape, $query_polygon);
请注意,中缀函数(LIKE、GLOB、REGEXP 和 MATCH)反转其参数的顺序。因此,“like(A,B)” 通常与 “B like A” 的工作方式相同。但是,xFindFunction() 始终查看最左侧的参数,而不是第一个逻辑参数。因此,对于形式 “B like A”,SQLite 查看左侧操作数 “B”,如果该操作数是虚拟表列,则会对该虚拟表调用 xFindFunction() 方法。但是,如果使用形式 “like(A,B)”,则 SQLite 会检查 A 项以查看它是否是虚拟表的列,如果是,则会调用虚拟表 A 的列的 xFindFunction() 方法。
此例程返回的函数指针在第一个参数中给出的 sqlite3_vtab 对象的生命周期内必须有效。
int (*xBegin)(sqlite3_vtab *pVTab);
此方法在虚拟表上开始事务。此方法是可选的。 sqlite3_module 的 xBegin 指针可以为 NULL。
此方法之后总是紧跟着对 xCommit 或 xRollback 方法的调用。虚拟表事务不会嵌套,因此在对单个虚拟表调用 xBegin 方法后,不会在对 xCommit 或 xRollback 方法进行调用之前,再次调用该方法。在 xBegin 和相应的 xCommit 或 xRollback 之间,可能会调用其他方法,并且很可能调用多次。
int (*xSync)(sqlite3_vtab *pVTab);
此方法表示在虚拟表上开始两阶段提交。此方法是可选的。 sqlite3_module 的 xSync 指针可以为 NULL。
此方法仅在调用 xBegin 方法后以及在调用 xCommit 或 xRollback 之前调用。为了实现两阶段提交,在对任何虚拟表调用 xCommit 方法之前,会对所有虚拟表的 xSync 方法进行调用。如果任何 xSync 方法失败,则整个事务将回滚。
int (*xCommit)(sqlite3_vtab *pVTab);
此方法会导致虚拟表事务提交。此方法是可选的。 sqlite3_module 的 xCommit 指针可以为 NULL。
对此方法的调用总是紧跟着先前对 xBegin 和 xSync 的调用。
int (*xRollback)(sqlite3_vtab *pVTab);
此方法会导致虚拟表事务回滚。此方法是可选的。 sqlite3_module 的 xRollback 指针可以为 NULL。
对此方法的调用总是紧跟着先前对 xBegin 的调用。
int (*xRename)(sqlite3_vtab *pVtab, const char *zNew);
此方法提供通知,告知虚拟表实现虚拟表将被赋予一个新名称。如果此方法返回 SQLITE_OK,则 SQLite 会重命名该表。如果此方法返回 错误代码,则会阻止重命名。
xRename 方法是可选的。如果省略,则无法使用 ALTER TABLE RENAME 命令重命名虚拟表。
在调用此方法之前,将启用 PRAGMA legacy_alter_table 设置,并且在此方法完成后会恢复 legacy_alter_table 的值。这是对使用 影子表 的虚拟表的正确操作所必需的,其中影子表必须重命名以匹配新的虚拟表名称。如果 legacy_alter_format 已关闭,则每次 xRename 方法尝试更改影子表的名称时,都会对虚拟表调用 xConnect 方法。
int (*xSavepoint)(sqlite3_vtab *pVtab, int); int (*xRelease)(sqlite3_vtab *pVtab, int); int (*xRollbackTo)(sqlite3_vtab *pVtab, int);
这些方法为虚拟表实现提供了实现嵌套事务的机会。它们始终是可选的,并且仅在 SQLite 版本 3.7.7(2011 年 6 月 23 日)及更高版本中调用。
当调用 xSavepoint(X,N) 时,表示向虚拟表 X 发送信号,要求它将当前状态保存为保存点 N。随后对 xRollbackTo(X,R) 的调用意味着虚拟表的状态应返回到上次调用 xSavepoint(X,R) 时的状态。对 xRollbackTo(X,R) 的调用将使所有 N>R 的保存点失效;所有失效的保存点都不会在先被 xSavepoint() 调用重新初始化之前被回滚或释放。对 xRelease(X,M) 的调用会使所有 N>=M 的保存点失效。
除了在对 xBegin() 进行调用与对 xCommit() 或 xRollback() 进行调用之间之外,绝不会调用 xSavepoint()、xRelease() 或 xRollbackTo() 方法。
某些虚拟表实现(例如:FTS3、FTS5 和 RTREE)使用真正的(非虚拟)数据库表来存储内容。例如,当将内容插入 FTS3 虚拟表时,数据最终会存储在名为 “%_content”、“%_segdir”、“%_segments”、“%_stat” 和 “%_docsize” 的真实表中,其中 “%” 是原始虚拟表的名称。存储虚拟表内容的这种辅助真实表称为 “影子表”。有关其他信息,请参见 (1)、(2) 和 (3)。
xShadowName 方法的存在是为了允许 SQLite 确定某个真实表是否实际上是虚拟表的影子表。
如果 SQLite 识别出某个表为影子表,并且如果设置了 SQLITE_DBCONFIG_DEFENSIVE 标志,则影子表对于普通 SQL 语句来说是只读的。影子表仍然可以写入,但只能通过从某些虚拟表实现的方法内部调用的 SQL 语句写入。
如果 SQLite 识别出某个表为影子表,并且如果设置了 SQLITE_DBCONFIG_DEFENSIVE 标志,则影子表对于普通 SQL 语句来说是只读的。影子表仍然可以写入,但只能通过从某些虚拟表实现的方法内部调用的 SQL 语句写入。
xShadowName 方法的全部意义在于保护影子表的内容,使其免遭恶意 SQL 语句的破坏。每个使用影子表的虚拟表实现都应该能够检测和处理损坏的影子表内容。但是,特定虚拟表实现中的错误可能会允许故意损坏的影子表导致崩溃或其他故障。xShadowName 机制旨在通过阻止普通 SQL 语句故意破坏影子表来避免零日漏洞。
默认情况下,影子表是可读写的。只有当使用 sqlite3_db_config() 设置 SQLITE_DBCONFIG_DEFENSIVE 标志时,影子表才会变为只读的。影子表需要默认情况下是可读写的,以便保持向后兼容性。例如,由 CLI 的 .dump 命令生成的 SQL 文本直接写入影子表。
如果 sqlite3_module 的 iVersion 为 4 或更高,并且 xIntegrity 方法不为 NULL,则 PRAGMA integrity_check 和 PRAGMA quick_check 命令会在其处理过程中调用 xIntegrity。如果 xIntegrity 方法在第五个参数中写入错误消息字符串,则 PRAGMA integrity_check 会将其输出中报告该错误。因此,换句话说,xIntegrity 方法允许 PRAGMA integrity_check 命令验证存储在虚拟表中的内容的完整性。
xIntegrity 方法使用五个参数调用
xIntegrity 方法通常应返回 SQLITE_OK,即使它在虚拟表的内容中发现问题也是如此。任何其他错误代码都意味着 xIntegrity 方法本身在尝试评估虚拟表内容时遇到了问题。因此,例如,如果发现 FTS5 的反向索引在内部不一致,则 xIntegrity 方法应将适当的错误消息写入 pzErr 参数并返回 SQLITE_OK。但是,如果 xIntegrity 方法由于内存不足而无法完成对虚拟表内容的评估,则应返回 SQLITE_NOMEM。
如果生成了错误消息,则应从 sqlite3_malloc64() 或等效函数获得用于保存错误消息字符串的空间。错误消息字符串的所有权将在 xIntegrity 返回时传递给 SQLite 内核。内核将确保调用 sqlite3_free() 来回收它已完成使用错误消息的内存。调用 xIntegrity 方法的 PRAGMA integrity_check 命令不会更改返回的错误消息。xIntegrity 方法本身应将虚拟表的名称作为消息的一部分包含在内。提供 zSchema 和 zName 参数是为了使这更容易。
mFlags 参数当前是一个布尔值(0 或 1),表示是否由于 PRAGMA integrity_check(mFlags==0)或由于 PRAGMA quick_check(mFlags==1)而调用 xIntegrity 方法。一般来说,xIntegrity 方法应该无论如何都以线性时间完成它能够完成的任何有效性检查,但只有在以下情况下才执行需要超线性时间的检查:(mFlags&1)==0. SQLite 的未来版本可能会使用 mFlags 参数的高位来指示其他处理选项。
对 xIntegrity 方法的支持是在 SQLite 3.44.0 版本(2023-11-01)中添加的。在同一版本中,xIntegrity 方法被添加到许多内置虚拟表中,例如 FTS3、FTS5 和 RTREE,以便当运行 PRAGMA integrity_check 时,这些表的內容将从现在起自动进行一致性检查。
本页最后修改时间为 2024-05-23 20:19:17 UTC