小. 快速. 可靠.
选择任意三个.
SQLite FTS5扩展
Table Of Contents

1. Overview of FTS5

FTS5是一个SQLite 虚拟表模块 ,可为数据库应用程序提供全文本搜索功能. 全文搜索引擎以其最基本的形式,使用户可以有效地搜索大量文档,以查找包含一个或多个搜索项实例的子集. Google提供给万维网用户的搜索功能尤其是全文搜索引擎,因为它允许用户搜索网络上包含例如" fts5"一词的所有文档.

要使用FTS5,用户将创建一个包含一列或多列的FTS5虚拟表. 例如:

CREATE VIRTUAL TABLE email USING fts5(sender, title, body);

在用于创建FTS5表的CREATE VIRTUAL TABLE语句中添加类型,约束或PRIMARY KEY声明是错误的. 创建FTS5表后,可以像其他任何表一样使用INSERTUPDATEDELETE语句填充FTS5表. 像其他任何没有PRIMARY KEY声明的表一样,FTS5表具有名为rowid的隐式INTEGER PRIMARY KEY字段.

在上面的示例中未显示的是,还有一些选项可以作为CREATE VIRTUAL TABLE语句的一部分提供给FTS5,以配置新表的各个方面. 这些可用于修改FTS5表从文档和查询中提取术语的方式,在磁盘上创建额外的索引以加快前缀查询的速度,或用于创建FTS5表作为对其他位置存储的内容的索引.

填充后,有三种方法可以对FTS5表的内容执行全文查询:

如果使用MATCH或=运算符,则MATCH运算符左侧的表达式通常是FTS5表的名称( 指定column-filter时例外). 右侧的表达式必须是指定要搜索的术语的文本值. 对于表值函数语法,将要搜索的术语指定为第一个表参数. 例如:

-- 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还允许用户搜索包含以下内容的行:

通过提供更复杂的FTS5查询字符串作为MATCH运算符(或=运算符,或作为表值函数语法的第一个参数)右边的文本来请求此类高级搜索. 完整的查询语法在此处描述 .

2. Compiling and Using FTS5

2.1. Building FTS5 as part of SQLite

3.9.0版 (2015-10-14)起,FTTS5已作为SQLite 合并的一部分包含在内. 如果使用两个自动配置构建系统之一,则在运行配置脚本时通过指定" --enable-fts5"选项来启用FTS5. (对于源树配置脚本,当前默认情况下禁用FTS5,对于合并配置脚本,默认情况下启用FTS5,但是这些默认值将来可能会更改.)

或者,如果使用其他构建系统来编译sqlite3.c,则通过安排要定义的SQLITE_ENABLE_FTS5预处理器符号来进行.

2.2. Building a Loadable Extension

或者,可以将FTS5构建为可加载扩展.

规范的FTS5源代码由SQLite源树的" ext / fts5"目录中的一系列* .c和其他文件组成. 构建过程将其减少为仅两个文件-" fts5.c"和" fts5.h"-可用于构建SQLite可加载扩展.

  1. 从化石获取最新的SQLite代码.
  2. 按照如何编译SQLite中所述创建Makefile.
  3. 构建" fts5.c"目标. 这也会创建fts5.h.
$ wget -c http://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"中的代码编译成可加载扩展,或静态链接到应用程序中. 定义了两个入口点,它们都具有相同的作用:

不需要另一个文件" fts5.h"来编译FTS5扩展名. 由实现自定义FTS5标记程序或辅助功能的应用程序使用.

3. Full-text Query Syntax

以下块包含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 Strings

在FTS表达式中,可以通过以下两种方式之一指定字符串

3.2. FTS5 Phrases

FTS查询由短语组成 . 短语是一个或多个标记的有序列表. 通过将字符串传递给FTS表令牌生成器,可以将其转换为短语. 可以使用" +"运算符将两个短语连接成一个大短语. 例如,假设使用的令牌生成器模块将输入" one.two.three"令牌化为三个单独的令牌,则以下四个查询都指定相同的短语:

... MATCH '"one two three"'
... MATCH 'one + two + three'
... MATCH '"one two" + three'
... MATCH 'one.two.three'

如果文档包含至少一个与构成该短语的标记序列匹配的标记子序列,则该短语与文档匹配.

3.3. FTS5 Prefix Queries

如果FTS表达式中的字符串后面跟随" *"字符,则将从字符串中提取的最终标记标记为前缀标记 . 如您所料,前缀标记会匹配任何作为前缀的文档标记. 例如,以下块中的前两个查询将匹配包含令牌" one"的任何文档,紧随其后的是令牌" two",然后是任何以" thr"开头的令牌.

... MATCH '"one two thr" * '
... MATCH 'one + two + thr*'
... MATCH '"one two thr*"'      -- May not work as expected!

上面块中的最终查询可能无法正常工作. 由于" *"字符位于双引号内,因此将其传递给令牌生成器,该令牌生成器可能会将其丢弃(或者,取决于所使用的特定令牌生成器,将其作为最终令牌的一部分包含在内),而不是识别作为特殊的FTS字符.

3.4. FTS5 Initial Token Queries

如果不属于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 Queries

可以将两个或多个短语分组为NEAR组 . , followed by a close parenthesis. NEAR组由标记" NEAR"(区分大小写)指定,后跟一个开放的括号字符,然后是两个或多个由空格分隔的短语,并可选地后面跟一个逗号和数字参数 ,然后是一个封闭的括号. 例如:

... MATCH 'NEAR("one two" "three four", 10)'
... MATCH 'NEAR("one two" thr* + four)'

如果未提供参数,则默认值为10.如果文档包含至少一个以下标记集,则NEAR组与该文档匹配:

  1. 每个短语至少包含一个实例,并且
  2. 在簇中,第一个短语的末尾和最后一个短语的末尾之间的令牌数小于或等于

例如:

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 Column Filters

单个短语或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 Boolean Operators

可以使用布尔运算符将短语和NEAR组排列成表达式. 从最高(最紧密的分组)到最低(最松散的分组)的优先顺序,运算符为:

Operator Function
<query1> NOT <query2> 如果query1匹配而query2不匹配则匹配.
<query1> AND <query2> 如果query1和query2都匹配,则匹配.
<query1> OR <query2> 如果query1或query2匹配,则匹配.

