MySQL 使用 MyISAM
和 InnoDB
支持服务器端全文解析器插件。有关全文解析器插件的介绍性信息,请参阅 全文解析器插件.
可以使用全文解析器插件来替换或修改内置的全文解析器。本节介绍如何编写名为 simple_parser
的全文解析器插件。此插件执行基于比 MySQL 内置全文解析器使用的规则更简单的规则的解析:单词是非空的白字符运行。
说明使用 MySQL 源代码发行版中 plugin/fulltext
目录中的源代码,因此将位置更改为该目录。以下步骤描述了如何创建插件库
-
要编写全文解析器插件,请在插件源文件中包含以下头文件。根据插件的功能和要求,可能还需要其他 MySQL 或通用头文件。
#include <mysql/plugin.h>
plugin.h
定义了MYSQL_FTPARSER_PLUGIN
服务器插件类型以及声明插件所需的数据结构。 -
为插件库文件设置库描述符。
此描述符包含服务器插件的通用插件描述符。对于全文解析器插件,类型必须为
MYSQL_FTPARSER_PLUGIN
。这是标识插件在创建FULLTEXT
索引时在WITH PARSER
子句中合法使用的值。(此子句中没有其他插件类型是合法的。)例如,包含名为
simple_parser
的单个全文解析器插件的库的库描述符如下所示mysql_declare_plugin(ftexample) { MYSQL_FTPARSER_PLUGIN, /* type */ &simple_parser_descriptor, /* descriptor */ "simple_parser", /* name */ "Oracle Corporation", /* author */ "Simple Full-Text Parser", /* description */ PLUGIN_LICENSE_GPL, /* plugin license */ simple_parser_plugin_init, /* init function (when loaded) */ simple_parser_plugin_deinit,/* deinit function (when unloaded) */ 0x0001, /* version */ simple_status, /* status variables */ simple_system_variables, /* system variables */ NULL, 0 } mysql_declare_plugin_end;
name
成员 (simple_parser
) 指示在诸如INSTALL PLUGIN
或UNINSTALL PLUGIN
的语句中用于引用插件的名称。这也是SHOW PLUGINS
或INFORMATION_SCHEMA.PLUGINS
显示的名称。有关更多信息,请参阅 第 4.4.2.1 节,“服务器插件库和插件描述符”.
-
设置类型特定的插件描述符。
库描述符中的每个通用插件描述符都指向一个类型特定的描述符。对于全文解析器插件,类型特定的描述符是
plugin.h
文件中st_mysql_ftparser
结构的实例struct st_mysql_ftparser { int interface_version; int (*parse)(MYSQL_FTPARSER_PARAM *param); int (*init)(MYSQL_FTPARSER_PARAM *param); int (*deinit)(MYSQL_FTPARSER_PARAM *param); };
如结构定义所示,描述符具有接口版本号并包含指向三个函数的指针。
接口版本号使用符号指定,符号的形式为:
MYSQL_
。对于全文解析器插件,符号是xxx
_INTERFACE_VERSIONMYSQL_FTPARSER_INTERFACE_VERSION
。在源代码中,您将在include/mysql/plugin_ftparser.h
中找到为全文解析器插件定义的实际接口版本号。当前接口版本号是0x0101
。init
和deinit
成员应该指向一个函数,或者如果不需要该函数,则设置为 0。parse
成员必须指向执行解析的函数。在
simple_parser
声明中,该描述符由&simple_parser_descriptor
指示。该描述符指定全文插件接口的版本号(如MYSQL_FTPARSER_INTERFACE_VERSION
所示),以及插件的解析、初始化和反初始化函数static struct st_mysql_ftparser simple_parser_descriptor= { MYSQL_FTPARSER_INTERFACE_VERSION, /* interface version */ simple_parser_parse, /* parsing function */ simple_parser_init, /* parser init function */ simple_parser_deinit /* parser deinit function */ };
全文解析器插件在两种不同的上下文中使用:索引和搜索。在这两种上下文中,服务器在处理导致调用插件的每个 SQL 语句的开始和结束时调用初始化和反初始化函数。但是,在语句处理期间,服务器以特定于上下文的的方式调用主解析函数
对于索引,服务器为要索引的每个列值调用解析器。
对于搜索,服务器调用解析器来解析搜索字符串。解析器还可能被调用以处理语句处理的行。在自然语言模式下,服务器无需调用解析器。对于布尔模式短语搜索或具有查询扩展的自然语言搜索,解析器用于解析列值以获取索引中没有的信息。此外,如果对没有
FULLTEXT
索引的列进行布尔模式搜索,则将调用内置解析器。(插件与特定索引相关联。如果没有索引,则不会使用任何插件。)
通用插件描述符中的插件声明具有指向初始化和反初始化函数的
init
和deinit
成员,类型特定的插件描述符也指向它。但是,这两对函数具有不同的用途,并且由于不同的原因而被调用对于通用插件描述符中的插件声明,初始化和反初始化函数在加载和卸载插件时被调用。
对于类型特定的插件描述符,初始化和反初始化函数被调用以用于插件的每个 SQL 语句。
插件描述符中命名的每个接口函数都应该返回零表示成功,或者返回非零表示失败,并且它们都接收一个指向包含解析上下文的
MYSQL_FTPARSER_PARAM
结构的 arguments。该结构具有以下定义typedef struct st_mysql_ftparser_param { int (*mysql_parse)(struct st_mysql_ftparser_param *, char *doc, int doc_len); int (*mysql_add_word)(struct st_mysql_ftparser_param *, char *word, int word_len, MYSQL_FTPARSER_BOOLEAN_INFO *boolean_info); void *ftparser_state; void *mysql_ftparam; struct charset_info_st *cs; char *doc; int length; int flags; enum enum_ftparser_mode mode; } MYSQL_FTPARSER_PARAM;
结构成员的用途如下
-
mysql_parse
: 指向调用服务器内置解析器的回调函数的指针。当插件充当内置解析器的前端时,请使用此回调。也就是说,当调用插件解析函数时,它应该处理输入以提取文本并将文本传递给mysql_parse
回调。此回调函数的第一个参数应该是
param
值本身param->mysql_parse(param, ...);
前端插件可以提取文本并将其一次性传递给内置解析器,也可以逐段提取和传递文本给内置解析器。但是,在这种情况下,内置解析器会将文本片段视为它们之间存在隐式词语分隔符。
-
mysql_add_word
: 指向向全文索引或搜索词列表添加单词的回调函数的指针。当解析器插件替换内置解析器时,请使用此回调。也就是说,当调用插件解析函数时,它应该将输入解析为单词,并为每个单词调用mysql_add_word
回调。此回调函数的第一个参数应该是
param
值本身param->mysql_add_word(param, ...);
ftparser_state
: 这是一个通用指针。插件可以将其设置为指向要用于其自身目的的内部信息。mysql_ftparam
: 由服务器设置。它作为第一个参数传递给mysql_parse
或mysql_add_word
回调。cs
: 指向有关文本字符集的信息的指针,如果信息不可用,则为 0。doc
: 指向要解析的文本的指针。length
: 要解析的文本的长度,以字节为单位。-
flags
: 解析器标志。如果没有特殊标志,则为零。唯一非零标志是MYSQL_FTFLAGS_NEED_COPY
,这意味着mysql_add_word()
必须保存单词的副本(也就是说,它不能使用指向单词的指针,因为单词位于将被覆盖的缓冲区中。)此标志可能在调用解析器插件之前、由解析器插件本身或由
mysql_parse()
函数设置或重置。 -
mode
: 解析模式。此值将是以下常量之一MYSQL_FTPARSER_SIMPLE_MODE
: 以快速简单模式解析,用于索引和自然语言查询。解析器应该只将应该被索引的单词传递给服务器。如果解析器使用长度限制或停用词列表来确定要忽略的单词,则它不应该将这些单词传递给服务器。MYSQL_FTPARSER_WITH_STOPWORDS
: 以停用词模式解析。这用于布尔搜索中的短语匹配。解析器应该将所有单词传递给服务器,即使是停用词或超出任何正常长度限制的单词。MYSQL_FTPARSER_FULL_BOOLEAN_INFO
: 以布尔模式解析。这用于解析布尔查询字符串。解析器应该不仅识别单词,还应该识别布尔模式运算符,并使用mysql_add_word
回调将它们作为标记传递给服务器。为了告诉服务器正在传递什么类型的标记,插件需要填充MYSQL_FTPARSER_BOOLEAN_INFO
结构并将指向它的指针传递给它。
注意对于
MyISAM
,停用词列表和ft_min_word_len
以及ft_max_word_len
在分词器内部进行检查。对于InnoDB
,停用词列表和等效的词长变量设置(innodb_ft_min_token_size
和innodb_ft_max_token_size
)在分词器外部进行检查。因此,InnoDB
插件解析器不需要检查停用词列表、innodb_ft_min_token_size
或innodb_ft_max_token_size
。相反,建议将所有单词返回到InnoDB
。但是,如果希望在插件解析器中检查停用词,请使用MYSQL_FTPARSER_SIMPLE_MODE
,它适用于全文搜索索引和自然语言搜索。对于MYSQL_FTPARSER_WITH_STOPWORDS
和MYSQL_FTPARSER_FULL_BOOLEAN_INFO
模式,建议将所有单词(包括停用词)返回到InnoDB
,以防出现短语搜索。如果解析器在布尔模式下调用,则
param->mode
值将为MYSQL_FTPARSER_FULL_BOOLEAN_INFO
。解析器用于将标记信息传递到服务器的MYSQL_FTPARSER_BOOLEAN_INFO
结构如下所示typedef struct st_mysql_ftparser_boolean_info { enum enum_ft_token_type type; int yesno; int weight_adjust; char wasign; char trunc; int position; /* These are parser state and must be removed. */ char prev; char *quot; } MYSQL_FTPARSER_BOOLEAN_INFO;
解析器应按如下方式填写结构成员
-
type
:标记类型。下表显示了允许的类型。表 4.3 全文解析器标记类型
标记值 含义 FT_TOKEN_EOF
数据结束 FT_TOKEN_WORD
普通单词 FT_TOKEN_LEFT_PAREN
组或子表达式的开始 FT_TOKEN_RIGHT_PAREN
组或子表达式的结束 FT_TOKEN_STOPWORD
停用词
yesno
:单词是否必须存在才能匹配。0 表示单词是可选的,但如果存在会提高匹配的相关性。大于 0 的值表示单词必须存在。小于 0 的值表示单词必须不存在。weight_adjust
:一个权重因子,决定单词匹配的权重。它可以用来提高或降低单词在相关性计算中的重要性。值为零表示没有权重调整。大于或小于零的值分别表示更高或更低的权重。布尔全文搜索 中使用<
和>
运算符的示例说明了权重的作用。wasign
:权重因子的符号。负值的作用类似于~
布尔搜索运算符,它会导致单词对相关性的贡献为负。trunc
:是否应该像布尔模式*
截断运算符一样进行匹配。position
:单词在文档中的起始位置(以字节为单位)。由InnoDB
全文搜索使用。对于在布尔模式下调用的现有插件,必须为 position 成员添加支持。
插件不应使用
MYSQL_FTPARSER_BOOLEAN_INFO
结构的prev
和quot
成员。注意插件解析器框架不支持
@distance
布尔运算符。一个前导加号 (
+
) 或减号 (-
) 布尔运算符,后跟一个空格,然后是一个单词 ('+ apple'
或'- apple'
)。前导加号或减号必须直接与单词相邻,例如:'+apple'
或'-apple'
。
有关布尔全文搜索运算符的信息,请参见 布尔全文搜索。
-
设置插件接口函数。
库描述符中的通用插件描述符指定了服务器在加载和卸载插件时应调用的初始化和反初始化函数。对于
simple_parser
,这些函数不执行任何操作,只是返回零以指示它们成功static int simple_parser_plugin_init(void *arg __attribute__((unused))) { return(0); } static int simple_parser_plugin_deinit(void *arg __attribute__((unused))) { return(0); }
由于这些函数实际上没有执行任何操作,因此可以省略它们,并在插件声明中为它们分别指定 0。
用于
simple_parser
的类型特定插件描述符指定了服务器在使用插件时调用的初始化、反初始化和解析函数。对于simple_parser
,初始化和反初始化函数不执行任何操作static int simple_parser_init(MYSQL_FTPARSER_PARAM *param __attribute__((unused))) { return(0); } static int simple_parser_deinit(MYSQL_FTPARSER_PARAM *param __attribute__((unused))) { return(0); }
同样,由于这些函数不执行任何操作,因此可以省略它们,并在插件描述符中为它们分别指定 0。
主解析函数
simple_parser_parse()
充当内置全文解析器的替代,因此它需要将文本分割成单词并将每个单词传递到服务器。解析函数的第一个参数是指向包含解析上下文的结构的指针。此结构有一个doc
成员,它指向要解析的文本,以及一个length
成员,它指示文本的长度。插件执行的简单解析将非空空白字符运行视为单词,因此它通过以下方式识别单词static int simple_parser_parse(MYSQL_FTPARSER_PARAM *param) { char *end, *start, *docend= param->doc + param->length; for (end= start= param->doc;; end++) { if (end == docend) { if (end > start) add_word(param, start, end - start); break; } else if (isspace(*end)) { if (end > start) add_word(param, start, end - start); start= end + 1; } } return(0); }
当解析器找到每个单词时,它将调用一个函数
add_word()
将单词传递到服务器。add_word()
只是一个辅助函数;它不是插件接口的一部分。解析器将解析上下文指针传递给add_word()
,以及指向单词的指针和长度值static void add_word(MYSQL_FTPARSER_PARAM *param, char *word, size_t len) { MYSQL_FTPARSER_BOOLEAN_INFO bool_info= { FT_TOKEN_WORD, 0, 0, 0, 0, 0, ' ', 0 }; param->mysql_add_word(param, word, len, &bool_info); }
对于布尔模式解析,
add_word()
将bool_info
结构的成员填写,如前面关于st_mysql_ftparser_boolean_info
结构的讨论中所述。 -
设置状态变量。对于
simple_parser
插件,以下状态变量数组设置了一个状态变量,其值为静态文本,另一个状态变量的值存储在一个长整型变量中long number_of_calls= 0; struct st_mysql_show_var simple_status[]= { {"simple_parser_static", (char *)"just a static text", SHOW_CHAR}, {"simple_parser_called", (char *)&number_of_calls, SHOW_LONG}, {0,0,0} };
通过使用以插件名称开头的状态变量名称,可以使用
SHOW STATUS
轻松显示插件的变量。mysql> SHOW STATUS LIKE 'simple_parser%'; +----------------------+--------------------+ | Variable_name | Value | +----------------------+--------------------+ | simple_parser_static | just a static text | | simple_parser_called | 0 | +----------------------+--------------------+
要编译和安装插件库文件,请使用 第 4.4.3 节“编译和安装插件库” 中的说明。要使库文件可供使用,请将其安装到插件目录(由
plugin_dir
系统变量命名的目录)中。对于simple_parser
插件,它在从源代码构建 MySQL 时进行编译和安装。它也包含在二进制发行版中。构建过程会生成一个名为mypluglib.so
的共享对象库(.so
后缀可能因平台而异)。-
要使用插件,请将其注册到服务器。例如,要在运行时注册插件,请使用以下语句,根据需要调整您的平台的
.so
后缀INSTALL PLUGIN simple_parser SONAME 'mypluglib.so';
有关插件加载的更多信息,请参见 安装和卸载插件。
要验证插件安装,请检查
INFORMATION_SCHEMA.PLUGINS
表或使用SHOW PLUGINS
语句。请参见 获取服务器插件信息。-
测试插件以验证它是否正常工作。
创建一个包含字符串列的表,并将解析器插件与该列上的
FULLTEXT
索引关联起来mysql> CREATE TABLE t (c VARCHAR(255), -> FULLTEXT (c) WITH PARSER simple_parser -> ) ENGINE=MyISAM; Query OK, 0 rows affected (0.01 sec)
在表中插入一些文本并尝试一些搜索。这应该验证解析器插件是否将所有非空格字符视为单词字符
mysql> INSERT INTO t VALUES -> ('utf8mb4_0900_as_cs is a case-sensitive collation'), -> ('I\'d like a case of oranges'), -> ('this is sensitive information'), -> ('another row'), -> ('yet another row'); Query OK, 5 rows affected (0.02 sec) Records: 5 Duplicates: 0 Warnings: 0 mysql> SELECT c FROM t; +--------------------------------------------------+ | c | +--------------------------------------------------+ | utf8mb4_0900_as_cs is a case-sensitive collation | | I'd like a case of oranges | | this is sensitive information | | another row | | yet another row | +--------------------------------------------------+ 5 rows in set (0.00 sec) mysql> SELECT MATCH(c) AGAINST('case') FROM t; +--------------------------+ | MATCH(c) AGAINST('case') | +--------------------------+ | 0 | | 1.2968142032623 | | 0 | | 0 | | 0 | +--------------------------+ 5 rows in set (0.00 sec) mysql> SELECT MATCH(c) AGAINST('sensitive') FROM t; +-------------------------------+ | MATCH(c) AGAINST('sensitive') | +-------------------------------+ | 0 | | 0 | | 1.3253291845322 | | 0 | | 0 | +-------------------------------+ 5 rows in set (0.01 sec) mysql> SELECT MATCH(c) AGAINST('case-sensitive') FROM t; +------------------------------------+ | MATCH(c) AGAINST('case-sensitive') | +------------------------------------+ | 1.3109166622162 | | 0 | | 0 | | 0 | | 0 | +------------------------------------+ 5 rows in set (0.01 sec) mysql> SELECT MATCH(c) AGAINST('I\'d') FROM t; +--------------------------+ | MATCH(c) AGAINST('I\'d') | +--------------------------+ | 0 | | 1.2968142032623 | | 0 | | 0 | | 0 | +--------------------------+ 5 rows in set (0.01 sec)
对于内置解析器,“case” 和 “insensitive” 都无法像匹配 “case-insensitive” 一样匹配。