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
结构的参量。该结构具有以下定义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()
函数中由 MySQL 设置或重置。 -
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()
会按照前面关于st_mysql_ftparser_boolean_info
结构的讨论中所述填充bool_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”。