括号可用于对表达式进行分组,以便以通常的方式修改运算符优先级. 例如:

-- 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'

-- Match all documents that contain the token "two" but not "three", or
-- contain the token "one".
... MATCH 'one OR (two NOT three)'

短语和NEAR组也可以通过隐式AND运算符连接. 为简单起见,以上BNF语法中未显示这些内容. 本质上,仅由空格分隔的短语或NEAR组的任何序列(包括限于匹配指定列的序列)都被处理为好像在每对短语或NEAR组之间都存在隐式AND运算符. 隐式AND运算符永远不会插入括号内的表达式之后或之前. 例如:

... 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 OR two) three'    -- Syntax error!
... MATCH 'func(one two)'         -- Syntax error!

4. FTS5 Table Creation and Initialization

作为" CREATE VIRTUAL TABLE ... USING fts5 ..."语句的一部分指定的每个参数都是列声明或配置选项. 列声明由以SQLite可接受的任何方式引用的一个或多个空格分隔的FTS5裸字或字符串文字组成.

列声明中的第一个字符串或裸字是列名. 尝试将fts5表列命名为" rowid"或" rank",或为表本身使用的列分配相同的名称是错误的. 不支持.

列声明中的每个后续字符串或裸字都是用于修改该列行为的列选项. 列选项不区分大小写. 与SQLite核心不同,FTS5将无法识别的列选项视为错误. 当前,唯一识别的选项是" UNINDEXED"(参见下文) .

配置选项由FTS5裸字(选项名称)组成,后跟" ="字符,后跟选项值. 使用单个FTS5裸字或字符串文字(通过SQLite核心可接受的任何方式再次引用)指定选项值. 例如:

CREATE VIRTUAL TABLE mail USING fts5(sender, title, body, tokenize = 'porter ascii');

当前有以下配置选项:

4.1. The UNINDEXED column option

使用UNINDEXED列选项限定的列的内容不会添加到FTS索引中. 这意味着出于MATCH查询和FTS5辅助功能的目的,该列不包含可匹配的标记.

例如,为避免将" uuid"字段的内容添加到FTS索引中:

CREATE VIRTUAL TABLE customers USING fts5(name, addr, uuid UNINDEXED);

4.2. Prefix Indexes

默认情况下,FTS5维护单个索引,该索引记录文档集中每个令牌实例的位置. 这意味着查询完整令牌的速度很快,因为它需要一次查找,但是查询前缀令牌的速度可能很慢,因为它需要范围扫描. 例如,要查询前缀标记" abc *",需要对所有大于或等于" abc"且小于" abd"的标记进行范围扫描.

前缀索引是一个单独的索引,该索引记录了一定长度的前缀令牌的所有实例的位置,这些字符以用于加快对前缀令牌的查询的字符的形式记录. 例如,优化查询以查询前缀标记" abc *"需要使用三个字符前缀的前缀索引.

要将前缀索引添加到FTS5表中,请将" prefix"选项设置为单个正整数或包含一个或多个正整数值的空格分隔列表的文本值. 为每个指定的整数创建一个前缀索引. 如果在单个CREATE VIRTUAL TABLE语句中指定了多个"前缀"选项,则所有选项均适用.

-- Two ways to create an FTS5 table that maintains prefix indexes for
-- two and three character prefix tokens.
CREATE VIRTUAL TABLE ft USING fts5(a, b, prefix='2 3');
CREATE VIRTUAL TABLE ft USING fts5(a, b, prefix=2, prefix=3);

4.3. Tokenizers

CREATE VIRTUAL TABLE" tokenize"选项用于配置FTS5表使用的特定标记器. 选项参数必须是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具有三个内置的令牌生成器模块,在后续各节中进行介绍:

也可以为FTS5创建自定义标记器. 此处描述了用于执行此操作的API.

4.3.1. Unicode61 Tokenizer

Unicode标记生成器将所有Unicode字符分类为"分隔符"或"令牌"字符. 默认情况下,Unicode 6.1定义的所有空格和标点字符都被视为分隔符,所有其他字符都被视为标记字符. 更具体地说,所有分配给以" L"或" N"(特别是字母和数字)开头的常规类别或分配给" Co"类别("其他用途")的所有unicode字符均视为令牌. 所有其他字符都是分隔符.

一个或多个令牌字符的每次连续运行都被视为令牌. 根据Unicode 6.1定义的规则,标记生成器不区分大小写.

默认情况下,变音符号将从所有拉丁脚本字符中删除. 例如,这意味着" A"," a","À","à","Â"和"â"都被认为是等效的.

令牌规范中" unicode61"之后的所有参数均被视为交替的选项名称和值的列表. Unicode61支持以下选项:

Option Usage
remove_diacritics 此选项应设置为" 0"," 1"或" 2". 默认值为" 1". 如果将其设置为" 1"或" 2",则如上所述,从拉丁字母字符中删除变音符号. 但是,如果将其设置为" 1",则在使用单个unicode码点表示一个带有多个变音符号的字符的情况非常少见的情况下,不删除变音符号. 例如,变音符号不会从代码点0x1ED9中删除("带有小圆点和下面的点的拉丁文小写字母O"). 从技术上讲,这是一个错误,但是如果不产生向后兼容性问题就无法修复. 如果此选项设置为" 2",则将从所有拉丁字符中正确删除变音符号.
categories 此选项可用于修改被认为与令牌字符相对应的Unicode常规类别的集合. 该参数必须由以空格分隔的两个字符的常规类别缩写(例如" Lu"或" Nd")列表组成,或者由第二个字符替换为星号(" *")的形式组成,并解释为glob模式. 默认值为" L * N * Co".
tokenchars 此选项用于指定应视为标记字符的其他unicode字符,即使它们是根据Unicode 6.1的空格或标点符号也是如此. 将此选项设置为字符串中的所有字符均视为令牌字符.
separators 此选项用于指定应视为分隔符的其他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 '-_'"
);

or:

-- 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 Tokenizer

Ascii标记生成器与Unicode61标记生成器类似,不同之处在于:

例如:

-- 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 Tokenizer

搬运程序标记器是包装标记器. 它获取其他令牌生成器的输出,并在将每个令牌返回到FTS5之前,对每个令牌应用波特词干算法 . 这允许搜索词(例如"更正")匹配相似的词(例如"更正"或"更正"). 搬运工词干分析器算法仅设计用于英语术语-与其他语言一起使用可能会或可能不会提高搜索实用性.

