返回:SQLite—系列文章目录
上一篇:SQLite的知名用户(二十九)
下一篇:SQLite 的命令行 Shell(三十一)
1. FTS5概述
FTS5 是一个 SQLite 虚拟表模块,它为数据库应用程序提供全文搜索功能。在最基本的形式中, 全文搜索引擎允许用户有效地搜索大型 包含一个或多个实例的子集的文档集合 搜索词。除其他外,谷歌向万维网用户提供的搜索功能是全文搜索 引擎,因为它允许用户搜索 Web 上包含 例如,术语“FTS5”。
要使用 FTS5,用户需要创建一个具有一个或多个 FTS5 虚拟表的 FTS5 虚拟表 列。例如:
CREATE VIRTUAL TABLE email USING fts5(sender, title, body);
将类型、约束或 PRIMARY KEY 声明添加到 用于创建 FTS5 表的 CREATE VIRTUAL TABLE 语句。创建后, 可以使用 INSERT、UPDATE 或 DELETE 语句填充 FTS5 表 像任何其他桌子一样。与没有 PRIMARY KEY 声明的任何其他表一样,一个 FTS5 表具有一个名为 rowid 的隐式 INTEGER PRIMARY KEY 字段。
上面的示例中没有显示的是,还可以向 FTS5 提供各种选项,如 CREATE VIRTUAL TABLE语句的一部分,用于配置 新表。这些可用于修改 FTS5 表提取的方式 来自文档和查询的术语,用于在磁盘上创建额外的索引以加快速度 前缀查询,或创建充当内容索引的 FTS5 表 存储在别处。
填充后,有三种方法可以对 FTS5 表的内容:
- 在 SELECT 语句的 WHERE 子句中使用 MATCH 运算符,或者
- 在 SELECT 语句的 WHERE 子句中使用等于 (“=”) 运算符,或者
- 使用表值函数语法。
如果使用 MATCH 或 = 运算符,则 MATCH 左侧的表达式 operator 通常是 FTS5 表的名称(指定列筛选器时除外)。右边的表达式 必须是指定要搜索的字词的文本值。对于表值 函数语法,要搜索的术语被指定为第一个表参数。 例如:
-- Query for all rows that contain at least once instance of the term
-- "fts5" (in any column). The following three queries are equivalent.
SELECT * FROM email WHERE email MATCH 'fts5';
SELECT * FROM email WHERE email = 'fts5';
SELECT * FROM email('fts5');
默认情况下,FTS5 全文搜索与大小写无关。像其他任何其他人一样 不包含 ORDER BY 子句的 SQL 查询,上面的示例返回 导致任意顺序。按相关性对结果进行排序(从高到低) 相关),可以将 ORDER BY 添加到全文查询中,如下所示:
-- Query for all rows that contain at least once instance of the term
-- "fts5" (in any column). Return results in order from best to worst
-- match.
SELECT * FROM email WHERE email MATCH 'fts5' ORDER BY rank;
以及匹配行的列值和 rowid,应用程序 可以使用 FTS5 辅助函数来检索有关 匹配的行。例如,辅助函数可用于检索 匹配行的列值的副本,其中包含匹配行的所有实例 术语被 HTML <b></b> 标记包围。辅助功能包括 以与 SQLite 标量函数相同的方式调用,只是名称 的 FTS5 表被指定为第一个参数。例如:
-- Query for rows that match "fts5". Return a copy of the "body" column
-- of each row with the matches surrounded by <b></b> tags.
SELECT highlight(email, 2, '<b>', '</b>') FROM email('fts5');
可用辅助功能的说明和更多详细信息 关于特殊“等级”列的配置,可在下面找到。自定义辅助函数也可以在 C 语言中实现并注册 FTS5,就像自定义 SQL 函数可以注册到 SQLite 核心一样。
除了搜索包含术语的所有行外,FTS5 还允许 要搜索包含以下内容的行的用户:
- 任何以指定前缀开头的术语,
- “phrases” - 必须在 文档,以匹配查询,
- 出现在指定术语中、前缀术语或短语的集合 彼此接近(这些查询称为“NEAR 查询”),或
- 上述任何一项的布尔组合。
通过提供更复杂的搜索来请求这种高级搜索 FTS5 查询字符串作为 MATCH 运算符右侧的文本(或 = 运算符,或作为表值函数语法的第一个参数)。这 此处介绍了完整的查询语法。
2. FTS5的编译和使用
2.1. 将FTS5构建为SQLite的一部分
自 3.9.0 版 (2015-10-14) 起, FTS5 作为 SQLite 合并的一部分包含在内。 如果使用两个 autoconf 构建系统之一,则 FTS5 是 通过在运行配置时指定“--enable-fts5”选项来启用 脚本。(FTS5 目前默认为 source-tree configure 脚本,并默认启用 合并配置脚本,但这些默认值可能 未来会发生变化。
或者,如果 sqlite3.c 是使用其他构建系统编译的,则通过安排 要定义的SQLITE_ENABLE_FTS5预处理器符号。
2.2. 构建可加载扩展
或者,FTS5 可以构建为可加载的扩展。
规范的 FTS5 源代码由一系列 *.c 和其他文件组成 在 SQLite 源代码树的“ext/fts5”目录中。构建过程减少了 这只有两个文件 - “fts5.c” 和 “fts5.h” - 可用于构建一个 SQLite 可加载扩展。
- 从 fossil 获取最新的 SQLite 代码。
- 按照如何编译 SQLite 中所述创建 Makefile。
- 构建“fts5.c”目标。这也创建了 fts5.h。
$ wget -c https://www.sqlite.org/src/tarball/SQLite-trunk.tgz?uuid=trunk -O SQLite-trunk.tgz
.... output ...
$ tar -xzf SQLite-trunk.tgz
$ cd SQLite-trunk
$ ./configure && make fts5.c
... lots of output ...
$ ls fts5.[ch]
fts5.c fts5.h
然后,“fts5.c”中的代码可以编译成可加载的扩展或 静态链接到应用程序,如编译可加载扩展中所述。定义了两个入口点,两者都 其中做同样的事情:
- sqlite3_fts_init
- sqlite3_fts5_init
另一个文件“fts5.h”不是编译 FTS5 扩展所必需的。 它由实现自定义 FTS5 分词器或辅助函数的应用程序使用。
3. 全文查询语法
以下块包含 BNF 形式的 FTS 查询语法摘要。 详细解释如下。
<phrase> := string [*]
<phrase> := <phrase> + <phrase>
<neargroup> := NEAR ( <phrase> <phrase> ... [, N] )
<query> := [ [-] <colspec> :] [^] <phrase>
<query> := [ [-] <colspec> :] <neargroup>
<query> := [ [-] <colspec> :] ( <query> )
<query> := <query> AND <query>
<query> := <query> OR <query>
<query> := <query> NOT <query>
<colspec> := colname
<colspec> := { colname1 colname2 ... }
3.1. FTS5 字符串
在 FTS 表达式中,可以通过以下两种方式之一指定字符串:
-
用双引号 (“) 括起来。在字符串中,任何嵌入 双引号字符可以转义为 SQL 样式 - 通过添加第二个 双引号字符。
-
As an FTS5 bareword that is not "AND", "OR" or "NOT" (case sensitive). An FTS5 bareword is a string of one or more consecutive characters that are all either:
- Non-ASCII range characters (i.e. unicode codepoints greater than 127), or
- One of the 52 upper and lower case ASCII characters, or
- One of the 10 decimal digit ASCII characters, or
- 下划线字符(unicode codepoint 96)。
- 替换字符(unicode codepoint 26)。
3.2. FTS5 短语
fts5 查询中的每个字符串都由分词器和零个或多个令牌的列表解析(“标记化”),或者 术语,提取。例如,默认分词器对字符串“alpha beta gamma“到三个独立的标记 - ”alpha“、”beta“和”gamma“ - 在 次序。
FTS 查询由短语组成。短语是 一个或多个令牌。查询中每个字符串的标记都组成了一个 单个短语。两个短语可以连接成一个大短语 使用“+”运算符。例如,假设正在使用分词器模块 将输入“one.two.three”标记化为三个单独的标记,如下所示 四个查询都指定相同的短语:
... MATCH '"one two three"'
... MATCH 'one + two + three'
... MATCH '"one two" + three'
... MATCH 'one.two.three'
如果文档包含至少一个子序列,则短语与文档匹配 与组成短语的标记序列匹配的标记。
3.3. FTS5前缀查询
如果 FTS 表达式中的字符串后面跟有“*”字符,则 final 从字符串中提取的令牌被标记为前缀令牌。作为你 可能期望,前缀令牌与它所属的任何文档令牌匹配 前缀。例如,以下块中的前两个查询将匹配 包含令牌“one”后跟令牌的任何文档 “two”,然后是任何以“thr”开头的标记。
... MATCH '"one two thr" * '
... MATCH 'one + two + thr*'
... MATCH '"one two thr*"' -- May not work as expected!
上述块中的最后一个查询可能无法按预期工作。因为 “*”字符在双引号内,它将被传递给分词器, 这可能会丢弃它(或者可能,取决于特定的分词器 在使用中,将其作为最终令牌的一部分包含在内),而不是将其识别为 一个特殊的 FTS 角色。
3.4. FTS5 初始令牌查询
如果“^”字符紧接在不属于 NEAR 查询,则该短语仅匹配文档,前提是它以 列中的第一个标记。“^”语法可以与列筛选器结合使用,但不能插入到 一句话。
... MATCH '^one' -- first token in any column must be "one"
... MATCH '^ one + two' -- phrase "one two" must appear at start of a column
... MATCH '^ "one two"' -- same as previous
... MATCH 'a : ^two' -- first token of column "a" must be "two"
... MATCH 'NEAR(^one, two)' -- syntax error!
... MATCH 'one + ^two' -- syntax error!
... MATCH '"^one two"' -- May not work as expected!
3.5. FTS5 NEAR查询
两个或多个短语可以分组到一个 NEAR 组中。NEAR组 由令牌“NEAR”(区分大小写)指定,后跟 open 括号字符,后跟两个或多个空格分隔的短语,可选后跟逗号和数值参数 N,后跟 一个紧括号。例如:
... MATCH 'NEAR("one two" "three four", 10)'
... MATCH 'NEAR("one two" thr* + four)'
如果未提供 N 参数,则默认为 10。NEAR组 如果文档包含至少一个标记集,则匹配该文档:
- 包含每个短语的至少一个实例,并且
- 对于第一个短语末尾之间的标记数 团块中最后一个乐句的开头小于或等于 N。
例如:
CREATE VIRTUAL TABLE f USING fts5(x);
INSERT INTO f(rowid, x) VALUES(1, 'A B C D x x x E F x');
... MATCH 'NEAR(e d, 4)'; -- Matches!
... MATCH 'NEAR(e d, 3)'; -- Matches!
... MATCH 'NEAR(e d, 2)'; -- Does not match!
... MATCH 'NEAR("c d" "e f", 3)'; -- Matches!
... MATCH 'NEAR("c" "e f", 3)'; -- Does not match!
... MATCH 'NEAR(a d e, 6)'; -- Matches!
... MATCH 'NEAR(a d e, 5)'; -- Does not match!
... MATCH 'NEAR("a b c d" "b c" "e f", 4)'; -- Matches!
... MATCH 'NEAR("a b c d" "b c" "e f", 3)'; -- Does not match!
3.6. FTS5色谱柱过滤器
单个短语或 NEAR 组可能仅限于匹配 通过在 FTS 表前面加上列名来指定该表的列 后跟冒号字符。或者通过添加前缀到一组列 用括号括起来的空格分隔的列名列表 (“大括号”)后跟冒号字符。可以指定列名 使用上面为字符串描述的两种形式之一。与字符串不同 是短语的一部分,列名不会传递给分词器模块。 列名不区分大小写,通常以SQLite列名的方式 - 大写/小写等效性仅适用于 ASCII 范围字符。
... MATCH 'colname : NEAR("one two" "three four", 10)'
... MATCH '"colname" : one + two + three'
... MATCH '{col1 col2} : NEAR("one two" "three four", 10)'
... MATCH '{col2 col1 col3} : one + two + three'
如果列筛选器规范前面有“-”字符,则 它被解释为不匹配的列列表。例如:
-- Search for matches in all columns except "colname"
... MATCH '- colname : NEAR("one two" "three four", 10)'
-- Search for matches in all columns except "col1", "col2" and "col3"
... MATCH '- {col2 col1 col3} : one + two + three'
列筛选器规范也可以应用于任意表达式 括号内。在这种情况下,列筛选器适用于所有 表达式中的短语。嵌套列筛选器操作只能 进一步限制匹配的列子集,它们不能用于 重新启用筛选的列。例如:
-- The following are equivalent:
... MATCH '{a b} : ( {b c} : "hello" AND "world" )'
... MATCH '(b : "hello") AND ({a b} : "world")'
最后,可以使用以下方法指定单个列的列过滤器 列名作为 MATCH 运算符的 LHS(而不是通常的 表名)。例如:
-- Given the following table
CREATE VIRTUAL TABLE ft USING fts5(a, b, c);
-- The following are equivalent
SELECT * FROM ft WHERE b MATCH 'uvw AND xyz';
SELECT * FROM ft WHERE ft MATCH 'b : (uvw AND xyz)';
-- This query cannot match any rows (since all columns are filtered out):
SELECT * FROM ft WHERE b MATCH 'a : xyz';
3.7. FTS5布尔运算符
短语和 NEAR 组可以使用布尔值排列成表达式 运算符。按优先顺序,从最高(最紧密分组)到 最低(最松散的分组),运算符为:
算子 | 功能 |
---|---|
<query1> NOT <query2> | 如果 query1 匹配且 query2 不匹配,则匹配。 |
<query1> AND <query2> | 如果 query1 和 query2 都匹配,则匹配。 |
<query1> OR <query2> | 如果 query1 或 query2 匹配,则匹配。 |
括号可用于对表达式进行分组,以便修改运算符 以通常的方式优先。例如:
-- Because NOT groups more tightly than OR, either of the following may
-- be used to match all documents that contain the token "two" but not
-- "three", or contain the token "one".
... MATCH 'one OR two NOT three'
... MATCH 'one OR (two NOT three)'
-- Matches documents that contain at least one instance of either "one"
-- or "two", but do not contain any instances of token "three".
... MATCH '(one OR two) NOT three'
短语和 NEAR 组也可以通过隐式 AND 运算符连接。 为简单起见,上面的 BNF 语法中没有显示这些内容。从本质上讲,任何 短语序列或 NEAR 组(包括仅限于匹配的组) 指定的列)仅由空格分隔,就像有一个 每对短语或 NEAR 组之间的隐式 AND 运算符。含蓄 AND 运算符从不插入在 括号。隐式 AND 运算符的分组比其他所有运算符都更紧密 运算符,包括 NOT。例如:
... MATCH 'one two three' -- 'one AND two AND three'
... MATCH 'three "one two"' -- 'three AND "one two"'
... MATCH 'NEAR(one two) three' -- 'NEAR(one two) AND three'
... MATCH 'one OR two three' -- 'one OR two AND three'
... MATCH 'one NOT two three' -- 'one NOT (two AND three)'
... MATCH '(one OR two) three' -- Syntax error!
... MATCH 'func(one two)' -- Syntax error!
4. FTS5表的创建和初始化
每个参数都指定为“CREATE VIRTUAL TABLE ...使用 fts5 ..."语句可以是列声明,也可以是配置选项。列声明由一个或多个空格分隔的 FTS5 组成 以 SQLite 可接受的任何方式引用的裸词或字符串文字。
列声明中的第一个字符串或裸字是列名。它 尝试将 fts5 表列命名为“rowid”或“rank”或 为表本身使用的列分配相同的名称。这不是 支持。
列声明中的每个后续字符串或裸字都是一列 选项,用于修改该列的行为。列选项包括 与大小写无关。与 SQLite 核心不同,FTS5 考虑无法识别的列 选项是错误的。目前,唯一公认的选项是“未编制索引”(见下文)。
配置选项由 FTS5 简字组成 - 选项名称 - 后跟“=”字符,后跟选项值。选项值为 使用单个 FTS5 裸字或字符串文本指定,再次引用 以SQLite核心可接受的任何方式。例如:
CREATE VIRTUAL TABLE mail USING fts5(sender, title, body, tokenize = 'porter ascii');
目前有以下配置选项:
- “tokenize”选项,用于配置自定义分词器。
- “prefix”选项,用于向 FTS5 表添加前缀索引。
- “content”选项,用于使 FTS5 表成为外部内容表或无内容表。
- “content_rowid”选项,用于设置外部内容表的 rowid 字段。
- “columnsize”选项,用于配置 FTS5 表中每个值的标记大小是否为 单独存储在数据库中。
- “详细信息”选项。可以使用此选项 通过省略一些信息来减小磁盘上 FTS 索引的大小 从它。
4.1. 未索引列选项
使用“未索引列”选项限定的列的内容不是 添加到FTS指数中。这意味着,出于 MATCH 查询和 FTS5 辅助函数的目的,该列不包含可匹配的标记。
例如,避免将“uuid”字段的内容添加到 FTS 中 指数:
CREATE VIRTUAL TABLE customers USING fts5(name, addr, uuid UNINDEXED);
4.2. 前缀索引
默认情况下,FTS5 维护一个索引,记录每个索引的位置 文档集中的令牌实例。这意味着查询完成 令牌速度很快,因为它需要一次查找,但需要查询前缀 令牌可能很慢,因为它需要范围扫描。例如,要查询 前缀令牌“abc*”需要对大于 或等于“abc”且小于“abd”。
前缀索引是一个单独的索引,用于记录所有 用于加速的字符中一定长度的前缀标记的实例 对前缀令牌的向上查询。例如,优化前缀查询 令牌“abc*”需要由三个字符前缀组成的前缀索引。
要向 FTS5 表添加前缀索引,请将“前缀”选项设置为 单个正整数或包含空格的文本值 一个或多个正整数值的分隔列表。前缀索引是 为指定的每个整数创建。如果多个“前缀”选项是 指定为单个 CREATE VIRTUAL TABLE 语句的一部分,全部适用。
4.3. 分词器
CREATE VIRTUAL TABLE “tokenize” 选项用于配置 FTS5 表使用的特定分词器。option 参数必须是 FTS5 裸字,或 SQL 文本文字。论证的文本本身就是 被视为一个或多个 FTS5 裸字或 SQL 文本的空格序列 文字。其中第一个是要使用的分词器的名称。第二个 后续列表元素(如果存在)是传递给 分词器实现。
与选项值和列名不同,SQL 文本文本意为 分词器必须使用单引号字符进行引号。例如:
-- The following are all equivalent
CREATE VIRTUAL TABLE t1 USING fts5(x, tokenize = 'porter ascii');
CREATE VIRTUAL TABLE t1 USING fts5(x, tokenize = "porter ascii");
CREATE VIRTUAL TABLE t1 USING fts5(x, tokenize = "'porter' 'ascii'");
CREATE VIRTUAL TABLE t1 USING fts5(x, tokenize = '''porter'' ''ascii''');
-- But this will fail:
CREATE VIRTUAL TABLE t1 USING fts5(x, tokenize = '"porter" "ascii"');
-- This will fail too:
CREATE VIRTUAL TABLE t1 USING fts5(x, tokenize = 'porter' 'ascii');
FTS5 具有三个内置分词器模块,详见后续 部分:
- unicode61 分词器,基于 Unicode 6.1 标准。这 是默认值。
- ascii 分词器,它假定 ASCII 代码点范围 (0-127) 将被视为标记字符。
- porter tokenizer,它实现 porter 词干提取算法。
也可以为 FTS5 创建自定义分词器。此处介绍了用于执行此操作的 API。
4.3.1. Unicode61分词器
unicode 分词器将所有 unicode 字符分类为任一字符 “分隔符”或“标记”字符。默认情况下,所有空格和标点符号 Unicode 6.1 定义的字符被视为分隔符,并且所有 其他字符作为标记字符。更具体地说,所有 unicode 分配给以“L”或“N”开头的一般类别的字符(字母和数字, 具体而言)或类别“Co”(“其他私人用途”)被视为代币。 所有其他字符都是分隔符。
一个或多个标记字符的每个连续运行都被视为 令 牌。根据定义的规则,分词器不区分大小写 Unicode 6.1.
默认情况下,从所有拉丁字母字符中删除变音符号。这 例如,表示“A”、“a”、“À”、“à”、“”和“â” 都被认为是等价的。
令牌规范中“unicode61”后面的任何参数都将被处理 作为交替选项名称和值的列表。Unicode61 支持 以下选项:
选择 | 用法 |
---|---|
remove_diacritics | 此选项应设置为“0”、“1”或“2”。默认值为“1”。 如果设置为“1”或“2”,则从拉丁脚本中删除变音符号 如上所述的字符。但是,如果它设置为“1”,则变音符号 在相当罕见的情况下,单个 Unicode 代码点不会被删除 用于表示具有多个变音符号的字符。例如 变音符号不会从代码点0x1ED9中删除(“拉丁文小写字母 O CIRCUMFLEX 和下面的点“)。从技术上讲,这是一个错误,但无法修复 不会产生向后兼容性问题。如果此选项设置为 “2”,则从所有拉丁字符中正确删除变音符号。 |
类别 | 此选项可用于修改 Unicode 常规类别集 被视为对应于标记字符。参数必须 由两个字符的常规类别的空格分隔列表组成 缩写(例如“Lu”或“Nd”),或与第二个字符相同 替换为星号(“*”),解释为球形图案。默认值 值为“L* N* Co”。 |
令牌字符 | 此选项用于指定其他 Unicode 字符 应被视为标记字符,即使它们是空格或 标点符号符合 Unicode 6.1。中的所有字符 此选项设置为的字符串被视为标记字符。 |
分隔符 | 此选项用于指定其他 Unicode 字符 应被视为分隔符,即使它们是标记 符合 Unicode 6.1 的字符。字符串中的所有字符 此选项设置为被视为分隔符。 |
例如:
-- Create an FTS5 table that does not remove diacritics from Latin
-- script characters, and that considers hyphens and underscore characters
-- to be part of tokens.
CREATE VIRTUAL TABLE ft USING fts5(a, b,
tokenize = "unicode61 remove_diacritics 0 tokenchars '-_'"
);
或:
-- Create an FTS5 table that, as well as the default token character classes,
-- considers characters in class "Mn" to be token characters.
CREATE VIRTUAL TABLE ft USING fts5(a, b,
tokenize = "unicode61 categories 'L* N* Co Mn'"
);
fts5 unicode61 分词器与 fts3/4 逐字节兼容 Unicode61 分词器。
4.3.2. Ascii 分词器
Ascii 分词器类似于 Unicode61 分词器,不同之处在于:
- 所有非 ASCII 字符(代码点大于 127 的字符)都是 始终被视为标记字符。如果指定了任何非 ASCII 字符 作为分隔符选项的一部分,它们将被忽略。
- 大小写折叠仅对 ASCII 字符执行。因此,虽然“A”和 “a”被认为是等价的,“Ô和“ã”是不同的。
- 不支持remove_diacritics选项。
例如:
-- Create an FTS5 table that uses the ascii tokenizer, but does not
-- consider numeric characters to be part of tokens.
CREATE VIRTUAL TABLE ft USING fts5(a, b,
tokenize = "ascii separators '0123456789'"
);
4.3.3. Porter 分词器
porter 分词器是一个包装分词器。它需要一些输出 其他分词器,并在将每个令牌返回给 FTS5 之前将 porter 词干提取算法应用于每个令牌。这允许搜索像 “更正”以匹配类似的词,例如“更正”或“更正”。这 Porter Stemmer 算法设计用于英语术语 仅 - 将它与其他语言一起使用可能会也可能不会改善搜索实用程序。
默认情况下,porter 分词器作为默认值的包装器运行 分词器 (Unicode61)。或者,如果将一个或多个额外的参数添加到 “porter”之后的“tokenize”选项,它们被视为 Porter Stemmer 使用的底层分词器。例如:
-- Two ways to create an FTS5 table that uses the porter tokenizer to
-- stem the output of the default tokenizer (unicode61).
CREATE VIRTUAL TABLE t1 USING fts5(x, tokenize = porter);
CREATE VIRTUAL TABLE t1 USING fts5(x, tokenize = 'porter unicode61');
-- A porter tokenizer used to stem the output of the unicode61 tokenizer,
-- with diacritics removed before stemming.
CREATE VIRTUAL TABLE t1 USING fts5(x, tokenize = 'porter unicode61 remove_diacritics 1');
4.3.4. 卦分词器
卦分词器扩展了 FTS5 以支持子字符串 一般情况下的匹配,而不是通常的令牌匹配。使用 卦标记器,查询或短语标记可以匹配任何字符序列 在一行中,而不仅仅是一个完整的令牌。例如:
CREATE VIRTUAL TABLE tri USING fts5(a, tokenize="trigram");
INSERT INTO tri VALUES('abcdefghij KLMNOPQRST uvwxyz');
-- The following queries all match the single row in the table
SELECT * FROM tri('cdefg');
SELECT * FROM tri('cdefg AND pqr');
SELECT * FROM tri('"hij klm" NOT stuv');
卦分词器支持以下选项:
选择 | 用法 |
---|---|
case_sensitive | 此值可以设置为 1 或 0(默认值)。如果设置为 1, 则匹配区分大小写。否则,如果此选项设置为 0,匹配不区分大小写。 |
remove_diacritics | 此值也可以设置为 1 或 0(默认值)。它可能只有 如果case_sensitive选项设置为 0,则设置为 1 - 同时设置 选项设置为 1 是错误的。如果设置了此选项,则变音符号为 在匹配之前从文本中删除(例如,使“á”与“a”匹配)。 |
-- A case-sensitive trigram index
CREATE VIRTUAL TABLE tri USING fts5(a, tokenize="trigram case_sensitive 1");
除非设置了 remove_diacritics 选项,否则使用卦象的 FTS5 表 分词器还支持索引 GLOB 和 LIKE 模式匹配。例如:
SELECT * FROM tri WHERE a LIKE '%cdefg%';
SELECT * FROM tri WHERE a GLOB '*ij klm*xyz';
如果创建 FTS5 卦形分词器时 case_sensitive 选项设置为 1, 它可能只索引 GLOB 查询,而不能索引 LIKE。
笔记:
- 由少于 3 个 unicode 字符组成的子字符串不匹配任何 与全文查询一起使用时的行。如果 LIKE 或 GLOB 模式没有 包含至少一个非通配符 unicode 字符序列 FTS5 回退到整个表的线性扫描。
- 如果使用 detail=none 或 detail=column 选项创建 FTS5 表 指定的全文查询不得包含任何长度超过 3 的标记 Unicode 字符。LIKE 和 GLOB 模式匹配可能会稍微慢一些, 但仍然有效。如果索引仅用于 LIKE 和/或 GLOB 模式匹配,这些选项值得尝试以减少 索引大小。
4.4. 外部内容表和无内容表
通常,当将一行插入到 FTS5 表中时,除了构建 索引,FTS5 复制原始行内容。 当用户或 辅助函数实现,这些值是 从内容的私人副本中读取。可以使用“内容”选项 创建仅存储 FTS 全文索引条目的 FTS5 表。 因为列值本身通常比 关联的全文索引条目,这样可以节省大量数据库空间。
有两种方法可以使用“内容”选项:
- 通过将其设置为空字符串来创建无内容的 FTS5 表。在 在这种情况下,FTS5 假定原始列值不可用 在处理查询时。全文查询和一些辅助查询 函数仍然可以使用,但除了 rowid 之外没有列值 可以从表中读取。
- 通过将其设置为数据库对象的名称(表、虚拟表或 视图),FTS5 可以随时查询以检索列 值。这称为“外部内容”表。在这种情况下,所有 可以使用 FTS5 功能,但这是用户的责任 确保全文索引的内容与 命名的数据库对象。如果不是,查询结果可能是 不可预知的。
4.4.1. 无内容表
通过将“content”选项设置为 一个空字符串。例如:
CREATE VIRTUAL TABLE f1 USING fts5(a, b, c, content='');
无内容的 FTS5 表不支持 UPDATE 或 DELETE 语句,或者 不为 rowid 字段提供非 NULL 值的 INSERT 语句。 无内容表不支持 REPLACE 冲突处理。取代 和 INSERT OR REPLACE 语句被视为常规 INSERT 语句。 可以使用 FTS5 delete 命令从无内容表中删除行。
尝试从无内容中读取除 rowid 之外的任何列值 FTS5 表返回 SQL NULL 值。
4.4.2. 无内容删除表
从版本 3.43.0 开始,还提供无内容删除表。 通过将 content 选项设置为 空字符串,并将 contentless_delete 选项设置为 1。例如:
CREATE VIRTUAL TABLE f1 USING fts5(a, b, c, content='', contentless_delete=1);
无内容删除表与无内容表的不同之处在于:
- Contentless-delete 表同时支持 DELETE 和“INSERT OR REPLACE INTO“语句。
- 无内容删除表支持 UPDATE 语句,但前提是新 为 fts5 表的所有用户定义列提供值。
- 无内容删除表不支持 FTS5 delete 命令。
-- Supported UPDATE statement:
UPDATE f1 SET a=?, b=?, c=? WHERE rowid=?;
-- This UPDATE is not supported, as it does not supply a new value
-- for column "c".
UPDATE f1 SET a=?, b=? WHERE rowid=?;
除非需要向后兼容性,否则新代码应首选 Contentless-Delete 表到无内容表。
4.4.3. 外部内容表
通过设置内容来创建外部内容 FTS5 表 选项添加到表、虚拟表或视图的名称(以下简称“内容 表“)。每当需要列值时 FTS5,它查询内容表如下,rowid 为 对于哪些值需要绑定到 SQL 变量:
SELECT <content_rowid>, <cols> FROM <content> WHERE <content_rowid> = ?;
在上面的 <content> 中,将替换为内容表的名称。 默认情况下,<content_rowid> 将替换为文本“rowid”。或 如果在 CREATE VIRTUAL TABLE 语句中设置了 “content_rowid” 选项, 按该选项的值。<cols> 替换为逗号分隔的列表 FTS5 表列名称。例如:
-- If the database schema is:
CREATE TABLE tbl (a, b, c, d INTEGER PRIMARY KEY);
CREATE VIRTUAL TABLE fts USING fts5(a, c, content=tbl, content_rowid=d);
-- Fts5 may issue queries such as:
SELECT d, a, c FROM tbl WHERE d = ?;
还可以按如下方式查询内容表:
SELECT <content_rowid>, <cols> FROM <content> ORDER BY <content_rowid> ASC;
SELECT <content_rowid>, <cols> FROM <content> ORDER BY <content_rowid> DESC;
用户仍然有责任确保 外部内容 FTS5 表与内容表保持同步。 一种方法是使用触发器。例如:
-- Create a table. And an external content fts5 table to index it.
CREATE TABLE tbl(a INTEGER PRIMARY KEY, b, c);
CREATE VIRTUAL TABLE fts_idx USING fts5(b, c, content='tbl', content_rowid='a');
-- Triggers to keep the FTS index up to date.
CREATE TRIGGER tbl_ai AFTER INSERT ON tbl BEGIN
INSERT INTO fts_idx(rowid, b, c) VALUES (new.a, new.b, new.c);
END;
CREATE TRIGGER tbl_ad AFTER DELETE ON tbl BEGIN
INSERT INTO fts_idx(fts_idx, rowid, b, c) VALUES('delete', old.a, old.b, old.c);
END;
CREATE TRIGGER tbl_au AFTER UPDATE ON tbl BEGIN
INSERT INTO fts_idx(fts_idx, rowid, b, c) VALUES('delete', old.a, old.b, old.c);
INSERT INTO fts_idx(rowid, b, c) VALUES (new.a, new.b, new.c);
END;
与无内容表一样,外部内容表不支持 REPLACE 冲突处理。指定 REPLACE 冲突处理的任何操作都是 使用 ABORT 处理。
4.4.4. 外部内容表陷阱
用户有责任确保FTS5外部内容 表(具有非空 content= 选项的表)与 content 表本身(由 content= 选项命名的表)。如果这些是 允许变得不一致,那么针对 FTS5 的查询结果 表格可能会变得不直观,并且看起来不一致。
在这些情况下,查询产生的明显不一致的结果 针对FTS5的外部内容表可以理解为如下:
-
如果查询不使用全文索引 - 不包含 MATCH 运算符或等效的表值函数语法 - 然后 查询有效地传递到外部内容表。在 在这种情况下,FTS指数的内容对结果没有影响 的查询。
-
如果查询确实使用全文索引,则 FTS5 模块 查询与匹配项的文档对应的 rowid 值集 查询。然后,对于每个这样的 rowid,它运行类似于以下内容的查询 若要检索任何必需的列值,请将“?”替换为 rowid value 和 <content> 并由指定的值<content_rowid> 对于 content= 和 content_rowid= 选项:
SELECT <content_rowid>, <cols> FROM <content> WHERE <content_rowid> = ?;
例如,如果使用以下脚本创建数据库:
-- Create and populate a table.
CREATE TABLE tbl(a INTEGER PRIMARY KEY, t TEXT);
INSERT INTO tbl VALUES(1, 'all that glitters');
INSERT INTO tbl VALUES(2, 'is not gold');
-- Create an external content FTS5 table
CREATE VIRTUAL TABLE ft USING fts5(t, content='tbl', content_rowid='a');
则内容表包含两行,但 FTS 索引不包含 与它们相对应的条目。在这种情况下,将返回以下查询 结果不一致,如下所示:
-- Returns 2 rows. Because the query does not use the FTS index, it is
-- effectively executed against table 'tbl' directly, and so returns
-- both rows.
SELECT * FROM t1;
-- Returns 0 rows. This query does use the FTS index, which currently
-- contains no entries. So it returns 0 rows.
SELECT rowid, t FROM t1('gold')
或者,如果按如下方式创建和填充数据库:
-- Create and populate a table.
CREATE TABLE tbl(a INTEGER PRIMARY KEY, t TEXT);
-- Create an external content FTS5 table
CREATE VIRTUAL TABLE ft USING fts5(t, content='tbl', content_rowid='a');
INSERT INTO ft(rowid, t) VALUES(1, 'all that glitters');
INSERT INTO ft(rowid, t) VALUES(2, 'is not gold');
则内容表为空,但 FTS 索引包含 6种不同的代币。在这种情况下,将返回以下查询 结果不一致,如下所示:
-- Returns 0 rows. Since it does not use the FTS index, the query is
-- passed directly through to table 'tbl', which contains no data.
SELECT * FROM t1;
-- Returns 1 row. The "rowid" field of the returned row is 2, and
-- the "t" field set to NULL. "t" is set to NULL because when the external
-- content table "tbl" was queried for the data associated with the row
-- with a=2 ("a" is the content_rowid column), none could be found.
SELECT rowid, t FROM t1('gold')
如上一节所述,内容表上的触发器包括 这是确保 FTS5 外部内容表保持一致的好方法。 但是,只有在插入、更新或删除行时才会触发触发器 在内容表中。这意味着,例如,如果创建数据库 如下:
-- Create and populate a table.
CREATE TABLE tbl(a INTEGER PRIMARY KEY, t TEXT);
INSERT INTO tbl VALUES(1, 'all that glitters');
INSERT INTO tbl VALUES(2, 'is not gold');
-- Create an external content FTS5 table
CREATE VIRTUAL TABLE ft USING fts5(t, content='tbl', content_rowid='a');
-- Create triggers to keep the FTS5 table up to date
CREATE TRIGGER tbl_ai AFTER INSERT ON tbl BEGIN
INSERT INTO ft(rowid, t) VALUES (new.a, new.t);
END;
<similar triggers for update + delete>
则内容表和外部内容 FTS5 表不一致,如 创建触发器不会从内容表中复制现有行 进入富通指数。触发器只能确保对 创建后的内容表将反映在 FTS 索引中。
在这种情况下,以及 FTS 指数及其内容表的任何其他情况 变得不一致,可以使用“rebuild”命令完全丢弃 FTS 索引的内容和 根据内容表的当前内容重新生成它。
4.5. Columnsize 选项
通常,FTS5 在数据库中维护一个特殊的支持表,该表 将每个列值的大小存储在插入到主 FTS5 中的标记中 表。此支持表由 xColumnSize API 函数使用,而 xColumnSize API 函数又由 内置的 BM25 排名功能(并且可能很有用 以及其他排名功能)。
为了节省空间,可以通过设置 columnsize 选项设置为零。例如:
-- A table without the xColumnSize() values stored on disk:
CREATE VIRTUAL TABLE ft USING fts5(a, b, c, columnsize=0);
-- Three equivalent ways of creating a table that does store the
-- xColumnSize() values on disk:
CREATE VIRTUAL TABLE ft USING fts5(a, b, c);
CREATE VIRTUAL TABLE ft USING fts5(a, b, c, columnsize=1);
CREATE VIRTUAL TABLE ft USING fts5(a, b, columnsize='1', c);
将 columnsize 选项设置为除 0 或 1。
如果 FTS5 表配置了 columnsize=0 但不是无内容表,则 xColumnSize API 函数 仍然有效,但运行速度要慢得多。在这种情况下,而不是阅读 直接从数据库返回的值,它读取文本值 本身并按需计算其中的代币。
或者,如果该表也是无内容表, 则以下情况适用:
-
xColumnSize API 始终返回 -1。没有办法确定 存储在无内容 FTS5 表中的值中的令牌数 配置为 columnsize=0。
-
插入的每个行都必须附带一个显式指定的 rowid 价值。如果无内容表配置了 columnsize=0, 尝试将 NULL 值插入到 rowid 中是一种SQLITE_MISMATCH 错误。
-
表上的所有查询都必须是全文查询。换言之, 他们必须使用 MATCH 或 = 运算符,并将 table-name 列作为 左手操作数,否则使用表值函数语法。任何 不是全文查询的查询会导致错误。
存储 xColumnSize 值的表的名称 (除非指定了 columnsize=0)为 “<name>_docsize”,其中 <name> 是 FTS5 表本身的名称。可以在现有数据库上使用sqlite3_analyzer工具,以确定多少 通过使用 columnsize=0 重新创建 FTS5 表可以节省空间。
4.6. 细节选项
对于文档中的每个术语,FTS5 维护的 FTS 索引 存储文档的 rowid,即包含 列值中的术语和术语的偏移量。“细节” 选项可用于省略其中的一些信息。这样可以减少空间 索引在数据库文件中消耗,但也减少了 系统的能力和效率。
详细信息选项可以设置为“full”(默认值)、“column”或 “无”。例如:
-- The following two lines are equivalent (because the default value
-- of "detail" is "full".
CREATE VIRTUAL TABLE ft1 USING fts5(a, b, c);
CREATE VIRTUAL TABLE ft1 USING fts5(a, b, c, detail=full);
CREATE VIRTUAL TABLE ft2 USING fts5(a, b, c, detail=column);
CREATE VIRTUAL TABLE ft3 USING fts5(a, b, c, detail=none);
如果详细信息选项设置为列,则对于每个术语,FTS 索引仅记录 rowid 和列号,省略术语偏移量 信息。这会导致以下限制:
- NEAR 查询不可用。
- 短语查询不可用。
- 假设该表不是无内容表,则 xInstCount、xInst、xPhraseFirst 和 xPhraseNext 的速度比平时慢。这是因为不是读取所需的数据 他们必须直接从 FTS 索引加载和标记文档文本 按需。
- 如果该表也是无内容表,则 xInstCount、xInst、 xPhraseFirst 和 xPhraseNext API 的行为就好像当前行不包含 短语完全匹配(即 xInstCount() 返回 0)。
如果 detail 选项设置为 none,则对于每个术语 FTS 索引仅记录 rowid。列和偏移量信息 被省略。以及上面针对 detail=column 逐项列出的限制 模式下,这会带来以下额外限制:
- 列筛选器查询不可用。
- 假设该表不是无内容表,则 xPhraseFirstColumn 和 xPhraseNextColumn 的速度比平时慢。
- 如果该表也是无内容表,则 xPhraseFirstColumn 和 xPhraseNextColumn API 的行为就好像当前行不包含任何短语一样 完全匹配(即 xPhraseFirstColumn() 将迭代器设置为 EOF)。
在一项索引大量电子邮件(磁盘上为 1636 MiB)的测试中,FTS 磁盘上的索引为 743 MiB,detail=full 为 340 MiB,detail=column 和 134 带有 detail=none 的 MiB。
4.7. Tokendata选项
此选项仅对实现自定义分词器的应用程序有用。通常,分词器可能会 返回由任何字节序列(包括 0x00 个字节)组成的标记。 但是,如果表指定了 tokendata=1 选项,则 fts5 将忽略 令牌中的第一个 0x00 字节和任何尾随数据 的匹配。它仍然存储分词器返回的整个令牌, 但它被 FTS5 内核忽略了。
令牌的完整版本,包括任何0x00字节和尾随数据, 可用于自定义辅助 通过 xQueryToken 和 xInstToken API 进行函数。
这对于排名函数可能很有用。自定义分词器可以 向某些文档标记添加额外数据,允许排名函数给出 某些标记(例如文档标题中的标记)的命中权重更大。
或者,自定义分词器和自定义辅助工具的组合 函数可用于实现非对称搜索。分词器可以(比如)为每个文档令牌返回 令牌的大小写规范化和未标记版本,后跟0x00 byte,后跟文档中令牌的全文。查询时, fts5 将提供结果,就好像查询中的所有字符都是 大小写标准化且未标记。然后可以使用自定义辅助功能 在查询的 WHERE 子句中筛选出任何不匹配的行 在文档或查询术语中的二级或三级标记上。
5. 辅助功能
辅助函数类似于 SQL 标量函数, 除了它们只能在全文查询中使用(那些使用 MATCH 运算符)在 FTS5 表上。他们的结果是根据 仅在传递给他们的参数上,而且在当前比赛和 匹配的行。例如,辅助函数可以返回一个数值 指示匹配的准确性(参见 bm25() 函数), 或匹配行中包含一个或多个文本片段的文本片段 搜索词的实例(参见 Snippet() 函数)。
要调用辅助函数,FTS5 表的名称应为 指定为第一个参数。其他论点可能遵循第一个论点, 取决于所调用的特定辅助函数。例如,要 调用“highlight”函数:
SELECT highlight(email, 2, '<b>', '</b>') FROM email WHERE email MATCH 'fts5'
作为 FTS5 的一部分提供的内置辅助功能在 以下部分。应用程序还可以在 C 中实现自定义辅助功能。
5.1. 内置辅助功能
FTS5 提供三个内置辅助功能:
- bm25() 辅助函数返回一个实值 反映当前匹配的准确性。更好的匹配是 分配的数值较低。
- highlight() 辅助函数返回一个副本 当前匹配的一列中的文本与每个 结果中查询项的实例,周围有指定的 标记(例如“<b>”和“</b>”)。
- snippet() 辅助函数选择一个短 来自匹配行的一列的文本片段,并返回 它与被标记包围的查询术语的每个实例一起 与 highlight() 函数的方式相同。文本片段是 选择以最大化它包含的查询词的数量。
5.1.1. bm25() 函数
内置辅助函数 bm25() 返回一个实数值,指示 当前行与全文查询的匹配程度。比赛越好, 返回的值在数值上越小。如下所示的查询可能 用于按从最佳到最差的顺序返回匹配项:
SELECT * FROM fts WHERE fts MATCH ? ORDER BY bm25(fts)
为了计算文档分数,全文查询是分开的 添加到其组成短语中。文档 D 和 然后按如下方式计算查询 Q:
在上面,nPhrase 是查询中的短语数。|D|是当前文档中的令牌数,avgdl 是 FTS5 表。k1 和 b 都是常数, 硬编码分别为 1.2 和 0.75。
公式开头的“-1”项在大多数中都找不到 BM25 算法的实现。没有它,就会分配更好的匹配项 BM25 分数在数字上更高。由于默认排序顺序是 “升序”,这意味着将“ORDER BY bm25(fts)”附加到查询中会 使结果按从最差到最佳的顺序返回。“DESC”关键字 为了首先返回最佳匹配项。为了 避免了这个陷阱,BM25 的 FTS5 实现使结果成倍增加 在返回之前按 -1,确保分配更好的匹配项 数字上较低的分数。
IDF(qi) 是查询的逆文档频率 短语 i.计算方法如下,其中 N 是总数 FTS5 表中的行数,n(qi) 是总数 包含至少一个短语 i 实例的行数:
最后,f(qi,D) 是短语 i 的短语频率。默认情况下,这只是短语的出现次数 在当前行中。但是,通过将额外的实值参数传递给 bm25() SQL 函数中,表的每一列都可以分配不同的 权重和短语频率的计算方法如下:
其中 wc 是分配给 c 列的权重,n(qi,c) 是短语 i 在 当前行的 C 列。传递给 bm25() 的第一个参数 表名后面是分配给最左边列的权重 FTS5 表。第二个是分配给最左边第二个的权重 列,依此类推。如果所有表列的参数都不够, 其余列的权重为 1.0。如果太多 尾随参数,则忽略附加内容。例如:
-- Assuming the following schema:
CREATE VIRTUAL TABLE email USING fts5(sender, title, body);
-- Return results in bm25 order, with each phrase hit in the "sender"
-- column considered the equal of 10 hits in the "body" column, and
-- each hit in the "title" column considered as valuable as 5 hits in
-- the "body" column.
SELECT * FROM email WHERE email MATCH ? ORDER BY bm25(email, 10.0, 5.0);
有关以下内容的更多信息,请参阅维基百科 BM25及其变体。
5.1.2. highlight() 函数
highlight() 函数从指定的文本返回文本的副本 当前行的列,并插入了额外的标记文本以标记开始 和短语结尾匹配。
highlight() 必须使用以下三个参数来调用 表名。解释如下:
- 一个整数,指示要读取 文本来自。列从零开始从左到右编号。
- 在每个短语匹配之前要插入的文本。
- 在每个短语匹配后插入的文本。
例如:
-- Return a copy of the text from the leftmost column of the current
-- row, with phrase matches marked using html "b" tags.
SELECT highlight(fts, 0, '<b>', '</b>') FROM fts WHERE fts MATCH ?
在两个或多个短语实例重叠的情况下(共享一个或多个 标记),为每组插入一个打开和关闭标记 重叠的短语。例如:
-- Assuming this:
CREATE VIRTUAL TABLE ft USING fts5(a);
INSERT INTO ft VALUES('a b c x c d e');
INSERT INTO ft VALUES('a b c c d e');
INSERT INTO ft VALUES('a b c d e');
-- The following SELECT statement returns these three rows:
-- '[a b c] x [c d e]'
-- '[a b c] [c d e]'
-- '[a b c d e]'
SELECT highlight(ft, 0, '[', ']') FROM ft WHERE ft MATCH 'a+b+c AND c+d+e';
5.1.3. snippet() 函数
snippet() 函数类似于 highlight(),不同之处在于 返回整个列值,它会自动选择并提取一个 要处理和返回的文档文本的简短片段。snippet() 函数 必须在 table name 参数后面传递五个参数:
- 一个整数,指示要选择的 FTS 表列的索引 返回的文本。列按从左到右的顺序编号 从零开始。负值表示列应 被自动选中。
- 在返回的文本中匹配的每个短语之前插入的文本。
- 在返回的文本中匹配的每个短语之后插入的文本。
- 要添加到所选文本的开头或结尾以指示的文本 返回的文本不会出现在其列的开头或结尾, 分别。
- 返回文本中的最大标记数。这必须更大 小于零且等于或小于 64。
5.2. 按辅助功能结果排序
所有 FTS5 表都具有一个名为“rank”的特殊隐藏列。如果 当前查询不是全文查询(即,如果它不包含 MATCH operator),则 “rank” 列的值始终为 NULL。否则,在 全文查询,列排名默认包含与 通过执行 bm25() 辅助函数返回,不带尾随 参数。
从排名列读取和使用 bm25() 之间的区别 直接在查询中的函数仅在按 返回值。在这种情况下,使用“rank”比使用bm25()更快。
-- The following queries are logically equivalent. But the second may
-- be faster, particularly if the caller abandons the query before
-- all rows have been returned (or if the queries were modified to
-- include LIMIT clauses).
SELECT * FROM fts WHERE fts MATCH ? ORDER BY bm25(fts);
SELECT * FROM fts WHERE fts MATCH ? ORDER BY rank;
而不是使用没有尾随参数的 bm25(),特定的辅助词 映射到排名列的函数可以在每个查询上配置 基础,或者为 FTS 表设置不同的持久默认值。
要更改单个查询的排名列的映射, 与以下任一内容类似的术语将添加到 查询:
rank MATCH 'auxiliary-function-name(arg1, arg2, ...)'
rank = 'auxiliary-function-name(arg1, arg2, ...)'
MATCH 或 = 运算符的右侧必须是常量 计算结果为由辅助函数组成的字符串的表达式 invoke,后跟括号内零个或多个逗号分隔的参数。 参数必须是 SQL 文本。例如:
-- The following queries are logically equivalent. But the second may
-- be faster. See above.
SELECT * FROM fts WHERE fts MATCH ? ORDER BY bm25(fts, 10.0, 5.0);
SELECT * FROM fts WHERE fts MATCH ? AND rank MATCH 'bm25(10.0, 5.0)' ORDER BY rank;
表值函数语法也可用于指定替代方法 排名函数。在这种情况下,描述排名函数的文本应 指定为第二个表值函数参数。以下三个 查询是等效的:
SELECT * FROM fts WHERE fts MATCH ? AND rank MATCH 'bm25(10.0, 5.0)' ORDER BY rank;
SELECT * FROM fts WHERE fts = ? AND rank = 'bm25(10.0, 5.0)' ORDER BY rank;
SELECT * FROM fts WHERE fts(?, 'bm25(10.0, 5.0)') ORDER BY rank;
可以修改表的排名列的默认映射 使用 FTS5 秩配置选项。
6. 特殊 INSERT 命令
6.1. 'automerge' 配置选项
而不是在磁盘上使用单个数据结构来存储全文 index,FTS5 使用一系列 b 树。每次新交易 committed,包含已提交事务内容的新 B 树 写入数据库文件。查询全文索引时,每个 b-tree 必须单独查询并合并结果,然后才能 返回给用户。
为了防止数据库中的 b 树数量变得太多 大(减慢查询速度),较小的 B 树会定期合并到 包含相同数据的单个较大的 B 树。默认情况下,会发生这种情况 自动在 INSERT、UPDATE 或 DELETE 语句中修改 全文索引。“automerge”参数确定有多少个较小的 B 树一次合并在一起。将其设置为小值可以 加快查询速度(因为他们必须查询和合并更少的结果 b-trees),但也会减慢对数据库的写入速度(因为每个 INSERT, UPDATE 或 DELETE 语句必须作为自动的一部分执行更多工作 合并过程)。
构成全文索引的每个 b 树都被分配给一个“级别” 根据其大小。0 级 b 树是最小的,因为它们包含 单个事务的内容。更高级别的 b 树是 将两个或多个 0 级 B 树合并在一起,使它们更大。FTS5型 一旦存在 M 个或多个 b 树,就开始将 b 树合并在一起 具有相同的级别,其中 M 是“自动合并”的值 参数。
“automerge”参数允许的最大值为 16。默认值 值为 4。将“automerge”参数设置为 0 将禁用自动 完全 b 树的增量合并。
INSERT INTO ft(ft, rank) VALUES('automerge', 8);
6.2. 'crisismerge' 配置选项
“crisismerge”选项类似于“automerge”,因为它确定 构成全文索引的组件 B 树的频率和频率 合并在一起。一旦在单个级别上存在 C 或多个 b 树 在全文索引中,其中 C 是“crisismerge”的值 选项,关卡上的所有 B 树都会立即合并到一个 B 树中。
此选项与“自动合并”选项之间的区别在于,当 达到“自动合并”限制 FTS5 仅开始合并 b 树 一起。大部分工作是作为后续 INSERT 的一部分执行的, UPDATE 或 DELETE 操作。而当达到“crisismerge”限制时, 有问题的 B 树都会立即合并。这意味着 INSERT, 触发危机合并的 UPDATE 或 DELETE 可能需要很长时间才能 完成。
默认的“crisismerge”值为 16。没有上限。尝试 将“crisismerge”参数设置为值 0 或 1 等效于 将其设置为默认值 (16)。尝试将 “crisismerge”选项设置为负值。
INSERT INTO ft(ft, rank) VALUES('crisismerge', 16);
6.3. 'delete' 命令
此命令仅适用于外部内容和无内容表。它 用于从 全文索引。此命令和 delete-all 命令是从 无内容表。
为了使用此命令删除行,文本值“delete” 必须插入到与表同名的特殊列中。 要删除的行的 rowid 将插入到 rowid 列中。这 插入到其他列中的值必须与当前值匹配 存储在表中。例如:
-- Insert a row with rowid=14 into the fts5 table.
INSERT INTO ft(rowid, a, b, c) VALUES(14, $a, $b, $c);
-- Remove the same row from the fts5 table.
INSERT INTO ft(ft, rowid, a, b, c) VALUES('delete', 14, $a, $b, $c);
如果值作为“删除”的一部分“插入”到文本列中 命令与当前存储在表中的命令不同, 结果可能是不可预测的。
原因很容易理解:插入文档时 在 FTS5 表中,在全文索引中添加一个条目以记录 每个标记在新文档中的位置。删除文档时, 需要原始数据才能确定以下条目集 需要从全文索引中删除。因此,如果提供给 FTS5 的数据 删除行时,使用此命令与用于删除的行不同 确定插入时的令牌实例集,一些全文 索引条目可能无法正确删除,或者 FTS5 可能会尝试删除索引 不存在的条目。这可以将全文索引保留在 不可预测的状态,使未来的查询结果不可靠。
6.4. 'delete-all' 命令
此命令仅适用于外部内容和无内容表(包括无内容删除表)。它删除了所有 全文索引中的条目。
INSERT INTO ft(ft) VALUES('delete-all');
6.5. 'deletemerge' 配置选项
“deletemerge”选项仅由无内容删除表使用。
从无内容删除表中删除行时,条目 与其代币相关的代币不会立即从 FTS 指数中删除。 相反,包含已删除行的 rowid 的“逻辑删除”标记是 附加到包含行的 FTS 索引条目的 b 树。当 b-tree,任何存在逻辑删除的查询结果行 结果中省略了标记。当 b 树与其他 b 树合并时 b-树,删除的行及其逻辑删除标记都将被丢弃。
此选项指定 b 树中必须 在 B 树符合合并条件之前有墓碑标记 - 通过自动合并或 显式用户“合并”命令 - 即使它 不符合“自动合并”和“用户合并”选项确定的通常标准。
例如,指定 FTS5 应考虑合并组件 15% 行之后的 B 树具有关联的逻辑删除标记:
INSERT INTO ft(ft, rank) VALUES('deletemerge', 15);
此选项的默认值为 10。尝试将其设置为小于 零恢复默认值。将此选项设置为 0 或大于 100 确保 b 树永远不会因逻辑删除而符合合并条件 标记。
6.6. 'integrity-check' 命令
此命令用于验证全文索引是否在内部 一致,并且(可选)它与任何外部内容表一致。
通过插入文本值来调用 integrity-check 命令 'integrity-check' 进入与 FTS5 同名的特殊列 桌子。如果为“rank”列提供了值,则该值必须是 0 或 1。例如:
INSERT INTO ft(ft) VALUES('integrity-check');
INSERT INTO ft(ft, rank) VALUES('integrity-check', 0);
INSERT INTO ft(ft, rank) VALUES('integrity-check', 1);
上述三种形式对于所有 FTS 表都是等效的 不是外部内容表。他们检查索引数据结构是否 没有损坏,并且,如果 FTS 表不是无内容的,则 索引与表本身的内容匹配。
对于外部内容表,索引的内容仅为 与外部内容表的内容相比,如果值 为排名列指定的是 1。
在所有情况下,如果发现任何差异,该命令将失败 出现SQLITE_CORRUPT_VTAB错误。
6.7. 'merge' 命令
INSERT INTO ft(ft, rank) VALUES('merge', 500);
此命令将 b 树结构合并在一起,直到大约 N 页 的合并数据已写入数据库,其中 N 是绝对值 作为“merge”命令的一部分指定的参数的值。的大小 每个页面都由 FTS5 pgsz 选项配置。
如果参数为正值,则 B 树结构仅符合条件 如果满足以下条件之一,则进行合并:
- 在 a 上有 U 或更多这样的 b 树 单级(有关b树级别的说明,请参阅FTS5自动合并选项的文档),其中U是分配的值 到 FTS5 usermerge 选项。
- 合并已经开始(可能是通过“合并”命令 指定负参数)。
可以判断“合并”命令是否找到任何 b-trees 通过在执行命令之前和之后检查 sqlite3_total_changes() API 返回的值来合并在一起。如果 两个值之间的差值为 2 或更大,则执行工作。 如果差值小于 2,则“merge”命令是无操作的。在这个 至少没有理由再次执行相同的“合并”命令 直到 FTS 表下次更新。
如果参数为负数,并且 B 树结构超过 FTS 索引中的一个级别,所有 B 树结构都分配给相同的 合并操作开始前的级别。此外,如果参数 为负数,则 usermerge 配置选项的值不是 尊重 - 同一级别的两个 B 树可以合并在一起。
以上意味着执行“merge”命令时,负数 参数,直到 sqlite3_total_changes() 返回值的前后差小于 2 优化 FTS 索引 与 FTS5 optimize 命令相同。但是,如果添加了新的 b 树 在此过程中,FTS5 将移动新的 FTS 指数 b-tree 到与现有 b 树相同的级别,然后重新启动合并。自 避免这种情况,只有第一次调用“merge”才应指定负参数。 每个后续调用“merge”都应指定一个正值,以便 由第一次调用启动的合并将运行到完成,即使新的 B 树是 添加到FTS指数中。
6.8. 'optimize' 命令
此命令合并当前构成 将全文索引转换为单个大型 B 树结构。这确保了 全文索引占用数据库中的最小空间,并且位于 最快的查询表单。
有关更多详细信息,请参阅 FTS5 自动合并选项的文档 关于全文索引与其组成部分之间的关系 B树。
INSERT INTO ft(ft) VALUES('optimize');
因为它重新组织了整个 FTS 索引,所以 optimize 命令可以 需要很长时间才能运行。FTS5 merge 命令可用于除法 将FTS指数优化为多个步骤的工作。为此,请执行以下操作:
- 调用“merge”命令一次,并将参数设置为 -N,然后
- 调用“merge”命令零次或多次,并将参数设置为 N。
其中 N 是每次调用中要合并的数据页数 merge 命令。当 之前 sqlite3_total_changes() 函数返回的值的差异 在合并命令下降到 2 以下之后。合并命令可以是 作为相同或单独交易的一部分发行,并由相同或 不同的数据库客户端。有关详细信息,请参阅合并命令的文档。
6.9. pgsz配置选项
此命令用于设置持久性“pgsz”选项。
FTS5维护的全文索引存储为一系列固定大小的索引 数据库表中的 Blob。对于使 将全文索引设置为相同的大小。pgsz 选项确定大小 由后续索引编写者创建的所有 Blob。默认值为 1000。
INSERT INTO ft(ft, rank) VALUES('pgsz', 4072);
6.10. 'rank' 配置选项
此命令用于设置永久的“rank”选项。
rank 选项用于更改默认辅助函数映射 对于排名列。该选项应设置为相同的文本值 格式,如“rank MATCH?”术语所述 以上。例如:
INSERT INTO ft(ft, rank) VALUES('rank', 'bm25(10.0, 5.0)');
6.11. 'rebuild' 命令
此命令首先删除整个全文索引,然后重新生成它 基于表格或内容的内容 表。它不适用于无内容 表。
INSERT INTO ft(ft) VALUES('rebuild');
6.12. 'secure-delete' 配置选项
此命令用于设置持久布尔值“secure-delete”选项。 例如:
INSERT INTO ft(ft, rank) VALUES('secure-delete', 1);
通常,当 fts5 表中的条目被更新或删除时,取而代之的是 从全文索引中删除条目时,删除键将添加到由 交易。这很有效,但这意味着旧的全文索引 条目将保留在数据库文件中,直到最终被删除 通过对全文索引的合并操作。任何有权访问 数据库可以使用这些条目来简单地重建 删除了 FTS5 表行。但是,如果设置了“安全删除”选项 设置为 1,则在以下情况下,全文条目实际上会从数据库中删除 更新或删除现有的 FTS5 表行。这较慢,但是 它可以防止使用旧的全文条目来重建已删除的全文条目 表行。
此选项可确保旧的全文条目不可用于 具有数据库 SQL 访问权限的攻击者。为了确保他们可以 无法被有权访问 SQLite 数据库文件的攻击者恢复 本身,应用程序还必须启用 SQLite 核心安全删除 选项,带有类似“PRAGMA secure_delete = 1”的命令。
警告:更新一个或多个表行后,或者 如果使用此选项集删除,则 FTS5 表可能不再被读取或 由早于 3.42.0 的任何版本的 FTS5(第一个版本)编写 其中此选项可用)。尝试这样做会导致 错误,并显示错误消息,例如“无效的 FTS5 文件格式(找到 5, 预期 4) - 运行 'rebuild'“。FTS5 文件格式可能会被还原,因此 早期版本的 FTS5 可以通过在表上运行“rebuild”命令来读取它 版本 3.42.0 或更高版本。
secure-delete 选项的默认值为 0。
6.13. 'usermerge' 配置选项
此命令用于设置持久的“usermerge”选项。
usermerge 选项类似于 automerge 和 crisismerge 选项。 它是将合并在一起的最小 b 树段数 带有正参数的“merge”命令。例如:
INSERT INTO ft(ft, rank) VALUES('usermerge', 4);
usermerge 选项的默认值为 4。允许的最小值 为 2,最大值为 16。
7. 扩展 FTS5
FTS5 具有 API,允许通过以下方式对其进行扩展:
- 添加在 C 语言中实现的新辅助函数,以及
- 添加新的分词器,也是用 C 语言实现的。
本文中描述的内置分词器和辅助函数 文档都是使用所描述的公开可用的 API 实现的 下面。
在新的辅助函数或分词器实现之前,可以 在 FTS5 中注册,应用程序必须获得指向“fts5_api”的指针 结构。每个数据库连接都有一个fts5_api结构,具有 FTS5 扩展已注册。若要获取指针,应用程序 使用单个参数调用 SQL 用户定义函数 fts5()。那 参数必须设置为指向指向 fts5_api 对象的指针的指针 使用 sqlite3_bind_pointer() 接口。 以下示例代码演示了该技术:
/*
** Return a pointer to the fts5_api pointer for database connection db.
** If an error occurs, return NULL and leave an error in the database
** handle (accessible using sqlite3_errcode()/errmsg()).
*/
fts5_api *fts5_api_from_db(sqlite3 *db){
fts5_api *pRet = 0;
sqlite3_stmt *pStmt = 0;
if( SQLITE_OK==sqlite3_prepare(db, "SELECT fts5(?1)", -1, &pStmt, 0) ){
sqlite3_bind_pointer(pStmt, 1, (void*)&pRet, "fts5_api_ptr", NULL);
sqlite3_step(pStmt);
}
sqlite3_finalize(pStmt);
return pRet;
}
向后兼容性警告:在 SQLite 版本 3.20.0 (2017-08-01) 之前,fts5() 工作得稍微好 不同。扩展 FTS5 的旧应用程序必须修改才能使用 上面显示的新技术。
fts5_api结构定义如下。它公开了三种方法, 一个用于注册新的辅助函数和分词器,一个用于注册 检索现有分词器。后者旨在促进 类似于内置的“分词器包装器”的实现 Porter 分词器。
typedef struct fts5_api fts5_api;
struct fts5_api {
int iVersion; /* Currently always set to 2 */
/* Create a new tokenizer */
int (*xCreateTokenizer)(
fts5_api *pApi,
const char *zName,
void *pUserData,
fts5_tokenizer *pTokenizer,
void (*xDestroy)(void*)
);
/* Find an existing tokenizer */
int (*xFindTokenizer)(
fts5_api *pApi,
const char *zName,
void **ppUserData,
fts5_tokenizer *pTokenizer
);
/* Create a new auxiliary function */
int (*xCreateFunction)(
fts5_api *pApi,
const char *zName,
void *pUserData,
fts5_extension_function xFunction,
void (*xDestroy)(void*)
);
};
若要调用 fts5_api 对象的方法,fts5_api指针本身 应作为方法的第一个参数,然后是另一个方法 具体的参数。例如:
rc = pFts5Api->xCreateTokenizer(pFts5Api, ... other args ...);
下面将分别介绍fts5_api结构方法 部分。
7.1. 自定义分词器
若要创建自定义分词器,应用程序必须实现三个 函数:分词器构造函数 (xCreate)、析构函数 (xDelete) 和 函数来执行实际的标记化 (xTokenize)。每个的类型 函数与fts5_tokenizer结构的成员变量相同:
typedef struct Fts5Tokenizer Fts5Tokenizer;
typedef struct fts5_tokenizer fts5_tokenizer;
struct fts5_tokenizer {
int (*xCreate)(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut);
void (*xDelete)(Fts5Tokenizer*);
int (*xTokenize)(Fts5Tokenizer*,
void *pCtx,
int flags, /* Mask of FTS5_TOKENIZE_* flags */
const char *pText, int nText,
int (*xToken)(
void *pCtx, /* Copy of 2nd argument to xTokenize() */
int tflags, /* Mask of FTS5_TOKEN_* flags */
const char *pToken, /* Pointer to buffer containing token */
int nToken, /* Size of token in bytes */
int iStart, /* Byte offset of token within input text */
int iEnd /* Byte offset of end of token within input text */
)
);
};
/* Flags that may be passed as the third argument to xTokenize() */
#define FTS5_TOKENIZE_QUERY 0x0001
#define FTS5_TOKENIZE_PREFIX 0x0002
#define FTS5_TOKENIZE_DOCUMENT 0x0004
#define FTS5_TOKENIZE_AUX 0x0008
/* Flags that may be passed by the tokenizer implementation back to FTS5
** as the third argument to the supplied xToken callback. */
#define FTS5_TOKEN_COLOCATED 0x0001 /* Same position as prev. token */
通过调用 FTS5 模块,将实现注册到 fts5_api对象的 xCreateTokenizer() 方法。如果已经有 Tokenizer,它被替换。如果非 NULL xDestroy 参数 传递给 xCreateTokenizer(),它与 pUserData 的副本一起调用 当数据库句柄关闭或 标记器被替换。
如果成功,xCreateTokenizer() 返回 SQLITE_OK。否则,它 返回 SQLite 错误代码。在这种情况下,不会调用 xDestroy 函数。
当 FTS5 表使用自定义分词器时,FTS5 核心调用 xCreate() 一次创建分词器,然后 xTokenize() 零次或多次分词 字符串,然后 xDelete() 释放 xCreate() 分配的任何资源。更多 具体说来:
x创建:
此函数用于分配和初始化分词器实例。 需要一个分词器实例来实际对文本进行分词化。
传递给此函数的第一个参数是 (void*) 的副本 fts5_tokenizer对象时应用程序提供的指针 已向 FTS5 注册(xCreateTokenizer() 的第三个参数)。 第二个和第三个参数是以 nul 结尾的字符串数组 包含分词器参数(如果有),在 分词器名称作为使用的 CREATE VIRTUAL TABLE 语句的一部分 创建 FTS5 表。
最后一个参数是输出变量。如果成功,(*ppOut) 应设置为指向新的分词器句柄并SQLITE_OK 返回。如果发生错误,则应SQLITE_OK以外的某些值 被退回。在本例中,fts5 假设 *ppOut 的最终值 未定义。
x删除:
调用此函数以删除先前的分词器句柄 使用 xCreate() 分配。Fts5 保证此函数将 每次成功调用 xCreate() 时,只调用一次。
xTokenize:
此函数应标记指示的 nText 字节字符串 通过参数 pText。pText 可能是 null 结尾的,也可能不是 nul 结尾的。第一个 传递给此函数的参数是指向 Fts5Tokenizer 对象的指针 由之前对 xCreate() 的调用返回。
第二个参数指示 FTS5 请求的原因 所提供文本的标记化。这始终是以下情况之一 四个值:
- FTS5_TOKENIZE_DOCUMENT - 正在将文档插入到 或从 FTS 表中删除。正在调用分词器 确定要添加到(或从中删除)的标记集 富通指数。
- FTS5_TOKENIZE_QUERY - 正在执行 MATCH 查询 相对于富通指数。正在调用分词器进行分词化 指定为查询的一部分的裸字或带引号的字符串。
- (FTS5_TOKENIZE_QUERY |FTS5_TOKENIZE_PREFIX) - 与 FTS5_TOKENIZE_QUERY,除了裸字或带引号的字符串是 后跟一个“*”字符,表示最后一个标记 分词器返回的将被视为令牌前缀。
- FTS5_TOKENIZE_AUX - 正在调用分词器 满足辅助器发出的 fts5_api.xTokenize() 请求 功能。或者由相同的 fts5_api.xColumnSize() 发出的请求 在 columnsize=0 数据库上。
对于输入字符串中的每个标记,提供的回调 xToken() 必须 被调用。它的第一个参数应该是指针的副本 作为第二个参数传递给 xTokenize()。第三和第四 参数是指向包含标记文本的缓冲区的指针,而 令牌的大小(以字节为单位)。第 4 个和第 5 个参数是字节偏移量 的第一个字节和紧跟在文本后面的第一个字节 令牌是在输入中派生的。
传递给 xToken() 回调的第二个参数 (“tflags”) 应该 通常设置为 0。例外情况是,如果分词器支持 同义词。在这种情况下,有关详细信息,请参阅下面的讨论。
FTS5 假设 xToken() 回调是针对 按它们在输入文本中出现的顺序排列。
如果 xToken() 回调返回除 SQLITE_OK 以外的任何值,则 应该放弃标记化,而 xTokenize() 方法应该 立即返回 xToken() 返回值的副本。或者,如果 输入缓冲区已耗尽,xTokenize() 应返回SQLITE_OK。最后 如果 xTokenize() 实现本身发生错误,则 可能会放弃标记化并返回除 SQLITE_OK或SQLITE_DONE。
7.1.1. 同义词支持
自定义分词器也可能支持同义词。考虑一个案例,其中 用户希望查询诸如“第一名”之类的短语。使用 内置分词器,FTS5 查询“first + place”将匹配实例 在文档集中的“第一名”,但不是替代形式 比如“第一名”。在某些应用程序中,最好匹配 “第一名”或“第一名”的所有实例,无论哪种形式 在 MATCH 查询文本中指定的用户。
在 FTS5 中,有几种方法可以解决这个问题:
- 通过将所有同义词映射到单个标记。在本例中,使用 在上面的例子中,这意味着分词器返回 输入“first”和“1st”的标记相同。假设令牌在 事实“第一”,这样当用户插入文档“我赢了” 第一名“条目被添加到代币”i“、”won“、 “第一”和“地方”。如果用户随后查询“第 1 个 + 地点”, 分词器将“first”替换为“1st”,查询有效 不出所料。
- 通过查询每个查询词的所有同义词的索引 分别。在本例中,在标记查询文本时, Tokenizer 可以为单个术语提供多个同义词 在文档中。然后,FTS5 查询每个 单独同义词。例如,面对查询:
... MATCH 'first place'
分词器提供“1st”和“first”作为 MATCH 查询中的第一个令牌,FTS5 有效地运行查询 似:
... MATCH '(first OR 1st) place'
除了,出于辅助函数的目的,查询 仍然似乎只包含两个短语 - “(第一个或第一个)” 被视为一个短语。
- 通过向 FTS 索引添加单个术语的多个同义词。 使用此方法,在标记文档文本时,标记器 为每个令牌提供多个同义词。这样当一个 诸如“我获得了第一名”之类的文档被标记化,条目是 添加到 FTS 指数中的“i”、“won”、“first”、“1st”和 “地方”。
这样,即使分词器不提供同义词 标记查询文本时(它不应该 - 这样做会是 inefficient),用户是否查询 “第一名 + 第一名”或“第一名 + 第二名”,因为 FTS指数对应于第一种代币的两种形式。
无论是解析文档还是查询文本,任何对 xToken 的调用都会导致 指定具有 FTS5_TOKEN_COLOCATED 位的 tflags 参数 被视为为前一个令牌提供同义词。例如 解析文档“我赢了第一名”时,支持 同义词将调用 xToken() 5 次,如下所示:
xToken(pCtx, 0, "i", 1, 0, 1);
xToken(pCtx, 0, "won", 3, 2, 5);
xToken(pCtx, 0, "first", 5, 6, 11);
xToken(pCtx, FTS5_TOKEN_COLOCATED, "1st", 3, 6, 11);
xToken(pCtx, 0, "place", 5, 12, 17);
第一次指定 FTS5_TOKEN_COLOCATED 标志是错误的 xToken() 被调用。可以为单个令牌指定多个同义词 通过按顺序对 xToken(FTS5_TOKEN_COLOCATED) 进行多次调用。 可以提供的同义词数量没有限制 单个令牌。
在许多情况下,上述方法(1)是最佳方法。它不会添加 额外的数据到 FTS 索引或需要 FTS5 查询多个术语, 因此,它在磁盘空间和查询速度方面是有效的。但是,它 不能很好地支持前缀查询。如果如上所述, token “first” 被 tokenizer 替换为 “1st”,然后是查询:
... MATCH '1s*'
将不匹配包含标记“1st”的文档(作为标记器 可能不会将“1s”映射到“first”的任何前缀)。
对于完整的前缀支持,方法(3)可能是首选。在这种情况下, 由于索引包含“first”和“1st”的条目,因此前缀 诸如“fi*”或“1s*”之类的查询将正确匹配。但是,因为 额外的条目被添加到FTS索引中,此方法使用更多空间 在数据库中。
方法(2)提供了(1)和(3)之间的中点。使用此方法, 诸如“1s*”之类的查询将匹配包含文本的文档 token “1st”,但不是 “first”(假设分词器无法 提供前缀的同义词)。但是,像“1st”这样的非前缀查询 将与“1st”和“first”匹配。此方法不需要 额外的磁盘空间,因为不会向 FTS 索引添加额外的条目。 另一方面,它可能需要更多的 CPU 周期来运行 MATCH 查询, 因为每个同义词都需要对 FTS 索引进行单独的查询。
使用方法 (2) 或 (3) 时,重要的是仅使用分词器 标记化文档文本(方法 (3))或查询时提供同义词 text(方法 (2)),而不是两者兼而有之。这样做不会导致任何错误,但会导致 低 效。
7.2. 自定义辅助函数
实现自定义辅助函数类似于实现标量 SQL 函数。实现 应是 fts5_extension_function 类型的 C 函数,定义如下:
typedef struct Fts5ExtensionApi Fts5ExtensionApi;
typedef struct Fts5Context Fts5Context;
typedef struct Fts5PhraseIter Fts5PhraseIter;
typedef void (*fts5_extension_function)(
const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
Fts5Context *pFts, /* First arg to pass to pApi functions */
sqlite3_context *pCtx, /* Context for returning result/error */
int nVal, /* Number of values in apVal[] array */
sqlite3_value **apVal /* Array of trailing arguments */
);
通过调用 FTS5 模块,将实现注册到 fts5_api对象的 xCreateFunction() 方法。如果已经有一个 辅助函数同名,则替换为新函数。 如果将非 NULL xDestroy 参数传递给 xCreateFunction(),则调用该参数 将 pUserData 指针的副本作为唯一参数传递,当 数据库句柄关闭或注册的辅助函数 取代。
如果成功,xCreateFunction() 返回SQLITE_OK。 否则,它 返回 SQLite 错误代码。在这种情况下,不会调用 xDestroy 函数。
传递给辅助函数回调的最后三个参数是 类似于传递给标量 SQL 实现的三个参数 功能。除了第一个传递给辅助函数的参数外,所有参数都是 可用于 apVal[] 数组中的实现。这 实现应通过内容句柄 pCtx 返回结果或错误。
传递给辅助函数回调的第一个参数是指针 到包含可以调用的方法的结构,以便获得 有关当前查询或行的信息。第二个参数是 不透明的句柄,应作为第一个参数传递给任何此类方法 调用。例如,以下辅助函数定义返回 当前行所有列中的标记总数:
/*
** Implementation of an auxiliary function that returns the number
** of tokens in the current row (including all columns).
*/
static void column_size_imp(
const Fts5ExtensionApi *pApi,
Fts5Context *pFts,
sqlite3_context *pCtx,
int nVal,
sqlite3_value **apVal
){
int rc;
int nToken;
rc = pApi->xColumnSize(pFts, -1, &nToken);
if( rc==SQLITE_OK ){
sqlite3_result_int(pCtx, nToken);
}else{
sqlite3_result_error_code(pCtx, rc);
}
}
以下部分介绍提供给辅助函数的 API 详细实现。更多的例子可以在“fts5_aux.c”中找到 源代码的文件。
7.2.1. 自定义辅助函数 API 参考
struct Fts5ExtensionApi {
int iVersion; /* Currently always set to 3 */
void *(*xUserData)(Fts5Context*);
int (*xColumnCount)(Fts5Context*);
int (*xRowCount)(Fts5Context*, sqlite3_int64 *pnRow);
int (*xColumnTotalSize)(Fts5Context*, int iCol, sqlite3_int64 *pnToken);
int (*xTokenize)(Fts5Context*,
const char *pText, int nText, /* Text to tokenize */
void *pCtx, /* Context passed to xToken() */
int (*xToken)(void*, int, const char*, int, int, int) /* Callback */
);
int (*xPhraseCount)(Fts5Context*);
int (*xPhraseSize)(Fts5Context*, int iPhrase);
int (*xInstCount)(Fts5Context*, int *pnInst);
int (*xInst)(Fts5Context*, int iIdx, int *piPhrase, int *piCol, int *piOff);
sqlite3_int64 (*xRowid)(Fts5Context*);
int (*xColumnText)(Fts5Context*, int iCol, const char **pz, int *pn);
int (*xColumnSize)(Fts5Context*, int iCol, int *pnToken);
int (*xQueryPhrase)(Fts5Context*, int iPhrase, void *pUserData,
int(*)(const Fts5ExtensionApi*,Fts5Context*,void*)
);
int (*xSetAuxdata)(Fts5Context*, void *pAux, void(*xDelete)(void*));
void *(*xGetAuxdata)(Fts5Context*, int bClear);
int (*xPhraseFirst)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*, int*);
void (*xPhraseNext)(Fts5Context*, Fts5PhraseIter*, int *piCol, int *piOff);
int (*xPhraseFirstColumn)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*);
void (*xPhraseNextColumn)(Fts5Context*, Fts5PhraseIter*, int *piCol);
/* Below this point are iVersion>=3 only */
int (*xQueryToken)(Fts5Context*,
int iPhrase, int iToken,
const char **ppToken, int *pnToken
);
int (*xInstToken)(Fts5Context*, int iIdx, int iToken, const char**, int*);
};
无效 *(*xUserData) (Fts5Context*)
返回扩展函数所在的上下文指针的副本 注册。
int (*xColumnTotalSize)(Fts5Context*, int iCol, sqlite3_int64 *pnToken)
如果参数 iCol 小于零,则设置输出变量 *pnToken 到 FTS5 表中的代币总数。或者,如果 iCol 是 非负数但小于表中的列数,返回 iCol 列中的标记总数,考虑 FTS5 表。
如果参数 iCol 大于或等于列数 在表中,返回SQLITE_RANGE。或者,如果发生错误(例如 OOM 条件或 IO 错误),相应的 SQLite 错误代码为 返回。
int (*xColumnCount) (Fts5Context*)
返回表中的列数。
int (*xColumnSize)(Fts5Context*, int iCol, int *pnToken)
如果参数 iCol 小于零,则设置输出变量 *pnToken 到当前行中的令牌总数。或者,如果 iCol 是 非负数但小于表中的列数,设置 *pnToken 设置为当前行的 iCol 列中的令牌数。
如果参数 iCol 大于或等于列数 在表中,返回SQLITE_RANGE。或者,如果发生错误(例如 OOM 条件或 IO 错误),相应的 SQLite 错误代码为 返回。
如果与 FTS5 表一起使用,此函数可能效率很低 使用“columnsize=0”选项创建。
int (*xColumnText)(Fts5Context*, int iCol, const char **pz, int *pn)
如果参数 iCol 小于零,或大于或等于 返回表中的列数,SQLITE_RANGE。
否则,此函数将尝试检索 iCol 列的文本 当前文档。如果成功,则 (*pz) 设置为指向缓冲区 包含 UTF-8 编码的文本,(*pn) 设置为以字节为单位的大小 (不是字符),并返回SQLITE_OK。否则 如果发生错误,则返回 SQLite 错误代码和最终值 of (*pz) 和 (*pn) 未定义。
int (*xPhraseCount)(Fts5Context*)
返回当前查询表达式中的短语数。
int (*xPhraseSize)(Fts5Context*, int iPhrase)
如果参数 iCol 小于零,或大于或等于 当前查询中的短语数,由 xPhraseCount 返回, 返回 0。否则,此函数返回 短语 i查询的短语。短语从零开始编号。
int (*xInstCount) (Fts5Context*, int *pnInst)
将 *pnInst 设置为 * 中所有短语的出现总数 当前行中的查询。如果成功,则返回SQLITE_OK,或者 如果发生错误,则为错误代码(即 SQLITE_NOMEM)。
如果与使用 “detail=none”或“detail=column”选项。如果创建了 FTS5 表 使用“detail=none”或“detail=column”和“content=”选项 (即,如果它是一个无内容的表),那么这个 API 总是返回 0。
int (*xInst)(Fts5Context*, int iIdx, int *piPhrase, int *piCol, int *piOff)
查询当前行中短语匹配 iIdx 的详细信息。 短语匹配项从零开始编号,因此 iIdx 参数 应大于或等于零且小于该值 xInstCount() 输出。如果 iIdx 小于零或大于 或等于 xInstCount() 返回的值,SQLITE_RANGE返回。
否则,输出参数 *piPhrase 设置为短语编号 *piCol 到它出现的列和 *piOff 的标记偏移量 短语的第一个标记。如果成功,则返回SQLITE_OK,或者返回 如果发生错误,则为错误代码(即 SQLITE_NOMEM)。
如果与使用 “detail=none”或“detail=column”选项。
sqlite3_int64 (*xRowid)(Fts5Context*)
返回当前行的 rowid。
int (*xTokenize)(Fts5Context*, 常量字符 *pText, int nText, 无效 *pCtx, int (*xToken)(void*, int, const char*, int, int, int) )
使用属于 FTS5 表的分词器对文本进行分词化。
int (*xQueryPhrase)(Fts5Context*, int iPhrase, void *pUserData, int(*)(const Fts5ExtensionApi*,Fts5Context*,void*) )
该接口用于查询 FTS 表中的短语 iPhrase 当前查询。具体而言,相当于以下内容的查询:
... FROM ftstable WHERE ftstable MATCH $p ORDER BY rowid
将 $p 设置为与短语 iPhrase 等效的短语 执行当前查询。适用于的任何列筛选器 当前查询的短语 iPhrase 包含在$p中。对于每个 行访问,回调函数作为第四个参数传递 被调用。传递给回调的上下文和 API 对象 函数可用于访问每个匹配行的属性。 调用 Api.xUserData() 将返回传递为 pUserData 的第三个参数。
如果参数 iPhrase 小于零,或大于或等于 查询中的短语数,由 xPhraseCount() 返回, 此函数返回SQLITE_RANGE。
如果回调函数返回除 SQLITE_OK 以外的任何值,则 查询被放弃,xQueryPhrase 函数立即返回。 如果返回的值为 SQLITE_DONE,则 xQueryPhrase 返回SQLITE_OK。 否则,错误代码将向上传播。
如果查询运行完成且没有发生任何事件,则返回SQLITE_OK。 或者,如果在查询完成之前发生某些错误或被 回调时,返回SQLite错误码。
int (*xSetAuxdata)(Fts5Context*, void *pAux, void(*xDelete)(void*))
将作为第二个参数传递的指针另存为扩展函数的 “辅助数据”。然后,指针可以被当前或任何 将来调用作为 使用 xGetAuxdata() API 的相同 MATCH 查询。
每个扩展功能都分配了一个辅助数据槽,用于 每个 FTS 查询(MATCH 表达式)。如果调用扩展函数 对于单个 FTS 查询,则所有调用共享一个 单个辅助数据上下文。
如果此函数时已经有辅助数据指针 调用,则将其替换为新指针。如果 xDelete 回调 与原始指针一起指定,此时调用它 点。
xDelete 回调(如果指定了 xDelete 回调)也会在 FTS5 查询完成后的辅助数据指针。
如果此函数中发生错误(例如 OOM 条件), 辅助数据设置为 NULL 并返回错误代码。如果 xDelete 参数不是 NULL,而是在辅助数据上调用 指针,然后返回。
void *(*xGetAuxdata)(Fts5Context*, int bClear)
返回 fts5 扩展的当前辅助数据指针 功能。有关详细信息,请参阅 xSetAuxdata() 方法。
如果 bClear 参数不为零,则清除辅助数据 (设置为 NULL),然后此函数返回。在本例中,xDelete, 如果有,则不调用。
int (*xRowCount)(Fts5Context*, sqlite3_int64 *pnRow)
此函数用于检索表中的总行数。 换言之,将返回的值与以下方法相同:
SELECT count(*) FROM ftstable;
int (*xPhraseFirst)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*, int*)
此函数与类型 Fts5PhraseIter 和 xPhraseNext 一起使用 方法,循环访问 当前行。这与通过 xInstCount/xInst API。而 xInstCount/xInst API 更方便 在某些情况下,此 API 可能会更快。迭代 通过短语 iPhrase 的实例,使用以下代码:
Fts5PhraseIter iter;
int iCol, iOff;
for(pApi->xPhraseFirst(pFts, iPhrase, &iter, &iCol, &iOff);
iCol>=0;
pApi->xPhraseNext(pFts, &iter, &iCol, &iOff)
){
// An instance of phrase iPhrase at offset iOff of column iCol
}
上面定义了 Fts5PhraseIter 结构。应用程序不应 直接修改此结构 - 它只能如上所示使用 使用 xPhraseFirst() 和 xPhraseNext() API 方法(以及 xPhraseFirstColumn() 和 xPhraseNextColumn(),如下图所示)。
如果与使用 “detail=none”或“detail=column”选项。如果创建了 FTS5 表 使用“detail=none”或“detail=column”和“content=”选项 (即如果它是一个无内容的表),那么这个 API 总是迭代 通过一个空集(所有对 xPhraseFirst() 的调用,将 iCol 设置为 -1)。
void (*xPhraseNext)(Fts5Context*, Fts5PhraseIter*, int *piCol, int *piOff)
请参阅上面的 xPhraseFirst。
int (*xPhraseFirstColumn)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*)
此函数和 xPhraseNextColumn() 类似于 xPhraseFirst() 和上面描述的 xPhraseNext() API。不同之处在于,相反 遍历当前行中短语的所有实例,这些 API 用于循环访问当前行中的列集 包含指定短语的一个或多个实例。例如:
Fts5PhraseIter iter;
int iCol;
for(pApi->xPhraseFirstColumn(pFts, iPhrase, &iter, &iCol);
iCol>=0;
pApi->xPhraseNextColumn(pFts, &iter, &iCol)
){
// Column iCol contains at least one instance of phrase iPhrase
}
如果与使用 “detail=none”选项。如果 FTS5 表是使用 “detail=none” “content=” 选项(即如果它是一个无内容的表), 那么这个 API 总是遍历一个空集(所有对 xPhraseFirstColumn() 将 iCol 设置为 -1)。
使用此 API 及其配套工具访问的信息 xPhraseFirstColumn() 也可以使用 xPhraseFirst/xPhraseNext 获得 (或 xInst/xInstCount)。这个 API 的主要优点是它是 与这些替代品一起使用时,效率明显高于这些替代品 “detail=column”表。
void (*xPhraseNextColumn)(Fts5Context*, Fts5PhraseIter*, int *piCol)
请参阅上面的 xPhraseFirstColumn。
int (*xQueryToken)(Fts5Context*, int iPhrase, int iToken, const char **ppToken, int *pnToken )
这用于访问当前短语 iPhrase 的令牌 iToken 查询。返回前,输出参数 *ppToken 设置为 point 添加到包含请求令牌的缓冲区,并将 *pnToken 添加到 此缓冲区的大小(以字节为单位)。
如果 iPhrase 或 iToken 小于零,或者 iPhrase 大于 或等于 查询中报告的短语数 xPhraseCount(),或者如果 iToken 等于或大于 短语 SQLITE_RANGE 中的 tokens 返回,并且返回 *ppToken 和 *pnToken 均归零。
输出文本不是指定 令 牌。它是分词器模块的输出。对于 tokendata=1 表,这包括任何嵌入的0x00和尾随数据。
int (*xInstToken)(Fts5Context*, int iIdx, int iToken, const char**, int*)
这用于访问短语命中 iIdx 中的令牌 iToken 当前行。如果 iIdx 小于零或大于或等于 xInstCount() 返回的值,SQLITE_RANGE返回。否则 输出变量 (*ppToken) 设置为指向包含 将文档令牌和 (*pnToken) 匹配到该缓冲区的大小 字节。如果指定的令牌与 前缀查询词。在这种情况下,两个输出变量始终处于设置状态 更改为 0。
输出文本不是标记化的文档文本的副本。 它是分词器模块的输出。对于 tokendata=1 表,这 包括任何嵌入的0x00和尾随数据。
如果与使用 “detail=none”或“detail=column”选项。
8. fts5vocab 虚拟表模块
fts5vocab 虚拟表模块允许用户从 直接的 FTS5 全文索引。fts5vocab 模块是 FTS5 的一部分 - 它 只要 FTS5 可用。
每个 fts5vocab 表都与一个 FTS5 表相关联。一个 fts5vocab 表通常是通过指定两个参数来代替列名来创建的 在 CREATE VIRTUAL TABLE 语句中 - 关联的 FTS5 表的名称 以及 fts5vocab 表的类型。目前有三种类型的 fts5vocab 桌子;“row”、“col”和“instance”。除非创建了 fts5vocab 表 在“temp”数据库中,它必须与 关联的 FTS5 表。
-- Create an fts5vocab "row" table to query the full-text index belonging
-- to FTS5 table "ft1".
CREATE VIRTUAL TABLE ft1_v USING fts5vocab('ft1', 'row');
-- Create an fts5vocab "col" table to query the full-text index belonging
-- to FTS5 table "ft2".
CREATE VIRTUAL TABLE ft2_v USING fts5vocab(ft2, col);
-- Create an fts5vocab "instance" table to query the full-text index
-- belonging to FTS5 table "ft3".
CREATE VIRTUAL TABLE ft3_v USING fts5vocab(ft3, instance);
如果在临时数据库中创建了 fts5vocab 表,则该表可能是关联的 在任何附加数据库中具有 FTS5 表。为了附加 fts5vocab 表到位于“temp”以外的数据库中的 FTS5 表,则 database 插入到 CREATE VIRTUAL TABLE 的 FTS5 表名之前 参数。例如:
-- Create an fts5vocab "row" table to query the full-text index belonging
-- to FTS5 table "ft1" in database "main".
CREATE VIRTUAL TABLE temp.ft1_v USING fts5vocab(main, 'ft1', 'row');
-- Create an fts5vocab "col" table to query the full-text index belonging
-- to FTS5 table "ft2" in attached database "aux".
CREATE VIRTUAL TABLE temp.ft2_v USING fts5vocab('aux', ft2, col);
-- Create an fts5vocab "instance" table to query the full-text index
-- belonging to FTS5 table "ft3" in attached database "other".
CREATE VIRTUAL TABLE temp.ft2_v USING fts5vocab('aux', ft3, 'instance');
在任何数据库中创建 fts5vocab 表时指定三个参数 除“temp”外,还会导致错误。
“row”类型的 fts5vocab 表包含每个不同术语的一行 在关联的 FTS5 表中。表列如下所示:
列 | 内容 |
---|---|
术语 | 存储在 FTS5 索引中的术语。 |
医生 | 包含至少一个术语实例的行数。 |
碳纳米管 | 整个 FTS5 表中术语的实例总数。 |
“col”类型的 fts5vocab 表包含每个不同术语/列的一行 组合在关联的 FTS5 表中。表列如下所示:
列 | 内容 |
---|---|
术语 | 存储在 FTS5 索引中的术语。 |
山坳 | 包含术语的 FTS5 表列的名称。 |
医生 | FTS5 表中$col列的行数 包含该术语的至少一个实例。 |
碳纳米管 | 出现在 FTS5 表的第 $col 列(考虑所有行)。 |
“instance”类型的 fts5vocab 表包含每个术语的一行 实例存储在关联的 FTS 索引中。假设 FTS5 表是 在将“detail”选项设置为“full”的情况下创建时,表列如下所示:
列 | 内容 |
---|---|
术语 | 存储在 FTS5 索引中的术语。 |
医生 | 包含术语实例的文档的 rowid。 |
山坳 | 包含术语 instance 的列的名称。 |
抵消 | 术语实例在其列中的索引。条款 按出现顺序从 0 开始编号。 |
如果创建 FTS5 表时将“detail”选项设置为“col”,则 实例虚拟表的偏移量列始终包含 NULL。 在本例中,表中每个唯一的术语/doc/col 都有一行 组合。或者,如果创建 FTS5 表时将 'detail' 设置为 'none', 则 offset 和 col 始终包含 NULL 值。为 detail=none FTS5 表,则每个 fts5vocab 表中都有一行 唯一的术语/文档组合。
例:
-- Assuming a database created using:
CREATE VIRTUAL TABLE ft1 USING fts5(c1, c2);
INSERT INTO ft1 VALUES('apple banana cherry', 'banana banana cherry');
INSERT INTO ft1 VALUES('cherry cherry cherry', 'date date date');
-- Then querying the following fts5vocab table (type "col") returns:
--
-- apple | c1 | 1 | 1
-- banana | c1 | 1 | 1
-- banana | c2 | 1 | 2
-- cherry | c1 | 2 | 4
-- cherry | c2 | 1 | 1
-- date | c3 | 1 | 3
--
CREATE VIRTUAL TABLE ft1_v_col USING fts5vocab(ft1, col);
-- Querying an fts5vocab table of type "row" returns:
--
-- apple | 1 | 1
-- banana | 1 | 3
-- cherry | 2 | 5
-- date | 1 | 3
--
CREATE VIRTUAL TABLE ft1_v_row USING fts5vocab(ft1, row);
-- And, for type "instance"
INSERT INTO ft1 VALUES('apple banana cherry', 'banana banana cherry');
INSERT INTO ft1 VALUES('cherry cherry cherry', 'date date date');
--
-- apple | 1 | c1 | 0
-- banana | 1 | c1 | 1
-- banana | 1 | c2 | 0
-- banana | 1 | c2 | 1
-- cherry | 1 | c1 | 2
-- cherry | 1 | c2 | 2
-- cherry | 2 | c1 | 0
-- cherry | 2 | c1 | 1
-- cherry | 2 | c1 | 2
-- date | 2 | c2 | 0
-- date | 2 | c2 | 1
-- date | 2 | c2 | 2
--
CREATE VIRTUAL TABLE ft1_v_instance USING fts5vocab(ft1, instance);
9. FTS5 数据结构
本节简要介绍了 FTS 模块存储其 数据库中的索引和内容。没有必要阅读或理解 本节中的材料,以便在应用程序中使用 FTS。但是,它 对于尝试分析和理解的应用程序开发人员可能有用 FTS 性能特征,或考虑增强 现有的 FTS 功能集。
在数据库中创建 FTS5 虚拟表时,介于 3 到 5 个真实 在数据库中创建表。这些被称为“影子表”,以及 由虚拟表模块用于存储持久性数据。他们不应该 由用户直接访问。许多其他虚拟表模块(包括 FTS3 和 rtree)也会创建和使用影子表。
FTS5 创建以下影子表。在每种情况下,实际的表名 基于 FTS5 虚拟表的名称(在下文中,将 % 替换为虚拟表的名称,以查找实际的影子表名称)。
-- This table contains most of the full-text index data.
CREATE TABLE %_data(id INTEGER PRIMARY KEY, block BLOB);
-- This table contains the remainder of the full-text index data.
-- It is almost always much smaller than the %_data table.
CREATE TABLE %_idx(segid, term, pgno, PRIMARY KEY(segid, term)) WITHOUT ROWID;
-- Contains the values of persistent configuration parameters.
CREATE TABLE %_config(k PRIMARY KEY, v) WITHOUT ROWID;
-- Contains the size of each column of each row in the virtual table
-- in tokens. This shadow table is not present if the "columnsize"
-- option is set to 0.
CREATE TABLE %_docsize(id INTEGER PRIMARY KEY, sz BLOB);
-- Contains the actual data inserted into the FTS5 table. There
-- is one "cN" column for each indexed column in the FTS5 table.
-- This shadow table is not present for contentless or external
-- content FTS5 tables.
CREATE TABLE %_content(id INTEGER PRIMARY KEY, c0, c1...);
以下各节更详细地介绍了这五个表是如何实现的 用于存储 FTS5 数据。
9.1. Varint格式
以下各节引用以“varint”形式存储的 64 位有符号整数。 FTS5 使用与 SQLite 核心在不同地方使用的相同的变体格式。
变量的长度介于 1 到 9 个字节之间。变量由以下任一部分组成 零个或多个字节,其中设置了高阶位,后跟一个字节 高阶位为clear,或9个字节,以较短者为准。下层 前 8 个字节中每个字节的 7 位和第 9 个字节的所有 8 位 用于重建 64 位二进制补码整数。Varints 是 big-endian:从变量的早期字节中获取的位更重要 比从后面的字节中获取的位。
9.2. FTS 指数(%_idx 和 %_data 表)
FTS 索引是一个有序的键值存储,其中键是文档 术语或术语前缀以及关联的值是“doclists”。文档列表是一个 压缩的 varint 数组,用于编码术语的每个实例的位置 在 FTS5 表中。单个项实例的位置定义为 组合:
- 它出现在的 FTS5 表行的 rowid,
- 术语实例所在的列的索引(列是 从零开始从左到右编号),以及
- 列值内项的偏移量(即 在此值之前的列值中显示的标记)。
FTS 索引包含 数据集,其中 nPrefix 是定义的前缀索引的数量。
与主 FTS 索引(不是前缀的索引)关联的键 index) 以字符“0”为前缀。第一个前缀的键 索引以“1”为前缀。第二个前缀索引的键是 以“2”为前缀,依此类推。例如,如果令牌“document”是 插入到 FTS5 表中,其前缀索引由 prefix=“2 4” 指定,则添加到 FTS 索引的键将是 “0document”、“1do”和“2docu”。
FTS 索引条目不存储在单个树或哈希表中 结构。相反,它们存储在一系列不可变的 b 树中,例如 结构称为“段 B 树”。每次写入 FTS5 表被提交,一个或多个(但通常只有一个)新段 b 树 添加包含任何已删除的新条目和逻辑删除 条目。查询 FTS 索引时,读取器会查询每个段 b-tree 依次合并结果,优先考虑较新的数据。
每个段 b 树都分配了一个数值级别。当新区段 b-tree 作为提交事务的一部分写入数据库, 它被分配到级别 0。属于单个级别的段 b 树是 定期合并在一起以创建单个更大的段 B 树 分配给下一个级别(即 0 级段 B 树是 合并为单个 1 级段 B 树)。因此,从数字上讲 较大的级别在(通常)较大的段 B 树中包含较旧的数据。 请参阅“自动合并”、“危机合并”和“用户合并”选项,以及 使用“merge”和“optimize”命令,详细了解如何 控制合并。
如果与术语或术语前缀关联的文档列表非常 大,可能有关联的文档列表 索引。doclist 索引类似于 b 树的内部节点集。 它允许大型文档列表有效地查询 rowid 或范围 吵吵嚷嚷。例如,在处理类似以下查询时:
SELECT ... FROM fts_table('term') WHERE rowid BETWEEN ? AND ?
FTS5 使用段 b 树索引来查找术语“term”的文档列表, 然后使用其 doclist 索引(假设它存在)来有效地识别 与所需范围内的 rowid 匹配的子集。
9.2.1. %_data 表 Rowid 空间
CREATE TABLE %_data(
id INTEGER PRIMARY KEY,
block BLOB
);
%_data 表用于存储三种类型的记录:
- 特殊结构记录, 以 id=1 存储。
- 特殊平均记录, 以 id=10 存储。
- 用于存储每个段 b 树叶和 doclist 索引叶的记录,以及 内部节点。请参阅下文,了解如何计算这些 id 值 记录。
系统中的每个段 b 树都分配有一个唯一的 16 位段 ID。 只有在原始所有者段 b 树 完全合并到更高级别的段 B 树中。在段 b 树中, 每个叶页都分配了一个唯一的页码 - 第一个叶页为 1,2 第二个,依此类推。
每个文档列表索引叶页也分配了一个页码。第一个 文档列表索引中的(最左边)叶页分配的页码与 显示其术语的段 B 树叶页(因为 Doclist 索引 仅为具有很长文档列表的术语创建,每个文档列表最多一个术语 段 B 树叶具有关联的 Doclist 索引)。将此页码称为 P。 如果文档列表太大,需要第二片叶子,则第二片叶子是 分配的页码 P+1。第三片叶子 P+2。文档列表索引的每一层 b-tree(叶子、叶子的父母、祖父母等)被分配了页码 以这种方式,从页码 P 开始。
%_data 表中用于存储任何给定段 b 树的“id”值 leaf 或 doclist 索引 leaf 或 node 的组成如下:
罗维德位 | 内容 |
---|---|
38..43 | (16 位)段 b 树 id 值。 |
37 | (1 位)Doclist 索引标志。设置为文档列表索引页,清除 对于段 B 树叶。 |
32..36 | (5 位)树中的高度。对于段 b 树,此值设置为 0 和 doclist 索引叶子,为 1 表示 doclist 的父母 索引叶,2 个给祖父母等。 |
0..31 | (32 位)页码 |
9.2.2. 结构记录格式
结构记录标识组成 当前的 FTS 指数,以及任何正在进行的增量合并的详细信息 操作。它存储在 id=10 的 %_data 表中。 结构记录以单个 32 位无符号值 - cookie 开头 价值。每次修改结构时,此值都会递增。 cookie 值后面是三个变量值,如下所示:
- 索引中的级别数(即关联的最大级别 与任何段 b 树加 1)。
- 索引中段 b 树的总数。
- 写入级别 0 树的段 b 树叶总数 自 FTS5 表创建以来。
然后,对于从 0 到 nLevel 的每个级别:
- 用作 当前增量合并的输入,如果没有 正在进行的增量合并,以为此级别创建新的段 B 树。
- 关卡上段 b 树的总数。
- 然后,对于每个段 b 树,从最旧到最新:
- 段 ID。
- 第一页的页码(通常为 1,始终为 >0)。
- 最后一页的页码(始终为 >0)。
9.2.3. 平均记录格式
平均值记录,始终以 id=1 存储在 %_data 表中, 不存储任何内容的平均值。相反,它包含的向量 (nCol+1) 填充变量值,其中 nCol 是 FTS5 中的列数 表,包括未编制索引的列。第一个变量包含总数 FTS5 表中的行数。第二个包含 存储在最左侧的 FTS5 表列中的所有值中的令牌。第三个 下一个最左边的所有值中的标记数,依此类推。的值 未编制索引的列始终为零。
9.2.4. 段B树格式
9.2.4.1. key/doclist 格式
key/doclist 格式是一种用于存储一系列键(document 术语或术语前缀以单个字符为前缀,用于标识特定的 它们所属的索引)按排序顺序排列,每个索引都有其关联的索引 文档列表。该格式由交替键和打包在一起的文档列表组成。
第一个密钥存储为:
- 指示键 (N) 中字节数的变量,后跟
- 密钥数据本身(N 字节)。
每个后续密钥存储为:
- 一个变量,指示键具有共同的前缀的大小 使用前一个键(以字节为单位),
- 一个变量,指示键中的字节数,跟在 通用前缀 (N),后跟
- 键后缀数据本身(N 个字节)。
例如,如果 FTS5 密钥/文档列表记录中的前两个密钥是 “0challenger”和“0chandelier”,则第一个键存储为 varint 11 后跟 11 个字节“0challenger”,第二个密钥存储为 varints 4 和 7,后跟 7 个字节“ndelier”。
图1 - 术语/文档列表格式
每个文档列表(通过其 rowid 值)标识包含 术语或术语前缀的至少一个实例以及关联的位置列表, 或“poslist”枚举行中每个术语实例的位置。在 从这个意义上说,“位置”被定义为列号和项偏移量 列值。
在文档列表中,文档始终按 rowid 排序的顺序存储。这 文档列表中的第一个 rowid 按原样存储为 varint。它是立即的 后跟其关联的位置列表。在此之后,区别 在第一个 rowid 和第二个 rowid 之间,作为 varint,然后是 doclist 与 DocList 中的第二个 rowid 相关联。等等。
无法通过解析文档列表来确定文档列表的大小。这必须 存储在外部。请参阅以下部分 如何在 FTS5 中实现这一点的详细信息。
图2 - 文档列表格式
位置列表(通常缩写为“poslist”)标识列 以及相关令牌的每个实例的行内的令牌偏移量。 poslist 的格式为:
- Varint 设置为 poslist 大小的两倍,不包括此字段, 如果在条目上设置了“删除”标志,则加一。
- 第 0 列(最左边的列)的偏移量列表(可能是空的) 行。每个偏移量都存储为 varint。第一个变量包含 第一个偏移量的值加上 2。第二个变体包含 第二次和第一次偏移量之间的差值,加上 2。等。为 例如,如果偏移量列表要包含偏移量 0、10、15 和 16,则 通过打包以下值进行编码,编码为 varints,结尾为 结束:
2, 12, 7, 3
- 对于包含多个实例之一的列 0 以外的每列 令牌的:
- 字节值 0x01。
- 列号,作为 varint。
- 偏移量列表,其格式与列 0 的偏移量列表相同。
图3 - 位置列表(poslist),在列0和i中具有偏移量
9.2.4.2. 分页
如果它足够小(默认情况下,这意味着小于 4000 字节),则 段 B 树的全部内容可以以 key/doclist 格式存储 在上一节中描述为 %_data 表中的单个 Blob。 否则,key/doclist 将拆分为多个页面(默认情况下,大约 每个字节 4000 字节),并存储在 %_data 表中的一组连续条目中 (详见上文)。
当将密钥/文档列表划分为多个页面时,以下修改是 按照格式制作:
- 单个变量或键数据字段永远不会跨越两个页面。
- 每个页面上的第一个键不是前缀压缩的。它存储在 上面描述的文档列表第一个键的格式 - 其大小为 后跟关键数据的变量。
- 如果页面上的第一个键之前有一个或多个 rowid,则 其中第一个不是 Delta 压缩的。它按原样存储,就像 如果它是其文档列表的第一个 rowid(它可能是也可能不是)。
每个页面还具有固定大小的 4 字节页眉和可变大小的页脚。 标头分为 2 个 16 位 big-endian 整数字段。他们 包含:
- 页面上第一个 rowid 值的字节偏移量(如果出现) 在第一个键之前,否则为 0。
- 页脚的字节偏移量。
页脚由一系列包含字节偏移量的变量组成 页面上显示的每个键。页脚的大小为零字节 如果页面上没有键。
图4 - 页面格式
9.2.4.3. 段索引格式
格式化段 b 树的内容的结果 key/doclist 格式,然后将其拆分为页面是非常非常的事情 类似于 B+树的叶子。而不是创建格式 此 b+tree 的内部节点,并将它们存储在 %_data 表中 除了叶子之外,存储在这些节点上的密钥是 添加到 %_idx 表中,定义为:
CREATE TABLE %_idx(
segid INTEGER, -- segment id
term TEXT, -- prefix of first key on page
pgno INTEGER, -- (2*pgno + bDoclistIndex)
PRIMARY KEY(segid, term)
);
对于每个包含至少一个键的“叶子”页面,都会添加一个条目 添加到 %_idx 表中。字段设置如下:
列 | 内容 |
---|---|
赛吉德 | 整数段 id。 |
术语 | 页面上第一个键的最小前缀 大于上一页上的所有键。对于 段中的第一页,此前缀为 0 个字节 大小。 |
PGNO的 | 此字段对页码进行编码(在 segment - 从 1) 和 doclist 索引标志开始。 如果 页面具有关联的 文档列表索引。此字段的值为: (pgno*2 + bDoclistIndexFlag) |
然后,找到段 i 的叶子,该叶子可能包含术语 t,而不是 通过内部节点进行搜索,FTS5 运行查询:
SELECT pgno FROM %_idx WHERE segid=$i AND term>=$t ORDER BY term LIMIT 1
9.2.4.4. Doclist 索引格式
上文中描述的区段索引 部分允许按术语或 假设存在所需大小的前缀索引,则为术语前缀。数据 本节中描述的结构 doclist 索引允许 FTS5 在关联的文档列表中有效地搜索 rowid 或范围或 rowids 具有单个术语或术语前缀。
并非所有键都有关联的文档列表索引。默认情况下,文档列表索引 仅当 Doclist 跨越 4 段 B 树叶时,才会为键添加 页面。Doclist 索引本身就是 b 树,既有叶子,也有内部 节点存储为 %_data 表中的条目,但实际上大多数文档列表是 小到可以放在一片叶子上。FTS5 对 doclist 使用相同的粗略大小 索引节点和叶子,就像对段 B 树叶子一样(默认为 4000 字节)。
文档列表索引叶和内部节点使用相同的页面格式。第一个 byte 是一个“flags”字节。对于文档列表的根页面,此设置为 0x00 索引 b 树,并0x01所有其他页面。页面的其余部分是一个 一系列紧密包装的变质,如下所示:
- 最左边子页的页码,后跟
- 最左侧子页面上的最小 rowid 值,后跟
- 每个后续子页面一个变量,包含以下值:
- 0x00 如果子页面上没有 rowid(这只会发生 当“子”页面实际上是段 B 树叶时),或者
- 子页面上最小的 rowid 与 存储在 DocList 索引页上的上一个 rowid 值。
对于 doclist 索引中最左边的 doclist 索引叶,最左边的子项 page 是包含键的叶子之后的第一个段 b 树叶 本身。
9.3. 文档大小表(%_docsize 表)
CREATE TABLE %_docsize(
id INTEGER PRIMARY KEY, -- id of FTS5 row this record pertains to
sz BLOB -- blob containing nCol packed varints
);
许多常见的搜索结果排名函数都需要输入大小 在结果文档的标记中(因为在短文档中搜索词的命中是 被认为比长文档中的一个更重要)。提供快速 访问此信息,对于 FTS5 表中的每一行,都存在一个 %_docsize 影子表中的相应记录(具有相同的 rowID) 它包含行中每个列值的大小(以标记为单位)。
列值大小存储在包含一个打包变量的 blob 中,用于 FTS5 表的每一列,从左到右。变量包含, 当然,对应列值中的令牌总数。未编入索引 列包含在此 varint 向量中;对他们来说,价值永远是 设置为零。
此表由 xColumnSize API 使用。它可以 通过指定 columnsize=0 选项完全省略。在这种情况下, xColumnSize API 仍可用于辅助函数,但运行更多 慢慢。
9.4. 表格内容 (%_content 表)
CREATE TABLE %_content(id INTEGER PRIMARY KEY, c0, c1...);
实际表内容 - 插入到 FTS5 表中的值为 存储在 %_content 表中。此表由一个“c*”列创建,用于 FTS5 表的每一列,包括任何未编制索引的列。的值 最左边的 FTS5 表列存储在 %_content 的 “c0” 列中 表,“c1”列中下一个 FTS5 表列的值,依此类推。
对于外部内容或无内容的 FTS5 表,将完全省略此表。 表。
9.5. 配置选项(%_config表)
CREATE TABLE %_config(k PRIMARY KEY, v) WITHOUT ROWID;
此表存储任何持久性配置选项的值。 列“k”存储选项的名称(文本),列“v”存储值。 示例内容:
sqlite> SELECT * FROM fts_tbl_config;
┌─────────────┬──────┐
│ k │ v │
├─────────────┼──────┤
│ crisismerge │ 8 │
│ pgsz │ 8000 │
│ usermerge │ 4 │
│ version │ 4 │
└─────────────┴──────┘
附录A:与FTS3/4的比较
此外,还有类似但更成熟的 FTS3/4 模块。 FTS5 是 FTS4 的新版本,包括各种修复和解决方案 在不向后牺牲的情况下无法在 FTS4 中修复的问题 兼容性。下面将介绍其中一些问题。
应用程序移植指南
为了使用 FTS5 而不是 FTS3 或 FTS4,应用程序通常需要 最小的修改。其中大多数分为三类 - 变化 用于创建 FTS 表的 CREATE VIRTUAL TABLE 语句是必需的, 对用于对表执行查询的 SELECT 查询所需的更改, 以及对使用 FTS 辅助功能的应用程序所需的更改。
对 CREATE VIRTUAL TABLE 语句的更改
-
模块名称必须从“fts3”或“fts4”更改为“fts5”。
-
必须从中删除所有类型信息或约束规范 列定义。FTS3/4 忽略列名后面的所有内容 列定义,FTS5 尝试解析它(并将报告错误 如果失败)。
-
“matchinfo=fts3”选项不可用。“columnsize=0”选项是等效的。
-
notindexed= 选项不可用。将 UNINDEXED 添加到列定义是等效的。
-
ICU 分词器不可用。
-
compress=、uncompress= 和 languageid= 选项不可用。 到目前为止,它们的功能还没有等效的。
-- FTS3/4 statement
CREATE VIRTUAL TABLE t1 USING fts4(
linkid INTEGER,
header CHAR(20),
text VARCHAR,
notindexed=linkid,
matchinfo=fts3,
tokenizer=unicode61
);
-- FTS5 equivalent (note - the "tokenizer=unicode61" option is not
-- required as this is the default for FTS5 anyway)
CREATE VIRTUAL TABLE t1 USING fts5(
linkid UNINDEXED,
header,
text,
columnsize=0
);
对 SELECT 语句的更改
-
“docid”别名不存在。应用程序必须使用“rowid” 相反。
-
将列筛选器指定为 FTS 查询的一部分,并使用列作为 MATCH 的 LHS 运算符略有不同。对于包含列“a”和“b”的表 以及类似于以下内容的查询:
... a MATCH 'b: string'
FTS3/4 在“b”列中搜索匹配项。但是,FTS5 始终 返回零行,因为首先对结果进行“B”列筛选,然后 对于“A”列,不留下任何结果。换句话说,在 FTS3/4 中, 内部过滤器覆盖外部过滤器,在 FTS5 中应用两个过滤器。
-
FTS 查询语法(MATCH 运算符的右侧)具有 在某些方面发生了变化。FTS5 语法与 FTS4 非常接近 “增强语法”。主要区别在于 FTS5 更挑剔 关于查询中无法识别的标点符号和类似字符 字符串。大多数使用 FTS3/4 的查询也应该使用 FTS5 和那些不返回解析错误的人应该返回解析错误。
辅助功能变更
FTS5 没有 matchinfo() 或 offsets() 函数,而 snippet() 函数 不像 FTS3/4 那样功能齐全。但是,由于 FTS5 确实提供 一个 API,允许应用程序创建自定义辅助函数,任何 所需的功能可以在应用程序代码中实现。
FTS5提供的内置辅助功能集可以是 将来会有所改进。
其他问题
-
现在提供了 fts4aux 模块提供的功能 由 fts5vocab 提供。这两个表的架构略有不同。
-
FTS3/4 “merge=X,Y” 命令已替换为 FTS5 merge 命令。
-
FTS3/4 “automerge=X” 命令已替换为 FTS5 自动合并选项。
技术差异总结
FTS5 与 FTS3/4 相似,因为两者的主要任务是维护 从每个唯一令牌到该令牌实例列表的索引映射 在一组文档中,其中每个实例都由文档标识 它出现在其中及其在该文档中的位置。例如:
-- Given the following SQL:
CREATE VIRTUAL TABLE ft USING fts5(a, b);
INSERT INTO ft(rowid, a, b) VALUES(1, 'X Y', 'Y Z');
INSERT INTO ft(rowid, a, b) VALUES(2, 'A Z', 'Y Y');
-- The FTS5 module creates the following mapping on disk:
A --> (2, 0, 0)
X --> (1, 0, 0)
Y --> (1, 0, 1) (1, 1, 0) (2, 1, 0) (2, 1, 1)
Z --> (1, 1, 1) (2, 0, 1)
在上面的示例中,每个三元组标识令牌的位置 实例按 rowid、列号(列按顺序编号) 从 0 开始,从左到右)和列值中的位置( 列值中的第一个标记为 0,第二个标记为 1,依此类推)。使用这个 index,FTS5 能够及时回答诸如“集合 包含标记'A'的所有文档“,或”所有文档的集合” 包含序列'Y Z'“。与 单个令牌称为“实例列表”。
FTS3/4 和 FTS5 的主要区别在于 FTS3/4, 每个 instance-list 都存储为单个大型数据库记录,而 在 FTS5 中,大型实例列表被划分为多个数据库记录。 这对于处理大型数据库具有以下含义: 包含大型列表:
-
FTS5 能够将实例列表以增量方式加载到内存中 以减少内存使用量和峰值分配大小。FTS3/4 非常 通常将整个实例列表加载到内存中。
-
在处理具有多个令牌的查询时,FTS5 是 有时能够确定查询可以通过以下方式回答 检查大型实例列表的子集。FTS3/4 几乎总是 必须遍历整个实例列表。
- 如果实例列表变得如此之大,以至于超过 SQLITE_MAX_LENGTH限制,FTS3/4 无法处理。FTS5型 没有这个问题。
由于这些原因,许多复杂的查询可能使用更少的内存并运行得更快 使用 FTS5。
FTS5 与 FTS3/4 的其他一些不同之处在于:
-
FTS5 支持“ORDER BY rank”,按 相关性降低。
-
FTS5 具有一个 API,允许用户创建自定义辅助设备 用于高级排名和文本处理应用程序的函数。这 特殊的“rank”列可以映射到自定义辅助函数 因此,将“ORDER BY rank”添加到查询中可以按预期工作。
-
FTS5 通过以下方式识别 unicode 分隔符和大小写等效性 违约。这也可以使用 FTS3/4 实现,但必须明确 启用。
-
查询语法已根据需要进行修订,以删除 歧义,并有可能转义特殊字符 在查询术语中。
-
默认情况下,FTS3/4 偶尔会将两个或多个 在 INSERT、UPDATE 或 用户执行的 DELETE 语句。这意味着任何操作 在 FTS3/4 表上可能会变得非常慢,因为 FTS3/4 可能会不可预测地选择将两个或多个大型 B 树合并在一起 在里面。FTS5 默认使用增量合并,这限制了 在任何给定范围内可能发生的处理量 INSERT、UPDATE 或 DELETE 操作。