默认情况下,搬运程序令牌生成器充当默认令牌生成器(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.4. External Content and Contentless Tables

通常,当将一行以及各种全文索引条目和其他数据插入FTS5表时,该行的副本将存储在FTS5模块管理的专用表中. 当用户或辅助功能实现从FTS5表中请求列值时,将从此私有表中读取它们. "内容"选项可用于创建仅存储FTS全文索引条目的FTS5表. 由于列值本身通常比关联的全文本索引条目大得多,因此可以节省大量的数据库空间.

There are two ways to use the "content" option:

4.4.1. Contentless Tables

通过将" content"选项设置为空字符串来创建无内容的FTS5表. 例如:

CREATE VIRTUAL TABLE f1 USING fts5(a, b, c, content='');

无内容的FTS5表不支持UPDATE或DELETE语句,或者不为rowid字段提供非NULL值的INSERT语句. 无内容表不支持REPLACE冲突处理. REPLACE和INSERT OR REPLACE语句被视为常规INSERT语句. 可以使用FTS5 delete命令无内容表中删除行 .

尝试从无内容的FTS5表中读取除rowid之外的任何列值,将返回SQL NULL值.

4.4.2. External Content Tables

通过将content选项设置为同一数据库内的表,虚拟表或视图(以下称为"内容表")的名称,可以创建外部内容FTS5表. 只要FTS5需要列值,它就会按以下方式查询内容表,并将需要值的行的rowid绑定到SQL变量:

SELECT <content_rowid>, <cols> FROM <content> WHERE <content_rowid> = ?;

In the above, <content> is replaced by the name of the content table. By default, <content_rowid> is replaced by the literal text "rowid". Or, if the "content_rowid" option is set within the CREATE VIRTUAL TABLE statement, by the value of that option. <cols> is replaced by a comma-separated list of the FTS5 table column names. For example:

-- 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.5. The Columnsize Option

通常,FTS5在数据库内维护一个特殊的备份表,该表将每个列值的大小存储在插入到单独表中的主FTS5表中的令牌中. 该支持表由xColumnSize API函数使用,而xColumnSize API函数又由内置 bm25排名函数使用 (并且可能对其他排名函数也很有用).

为了节省空间,可以通过将columnize选项设置为零来省略此后备表. 例如:

-- 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);

将columnize选项设置为0或1以外的任何值都是错误的.

如果FTS5表配置为columnize = 0,但不是无内容 ,则xColumnSize API函数仍然有效,但运行速度要慢得多. 在这种情况下,它不是读取值直接从数据库返回,而是读取文本值本身并根据需要对其中的令牌进行计数.

或者,如果该表也是无内容的表 ,则适用以下条件:

存储xColumnSize值的表的名称(除非指定了columnize = 0)是" <名称> _docsize",其中,<名称>是FTS5表本身的名称. 可以在现有数据库上使用sqlite3_analyzer工具,以便通过使用columnize = 0来重新创建FTS5表来确定可以节省多少空间.

4.6. The Detail Option

对于文档中的每个术语,FTS5维护的FTS索引存储文档的rowid,包含该术语的列的列号以及该术语在列值内的偏移量. "详细信息"选项可用于省略某些信息. 这减少了索引在数据库文件中占用的空间,但同时也降低了系统的功能和效率.

详细信息选项可以设置为"完整"(默认值),"列"或"无". 例如:

-- 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);

如果detail选项设置为column ,则对于每个术语,FTS索引仅记录rowid和列号,而忽略术语偏移量信息. 这导致以下限制:

如果detail选项设置为none ,那么对于每个术语,FTS索引仅记录存储的rowid. 列和偏移量信息均被省略. 除了上面对detail = column模式列出的限制外,这还施加了以下额外限制:

在对大量电子邮件(磁盘上的1636 MiB)建立索引的测试中,FTS索引是磁盘上的743 MiB,明细=满,340 MiB的明细=列,134 MiB的明细=无.

5. Auxiliary Functions

辅助函数与SQL标量函数相似,除了它们只能在FTS5表的全文查询(使用MATCH运算符的查询)中使用. 它们的结果不仅基于传递给它们的参数进行计算,而且还基于当前的匹配项和匹配的行进行计算. 例如,辅助函数可能会返回一个数字值,该值指示匹配的准确性(请参见bm25()函数),或者返回包含一个或多个搜索项实例的匹配行中的文本片段(请参见snippet( )功能).

要调用辅助功能,应将FTS5表的名称指定为第一个参数. 其他参数可以在第一个之后,具体取决于所调用的特定辅助功能. 例如,调用"突出显示"功能:

SELECT highlight(email, 2, '<b>', '</b>') FROM email WHERE email MATCH 'fts5'

下节将介绍作为FTS5的一部分提供的内置辅助功能. 应用程序还可以在C中实现自定义辅助功能 .

5.1. Built-in Auxiliary Functions

FTS5提供了三种内置辅助功能:

5.1.1. The bm25() function

内置辅助函数bm25()返回一个实值,该值指示当前行与全文查询的匹配程度. 匹配越好,数值返回的数值越小. 诸如以下的查询可用于从最佳匹配到最差匹配的顺序返回匹配:

SELECT * FROM fts WHERE fts MATCH ? ORDER BY bm25(fts)

为了计算文档分数,将全文查询分为其组成短语. and query is then calculated as follows: 然后,文档和查询的bm25得分计算如下:

In the above, nPhrase is the number of phrases in the query. |D| is the number of tokens in the current document, and avgdl is the average number of tokens in all documents within the FTS5 table. k1 and b are both constants, hard-coded at 1.2 and 0.75 respectively.

在BM25算法的大多数实现中找不到公式开头的" -1"项. 没有它,更好的匹配将被分配更高的BM25分数. 由于默认的排序顺序是"升序",因此这意味着在查询后附加" ORDER BY bm25(fts)"将导致结果按从最差到最好的顺序返回. 为了首先返回最佳匹配,将需要" DESC"关键字. 为了避免这种缺陷,BM25的FTS5实现将结果乘以-1,然后将其返回,以确保为更好的匹配分配较低的分数.

is the inverse-document-frequency of query phrase . 是查询短语的反文档频率. is the total number of rows in the FTS5 table and is the total number of rows that contain at least one instance of phrase : 计算公式如下,其中是FTS5表中的总行数, 是包含至少一个词组实例的行总数:

is the phrase frequency of phrase . 最后, 是短语的短语频率. 默认情况下,这只是该短语在当前行中出现的次数. 但是,通过将额外的实值参数传递给bm25()SQL函数,可以为表的每一列分配不同的权重,并按如下方式计算短语频率:

is the weight assigned to column and is the number of occurrences of phrase in column of the current row. 其中是分配给列的权重, 是短语在当前行的列中出现的次数. 在表名之后传递给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. The highlight() function

Highlight()函数从当前行的指定列返回文本的副本,并插入额外的标记文本以标记短语匹配的开始和结束.

表名称后面必须正好使用三个参数来调用highlight(). 解释如下:

  1. 一个整数,指示要从中读取文本的FTS表列的索引. 列从零开始从左到右编号.
  2. 每个词组匹配之前要插入的文本.
  3. 每个词组匹配后要插入的文本.

例如:

-- 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. The snippet() function

snippet()函数与Highlight()相似,除了它不返回整个列的值,而是自动选择并提取文档文本的一小段以进行处理和返回,这与snippet()函数相似. snippet()函数必须在表名称参数之后传递五个参数:

  1. 一个整数,指示要从中选择返回文本的FTS表列的索引. 列从零开始从左到右编号. 负值表示应自动选择该列.
  2. 要在返回的文本中每个短语匹配之前插入的文本.
  3. 在每个短语匹配后要在返回的文本中插入的文本.
  4. 要添加到所选文本开头或结尾的文本,分别指示所返回的文本不在其列的开头或结尾.
  5. 返回的文本中的最大令牌数. 该值必须大于零且等于或小于64.

5.2. Sorting by Auxiliary Function Results

所有FTS5表均具有一个名为" rank"的特殊隐藏列. 如果当前查询不是全文查询(即,如果它不包含MATCH运算符),则" rank"列的值始终为NULL. 否则,在全文查询中,默认情况下,列级别包含与不带尾随参数执行bm25()辅助函数所返回的值相同的值.

仅在按返回值进行排序时,从等级列读取和直接在查询中使用bm25()函数之间的区别才有意义. 在这种情况下,使用"等级"比使用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;

可以使用每个查询或通过为FTS表设置不同的持久默认值来配置映射到rank列的特定辅助功能,而不是使用不带尾随参数的bm25().

为了更改单个查询的等级列的映射,向查询的WHERE子句添加了类似于以下任一术语的术语:

rank MATCH 'auxiliary-function-name(arg1, arg2, ...)'
rank = 'auxiliary-function-name(arg1, arg2, ...)'

MATCH或=运算符的右侧必须是一个常量表达式,该表达式的结果为一个字符串,该字符串由要调用的辅助函数组成,后跟括号内的零个或多个逗号分隔的参数. 参数必须是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. Special INSERT Commands

6.1. The 'automerge' Configuration Option

FTS5使用一系列b树,而不是使用磁盘上的单个数据结构来存储全文索引. 每次提交新事务时,会将包含已提交事务内容的新b树写入数据库文件中. 当查询全文索引时,必须分别查询每个b树,并将结果合并后再返回给用户.

为了防止数据库中的b树数量过大(降低查询速度),较小的b树会定期合并为包含相同数据的单个较大b树. 默认情况下,这会在修改全文索引的INSERT,UPDATE或DELETE语句中自动发生. "自动合并"参数确定一次合并多少个较小的b树. 将其设置为较小的值可以加快查询速度(因为它们必须查询并合并来自较少b树的结果),但是也可以减慢写入数据库的速度(因为每个INSERT,UPDATE或DELETE语句必须执行更多工作)作为自动合并过程的一部分).

构成全文索引的每个b树均基于其大小分配给"级别". 级别0的b树最小,因为它们包含单个事务的内容. 更高级别的b树是将两个或更多的0级b树合并在一起的结果,因此它们更大. or more b-trees with the same level, where is the value of the 'automerge' parameter. 一旦存在或更多具有相同级别的b树,FTS5就开始将b树合并在一起,其中是'automerge'参数的值.

" automerge"参数的最大允许值为16.默认值为4.将" automerge"参数设置为0将完全禁用b树的自动增量合并.

INSERT INTO ft(ft, rank) VALUES('automerge', 8);

6.2. The 'crisismerge' Configuration Option

" crisismerge"选项类似于" automerge",因为它确定组成全文本索引的组件b树的合并方式和频率. or more b-trees on a single level within the full-text index, where is the value of the 'crisismerge' option, all b-trees on the level are immediately merged into a single b-tree. 在全文索引内的单个级别上存在或多个b树(其中为'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. The 'delete' Command

该命令仅适用于外部内容无内容表. 它用于从全文索引中删除与单行关联的索引条目. 此命令和delete-all命令是从无内容表的全文索引中删除条目的唯一方法.

In order to use this command to delete a row, the text value 'delete' must be inserted into the special column with the same name as the table. The rowid of the row to delete is inserted into the rowid column. The values inserted into the other columns must match the values currently stored in the table. For example:

-- 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. The 'delete-all' Command

该命令仅适用于外部内容无内容表. 它将从全文索引中删除所有条目.

INSERT INTO ft(ft) VALUES('delete-all');

6.5. The 'integrity-check' Command

此命令用于验证全文索引与FTS5表或content表的内容一致. 无内容表不可用.

通过将文本值" integrity-check"插入与FTS5表同名的特殊列中来调用完整性检查命令. 例如:

INSERT INTO ft(ft) VALUES('integrity-check');

如果全文索引与表的内容一致,则用于调用完整性检查命令的INSERT成功. 或者,如果发现任何差异,它将失败并显示SQLITE_CORRUPT_VTAB错误.

6.6. The 'merge' Command

INSERT INTO ft(ft, rank) VALUES('merge', 500);

此命令将b树结构合并在一起,直到大约N页合并数据已写入数据库,其中N是作为"合并"命令的一部分指定的参数的绝对值. 每页的大小由FTS5 pgsz选项配置.

如果参数为正值,则仅当满足以下条件之一时,B树结构才有资格合并:

通过在执行命令之前和之后检查sqlite3_total_changes() API返回的值,可以判断'merge'命令是否找到要合并的b树. 如果两个值之间的差为2或更大,则执行工作. 如果差异小于2,则"合并"命令为无操作. 在这种情况下,没有理由再次执行相同的"合并"命令,至少直到下一次更新FTS表之后.

如果参数为负,并且FTS索引内的B树结构在一个以上的级别上,则在开始合并操作之前,所有B树结构都分配给同一级别. 此外,如果参数为负,则不考虑usermerge配置选项的值-可以将来自同一级别的至少两个b树合并在一起.

上面的意思是执行带有负参数的'merge'命令直到sqlite3_total_changes()的返回值的前后差小于2时,以与FTS5优化命令相同的方式优化FTS索引. 但是,如果在此过程进行过程中将新的b树添加到FTS索引中,则FTS5会将新的b树移动到与现有b树相同的级别,然后重新开始合并. 为避免这种情况,只有第一次调用" merge"时才应指定否定参数. 以后每次对" merge"的调用都应指定一个正值,这样即使将新的b树添加到FTS索引中,由第一个调用开始的合并也将运行到完成.

6.7. The 'optimize' Command

该命令将当前构成全文索引的所有单个b树合并到单个大b树结构中. 这样可以确保全文索引占用数据库中的最小空间,并且以最快的形式查询.

有关全文索引与其组件b树之间的关系的更多详细信息,请参阅FTS5自动合并选项的文档.

INSERT INTO ft(ft) VALUES('optimize');

由于它重组了整个FTS索引,因此优化命令可能需要很长时间才能运行. FTS5 merge命令可用于将优化FTS索引的工作分为多个步骤. 去做这个:

其中N是在合并命令的每次调用中要合并的数据页数. 当合并命令前后sqlite3_total_changes()函数返回的值的差异降至两个以下时,应用程序应停止调用合并. 合并命令可以作为相同或独立事务的一部分,并由相同或不同的数据库客户端发出. 有关更多详细信息,请参考merge命令的文档.

6.8. The 'pgsz' Configuration Option

此命令用于设置持久性" pgsz"选项.

FTS5维护的全文本索引作为一系列固定大小的Blob存储在数据库表中. 组成全文本索引的所有blob都不必严格相同大小. pgsz选项确定由后续索引编写器创建的所有blob的大小. 默认值为1000.

INSERT INTO ft(ft, rank) VALUES('pgsz', 4072);

6.9. The 'rank' Configuration Option

此命令用于设置持久性" rank"选项.

rank选项用于更改rank列的默认辅助功能映射. 该选项应设置为与" rank MATCH?"所述格式相同的文本值. 以上条款. 例如:

INSERT INTO ft(ft, rank) VALUES('rank', 'bm25(10.0, 5.0)');

6.10. The 'rebuild' Command

该命令首先删除整个全文索引,然后根据表或内容表的内容对其进行重建. 无内容表不可用.

INSERT INTO ft(ft) VALUES('rebuild');

6.11. The 'usermerge' Configuration Option

此命令用于设置持久性" usermerge"选项.

usermerge选项类似于自动合并和危机合并选项. 它是带有正参数的"合并"命令将合并在一起的b树片段的最小数量. 例如:

INSERT INTO ft(ft, rank) VALUES('usermerge', 4);

usermerge选项的默认值为4.最小允许值为2,最大为16.

7. Extending FTS5

FTS5具有API,可通过以下方式扩展它:

本文档中描述的内置标记器和辅助功能全部使用下面描述的公共可用API实现.

在向FTS5注册新的辅助功能或令牌生成器实现之前,应用程序必须获取指向" fts5_api"结构的指针. 每个注册FTS5扩展名的数据库连接都有一个fts5_api结构. 为了获取指针,应用程序使用单个参数调用SQL用户定义的函数fts5(). 必须使用sqlite3_bind_pointer()接口将该参数设置为指向fts5_api对象的指针的指针. 下面的示例代码演示了该技术:

/*
** 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, (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结构定义如下. 它公开了三种方法,一种用于注册新的辅助功能和令牌生成器,另一种用于检索现有令牌生成器. 后者旨在促进类似于内置波特标记器的"标记器包装器"的实现.

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 *pContext,
    fts5_tokenizer *pTokenizer,
    void (*xDestroy)(void*)
  );

  /* Find an existing tokenizer */
  int (*xFindTokenizer)(
    fts5_api *pApi,
    const char *zName,
    void **ppContext,
    fts5_tokenizer *pTokenizer
  );

  /* Create a new auxiliary function */
  int (*xCreateFunction)(
    fts5_api *pApi,
    const char *zName,
    void *pContext,
    fts5_extension_function xFunction,
    void (*xDestroy)(void*)
  );
};

要调用fts5_api对象的方法,应将fts5_api指针本身作为方法的第一个参数传递,然后作为其他方法特定的参数传递. 例如:

rc = pFts5Api->xCreateTokenizer(pFts5Api, ... other args ...);

以下各节分别介绍fts5_api结构方法.

7.1. Custom Tokenizers

要创建自定义令牌生成器,应用程序必须实现三个功能:令牌生成器构造函数(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_api对象的xCreateTokenizer()方法向FTS5模块注册该实现. 如果已经存在具有相同名称的令牌生成器,则将其替换. 如果将非NULL xDestroy参数传递给xCreateTokenizer(),则在关闭数据库句柄或替换令牌生成器时,将使用作为唯一参数传递的pContext指针的副本来调用该参数.

如果成功,则xCreateTokenizer()返回SQLITE_OK. 否则,它将返回一个SQLite错误代码. 在这种情况下, 不会调用xDestroy函数.

当FTS5表使用自定义令牌生成器时,FTS5内核将调用一次xCreate()创建一个令牌生成器,然后调用xTokenize()零次或更多次以对字符串进行令牌化,然后调用xDelete()释放由xCreate()分配的任何资源. 进一步来说:

xCreate:

此函数用于分配和初始化令牌生成器实例. 需要标记程序实例才能实际标记文本.

传递给此函数的第一个参数是在fts5_tokenizer对象向FTS5注册时由应用程序提供的(void *)指针的副本(xCreateTokenizer()的第三个参数). 第二个和第三个参数是一个以nul结尾的字符串的数组,其中包含令牌化程序参数(如果有),该令牌化程序参数在令牌化程序名称之后指定,该令牌化程序名称是用于创建FTS5表的CREATE VIRTUAL TABLE语句的一部分.

最后一个参数是输出变量. 如果成功,则应将(* ppOut)设置为指向新的标记生成器句柄,并返回SQLITE_OK. 如果发生错误,则应返回除SQLITE_OK以外的其他值. 在这种情况下,fts5假定* ppOut的最终值未定义.

xDelete:

调用此函数以删除以前使用xCreate()分配的令牌生成器句柄. Fts5保证每次成功调用xCreate()时,该函数将仅被调用一次.

xTokenize:

预期该函数可以对参数pText指示的nText字节字符串进行标记化. pText可以终止也可以不终止. 传递给此函数的第一个参数是指向Fts5Tokenizer对象的指针,该对象是由先前对xCreate()的调用返回的.

第二个参数指示FTS5请求对提供的文本进行标记化的原因. 这始终是以下四个值之一:

  • FTS5_TOKENIZE_DOCUMENT-将文档插入FTS表或从FTS表中删除. 正在调用令牌生成器以确定要添加到FTS索引(或从中删除)的令牌集.

  • FTS5_TOKENIZE_QUERY-正在对FTS索引执行MATCH查询. 调用令牌生成器以令牌化指定为查询一部分的裸字或带引号的字符串.

  • (FTS5_TOKENIZE_QUERY | FTS5_TOKENIZE_PREFIX) -与FTS5_TOKENIZE_QUERY相同,不同之处在于裸字或带引号的字符串后跟一个" *"字符,指示令牌生成器返回的最后一个令牌将被视为令牌前缀.

  • FTS5_TOKENIZE_AUX-调用分词器以满足辅助功能发出的fts5_api.xTokenize()请求. 或者是在columnize = 0数据库上由fts5_api.xColumnSize()请求.

对于输入字符串中的每个令牌,必须调用提供的回调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. Synonym Support

自定义标记器可能还支持同义词. 考虑用户希望查询诸如"第一名"之类的短语的情况. 使用内置的分词器,FTS5查询"第一+位置"将匹配文档集中"第一位置"的实例,但不匹配诸如"第一位置"之类的替代形式. 在某些应用程序中,最好匹配"第一名"或"第一名"的所有实例,而不管用户在MATCH查询文本中指定的哪种形式.

在FTS5中有几种方法可以解决此问题:

  1. 通过将所有同义词映射到单个令牌. 在这种情况下,使用上面的示例,这意味着令牌生成器为输入" first"和" 1st"返回相同的令牌. 假设令牌实际上是"第一",因此,当用户插入文档时,"我赢了第一名"条目将添加到令牌" i","获胜","第一"和"地方"的索引中. 如果用户随后查询" 1st + place",则令牌生成器将" first"替换为" 1st",并且查询将按预期进行.

  2. 通过分别查询每个查询词的所有同义词的索引. 在这种情况下,当标记化查询文本时,标记化器可以为文档中的单个术语提供多个同义词. 然后FTS5分别查询每个同义词的索引. 例如,面对查询:

      ...比赛"第一名"
    

    令牌生成器同时提供" 1st"和" first"作为MATCH查询中第一个令牌的同义词,FTS5有效地运行类似于以下查询:

      ...匹配"(第一个或第一个)位置"
    

    除此之外,出于辅助功能的目的,查询似乎仍然只包含两个短语-"(第一个或第一个)"被视为单个短语.

  3. 通过将单个术语的多个同义词添加到FTS索引. 使用此方法,在标记文档文本时,标记生成器为每个标记提供多个同义词. 因此,当对诸如"我赢得第一名"之类的文档进行标记时,会将条目添加到FTS索引中" i","获胜","第一","第一"和"位置".

    这样,即使令牌化程序在对查询文本进行令牌化时不提供同义词(这样做也不应该这样做-这样做效率低下),但是用户查询"第一个+位置"还是"第一个+位置"都没有关系,因为FTS索引中存在与两种形式的第一个令牌相对应的条目.

argument with the FTS5_TOKEN_COLOCATED bit is considered to supply a synonym for the previous token. 无论是解析文档还是查询文本,对任何使用FTS5_TOKEN_COLOCATED位指定参数的xToken的调用都被视为提供了先前标记的同义词. 例如,当解析文档"我赢得第一名"时,支持同义词的令牌生成器将调用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);

在首次调用xToken()时指定FTS5_TOKEN_COLOCATED标志是错误的. 通过依次调用xToken(FTS5_TOKEN_COLOCATED),可以为单个令牌指定多个同义词. 对于单个令牌可以提供的同义词数量没有限制.

在许多情况下,上述方法(1)是最好的方法. 它不会在FTS索引中添加额外的数据,也不会要求FTS5查询多个术语,因此在磁盘空间和查询速度方面非常有效. 但是,它不能很好地支持前缀查询. 如上所述,如果令牌生成器将令牌" first"替换为" 1st",则查询:

... MATCH '1s*'

不会匹配包含令牌" 1st"的文档(因为令牌生成器可能不会将" 1s"映射到" first"的任何前缀).

对于全前缀支持,方法(3)可能是首选. 在这种情况下,由于索引包含" first"和" 1st"的条目,因此前缀查询(例如" fi *"或" 1s *")将正确匹配. 但是,由于将额外的条目添加到FTS索引,因此该方法使用数据库中的更多空间.

Method (2) offers a midpoint between (1) and (3). Using this method, a query such as '1s*' will match documents that contain the literal token "1st", but not "first" (assuming the tokenizer is not able to provide synonyms for prefixes). However, a non-prefix query like '1st' will match against "1st" and "first". This method does not require extra disk space, as no extra entries are added to the FTS index. On the other hand, it may require more CPU cycles to run MATCH queries, as separate queries of the FTS index are required for each synonym.

使用方法(2)或(3)时,重要的是,令牌化程序仅在对文档文本(方法(2))或查询文本(方法(3))进行令牌化时才提供同义词. 这样做不会导致任何错误,但是效率很低.

7.2. Custom Auxiliary Functions

实现自定义辅助功能类似于实现标量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_api对象的xCreateFunction()方法向FTS5模块注册该实现. 如果已有同名辅助功能,则将其替换为新功能. 如果将非NULL xDestroy参数传递给xCreateFunction(),则在关闭数据库句柄或替换已注册的辅助函数时,将使用作为唯一参数传递的pContext指针的副本来调用该参数.

如果成功,则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. Custom Auxiliary Functions API Reference

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);
};
void *(*xUserData)(Fts5Context*)

返回注册了扩展功能的上下文指针的副本.

int (*xColumnTotalSize)(Fts5Context*, int iCol, sqlite3_int64 *pnToken)

If parameter iCol is less than zero, set output variable *pnToken to the total number of tokens in the FTS5 table. Or, if iCol is non-negative but less than the number of columns in the table, return the total number of tokens in column iCol, considering all rows in the FTS5 table.

如果参数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错误代码.

如果与通过" columnsize = 0"选项创建的FTS5表一起使用,此功能可能效率很低.

int (*xColumnText)(Fts5Context*, int iCol, const char **pz, int *pn)

此函数尝试检索当前文档的iCol列的文本. 如果成功,则将(* pz)设置为指向包含以utf-8编码的文本的缓冲区,将(* pn)设置为以字节(不是字符)为单位的缓冲区大小,并返回SQLITE_OK. 否则,如果发生错误,则返回SQLite错误代码,并且(* pz)和(* pn)的最终值不确定.

int (*xPhraseCount)(Fts5Context*)

返回当前查询表达式中的短语数.

int (*xPhraseSize)(Fts5Context*, int iPhrase)

返回查询短语iPhrase中的令牌数. 短语从零开始编号.

int (*xInstCount)(Fts5Context*, int *pnInst)

将* pnInst设置为当前行中查询内所有短语出现的总数. 如果成功,则返回SQLITE_OK,如果发生错误,则返回错误代码(即SQLITE_NOMEM).

如果与通过" detail = none"或" detail = column"选项创建的FTS5表一起使用,此API可能会非常慢. 如果使用" detail = none"或" detail = column"和" content ="选项创建的FTS5表(即,如果它是一个无内容的表),则此API始终返回0.

int (*xInst)(Fts5Context*, int iIdx, int *piPhrase, int *piCol, int *piOff)

在当前行中查询短语匹配iIdx的详细信息. 词组匹配的编号从零开始,因此iIdx参数应大于或等于零且小于xInstCount()输出的值.

通常,将输出参数* piPhrase设置为短语编号,将* piCol设置为短语出现的列,并将* piOff设置为短语的第一个标记的标记偏移量. 如果成功,则返回SQLITE_OK;如果发生错误,则返回错误代码(即SQLITE_NOMEM).

如果与通过" detail = none"或" detail = column"选项创建的FTS5表一起使用,此API可能会非常慢.

sqlite3_int64 (*xRowid)(Fts5Context*)

返回当前行的rowid.

int (*xTokenize)(Fts5Context*, const char *pText, int nText, void *pCtx, int (*xToken)(void*, int, const char*, int, int, int) )

使用属于FTS5表的标记器标记文本.

int (*xQueryPhrase)(Fts5Context*, int iPhrase, void *pUserData, int(*)(const Fts5ExtensionApi*,Fts5Context*,void*) )

此API函数用于在FTS表中查询当前查询的词组iPhrase. 具体来说,查询等效于:

... FROM ftstable WHERE ftstable MATCH $p ORDER BY rowid

将$ p设置为与当前查询的短语iPhrase相等的短语. $ p中包含适用于当前查询的短语iPhrase的任何列过滤器. 对于访问的每一行,将调用作为第四个参数传递的回调函数. 传递给回调函数的上下文和API对象可用于访问每个匹配行的属性. 调用Api.xUserData()返回作为第三个参数传递给pUserData的指针的副本.

如果回调函数返回的值不是SQLITE_OK,则查询将被放弃,并且xQueryPhrase函数将立即返回. 如果返回的值为SQLITE_DONE,则xQueryPhrase返回SQLITE_OK. 否则,错误代码将向上传播.

如果查询运行完成而没有发生意外,则返回SQLITE_OK. 或者,如果在查询完成或回调终止之前发生了一些错误,则会返回一个SQLite错误代码.

int (*xSetAuxdata)(Fts5Context*, void *pAux, void(*xDelete)(void*))

将作为第二个参数传递的指针保存为扩展功能的"辅助数据". 然后,可以使用xGetAuxdata()API通过作为相同MATCH查询的一部分而进行的相同fts5扩展功能的当前或将来调用来检索指针.

每个扩展功能都为每个FTS查询(MATCH表达式)分配了一个辅助数据时隙. 如果为单个FTS查询多次调用扩展功能,则所有调用共享一个辅助数据上下文.

If there is already an auxiliary data pointer when this function is invoked, then it is replaced by the new pointer. If an xDelete callback was specified along with the original pointer, it is invoked at this point.

FTS5查询完成后,还将在辅助数据指针上调用xDelete回调(如果已指定).

如果此函数内发生错误(例如,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表一起使用,此API可能会非常慢. 如果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表一起使用,此API可能会非常慢. 如果FTS5表是使用" detail = none"" content ="选项创建的(即,如果它是无内容的表),则此API始终会迭代一个空集(所有对xPhraseFirstColumn()的调用都将iCol设置为-1) .

使用此API及其配套的xPhraseFirstColumn()访问的信息也可以使用xPhraseFirst / xPhraseNext(或xInst / xInstCount)获得. 此API的主要优点是,与" detail = column"表一起使用时,它比那些替代方法更有效.

void (*xPhraseNextColumn)(Fts5Context*, Fts5PhraseIter*, int *piCol)

请参阅上面的xPhraseFirstColumn.

8. The fts5vocab Virtual Table Module

fts5vocab虚拟表模块允许用户直接从FTS5全文索引中提取信息. fts5vocab模块是FTS5的一部分-每当有FTS5时都可用.

每个fts5vocab表都与一个FTS5表关联. 通常,通过在CREATE VIRTUAL TABLE语句中指定两个参数来代替列名来创建fts5vocab表-关联的FTS5表的名称和fts5vocab表的类型. 目前,fts5vocab表有三种类型: "行"," col"和"实例". 除非在" temp"数据库​​中创建了fts5vocab表,否则它必须与关联的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表上,在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');

在除" temp"以外的任何数据库中创建fts5vocab表时,指定三个参数会导致错误.

An fts5vocab table of type "row" contains one row for each distinct term in the associated FTS5 table. The table columns are as follows:

ColumnContents
term 该术语,存储在FTS5索引中.
doc The number of rows that contain at least one instance of the term.
cnt 整个FTS5表中该术语的实例总数.

类型为" col"的fts5vocab表包含用于相关FTS5表中每个不同术语/列组合的一行. 表列如下:

ColumnContents
term 该术语,存储在FTS5索引中.
col 包含该术语的FTS5表列的名称.
doc FTS5表中$ col列包含该术语的至少一个实例的行数.
cnt 显示在FTS5表的$ col列中的术语的实例总数(考虑所有行).

类型为" instance"的fts5vocab表针对存储在关联的FTS索引中的每个术语实例包含一行. 假设创建的FTS5表的'detail'选项设置为'full',则表列如下:

ColumnContents
term 该术语,存储在FTS5索引中.
doc 包含术语实例的文档的rowid.
col 包含术语实例的列的名称.
offset 术语实例在其列中的索引. 术语按从0开始的出现顺序编号.

column of an instance virtual table always contains NULL. 如果创建的FTS5表的'detail'选项设置为'col',则实例虚拟表的列始终包含NULL. 在这种情况下,表格中的每个唯一术语/文档/列组合只有一行. and always contain NULL values. 或者,如果在将"详细信息"设置为"无"的情况下创建FTS5表,则和始终都包含NULL值. 对于detail = none FTS5表,对于每个唯一的term / doc组合,在fts5vocab表中都有一行.

Example:

-- 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);

Appendix A: Comparison with FTS3/4

也提供类似但更成熟的FTS3 / 4模块. FTS5是FTS4的新版本,其中包括各种修复程序和解决方案,这些问题和解决方案在不牺牲向后兼容性的情况下无法在FTS4中解决. 其中一些问题描述如下 .

Application Porting Guide

为了使用FTS5代替FTS3或FTS4,应用程序通常需要进行最少的修改. 其中大多数分为三类-用于创建FTS表的CREATE VIRTUAL TABLE语句所需的更改,用于对表执行查询的SELECT查询所需的更改以及使用FTS辅助功能的应用程序所需的更改.

Changes to CREATE VIRTUAL TABLE statements

  1. 模块名称必须从" fts3"或" fts4"更改为" fts5".

  2. 必须从列定义中删除所有类型信息或约束规范. FTS3 / 4会忽略列定义中列名称后面的所有内容,FTS5尝试对其进行解析(如果失败,则会报告错误).

  3. " matchinfo = fts3"选项不可用. " columnsize = 0"选项是等效的.

  4. notindexed =选项不可用. 将UNINDEXED添加到列定义是等效的.

  5. ICU令牌生成器不可用.

  6. 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
);

Changes to SELECT statements

  1. " docid"别名不存在. 应用程序必须使用" rowid"来代替.

  2. 将列过滤器既指定为FTS查询的一部分,又将列用作MATCH运算符的LHS时的查询行为略有不同. 对于具有" a"和" b"列的表,以及与以下查询类似的查询:

    ... a MATCH 'b: string'
    

    FTS3 / 4在" b"列中搜索匹配项. 但是,FTS5始终返回零行,因为首先对列" b"过滤结果,然后对" a"列进行过滤,而没有结果. 换句话说,在FTS3 / 4中,内部过滤器覆盖外部过滤器,在FTS5中,两个过滤器都应用.

  3. FTS查询语法(MATCH运算符的右侧)以某些方式发生了变化. FTS5语法非常类似于FTS4"增强语法". 主要区别在于FTS5对于无法识别的标点字符和查询字符串中的类似字符都比较挑剔. 大多数与FTS3 / 4一起使用的查询也应该与FTS5一起使用,而那些不应该返回解析错误的查询.

Auxiliary Function Changes

FTS5没有matchinfo()或offsets()函数,并且snippet()函数的功能不如FTS3 / 4中的功能完整. 但是,由于FTS5确实提供了允许应用程序创建自定义辅助功能的API,因此可以在应用程序代码内实现任何必需的功能.

FTS5提供的内置辅助功能集可能会在将来得到改进.

Other Issues

  1. fts5vocab现在提供了fts4aux模块提供的功能 . 这两个表的架构略有不同.

  2. FTS3 / 4" merge = X,Y"命令已由FTS5合并命令替换.

  3. FTS3 / 4" automerge = X"命令已由FTS5自动合并选项代替.

Summary of Technical Differences

FTS5与FTS3 / 4相似,因为每个FTS5的主要任务是维护从每个唯一令牌到一组文档中该令牌的实例列表的索引映射,其中每个实例都由出现它的文档来标识.及其在该文档中的位置. 例如:

-- 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,依此类推). 使用该索引,FTS5能够及时提供查询答案,例如"包含令牌'A'的所有文档的集合"或"包含序列'Y Z'的所有文档的集合". 与单个令牌关联的实例列表称为"实例列表".

FTS3 / 4和FTS5之间的主要区别在于,在FTS3 / 4中,每个实例列表都存储为单个大型数据库记录,而在FTS5中,大型实例列表被划分为多个数据库记录. 这对于处理包含大列表的大型数据库具有以下含义:

由于这些原因,许多复杂的查询可能使用FTS5占用更少的内存并运行得更快.

FTS5与FTS3 / 4不同的其他一些方式是:

Appendix B: Shadow tables created by FTS5

在数据库中创建FTS5虚拟表时,将在数据库中创建3到5个真实表. 这些被称为" 影子表 ",由虚拟表模块用于存储持久性数据. 用户不应直接访问它们. 许多其他虚拟表模块,包括FTS3rtree ,也可以创建和使用影子表.

FTS5创建以下影子表. 在每种情况下,实际表名均基于FTS5虚拟表的名称(在下表中,将<name>替换为虚拟表的名称以找到实际的影子表名称).

表名Contents
<name>_data 该表包含大多数全文本索引数据.
<name>_idx This table contains the remainder of the full-text index data. It is almost always much smaller than the <name>_data table.
<name>_config 包含持久性配置参数的值.
<name>_content 包含插入FTS5表中的实际数据. 这个影子表不存在用于无内容外部内容 FTS5表.
<name>_docsize 包含令牌中虚拟表中每一行每一列的大小. 如果将" columnsize"选项设置为0,则此影子表不存在.

by  ICOPY.SITE