28.2.4.4 编写全文分析器插件

MySQL 支持带有MyISAMInnoDB的服务器端全文分析器插件。有关全文分析器插件的介绍性信息,请参见全文解析器插件

全文分析器插件可用于替换或修改内置的全文分析器。本节介绍如何编写名为simple_parser的全文分析器插件。该插件根据比 MySQL 内置全文分析器所使用的规则更简单的规则执行分析:单词是空格字符的非空运行。

说明使用 MySQL 源代码发行版plugin/fulltext目录中的源代码,因此将位置更改到该目录中。以下过程描述了如何创建插件库:

  • 要编写全文分析器插件,请在插件源文件中包含以下头文件。根据插件的功能和要求,可能还需要其他 MySQL 或常规头文件。
#include <mysql/plugin.h>

plugin.h定义MYSQL_FTPARSER_PLUGIN服务器插件类型和声明插件所需的数据结构。

  • 设置插件库文件的库 Descriptors。

该 Descriptors 包含服务器插件的常规插件 Descriptors。对于全文分析器插件,类型必须为MYSQL_FTPARSER_PLUGIN。这是一个值,用于在创建FULLTEXT索引时将插件标识为在WITH PARSER子句中合法使用。 (此条款没有其他插件类型合法.)

例如,包含单个名为simple_parser的全文分析器插件的库的库 Descriptors 如下所示:

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)table 示在诸如INSTALL PLUGINUNINSTALL PLUGIN之类的语句中用于引用插件的名称。这也是SHOW PLUGINSINFORMATION_SCHEMA.PLUGINS显示的名称。

有关更多信息,请参见第 28.2.4.2.1 节,“服务器插件库和插件 Descriptors”

  • 设置特定于类型的插件 Descriptors。

库 Descriptors 中的每个常规插件 Descriptors 都指向特定于类型的 Descriptors。对于全文分析器插件,特定于类型的 Descriptors 是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);
};

如结构定义所示,Descriptors 具有接口版本号,并包含指向三个函数的指针。

接口版本号使用符号指定,格式为:MYSQL_xxx_INTERFACE_VERSION。对于全文分析器插件,符号为MYSQL_FTPARSER_INTERFACE_VERSION。在源代码中,您将找到include/mysql/plugin_ftparser.h中定义的全文分析器插件的实际接口版本号。随着对InnoDB的全文语法分析器插件的引入,在 MySQL 5.7 中,接口版本号从0x0100增加到0x0101

initdeinit成员应指向一个函数,如果不需要该函数,则将其设置为 0. parse成员必须指向执行解析的函数。

simple_parser声明中,该 Descriptors 由&simple_parser_descriptor指示。Descriptors 指定了全文插件接口的版本号(由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索引的列进行布尔模式搜索,则将调用内置解析器。 (插件与特定索引相关联.如果没有索引,则不使用插件.)

通用插件 Descriptors 中的插件声明具有initdeinit成员,这些成员指向初始化和反初始化函数,它所指向的特定于类型的插件 Descriptors 也是如此。但是,这些功能对具有不同的用途,并且由于不同的原因而被调用:

  • 对于常规插件 Descriptors 中的插件声明,在加载和卸载插件时会调用初始化和反初始化函数。

  • 对于特定于类型的插件 Descriptors,将针对使用该插件的每个 SQL 语句调用初始化和取消初始化函数。

插件 Descriptors 中命名的每个接口函数都应返回 0table 示成功,否则返回非零值,并且每个接口函数都接收一个参数,该参数指向包含解析上下文的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:指向回调函数的指针,该回调函数调用服务器的内置解析器。当插件充当内置解析器的前端时,请使用此回调。也就是说,当调用插件解析函数时,它应处理 Importing 以提取文本并将文本传递给mysql_parse回调。

此回调函数的第一个参数应为param值本身:

param->mysql_parse(param, ...);

前端插件可以一次提取文本并将其全部传递给内置解析器,或者一次可以一次提取文本并将其传递给内置解析器。但是,在这种情况下,内置解析器将文本片段视为好像它们之间存在隐含的分词符。

  • mysql_add_word:指向回调函数的指针,该回调函数将单词添加到全文索引或搜索词列 table 中。当解析器插件替换内置解析器时,请使用此回调。也就是说,当调用插件解析函数时,它应将 Importing 解析为单词,并为每个单词调用mysql_add_word回调。

此回调函数的第一个参数应为param值本身:

param->mysql_add_word(param, ...);
  • ftparser_state:这是一个通用指针。该插件可以将其设置为指向内部供其自身使用的信息。

  • mysql_ftparam:这是由服务器设置的。它作为第一个参数传递给mysql_parsemysql_add_word回调。

  • cs:指向有关文本字符集的信息的指针;如果无可用信息,则为 0.

  • doc:指向要解析的文本的指针。

  • length:要解析的文本的长度,以字节为单位。

  • flags:解析器标志。如果没有特殊标志,则为零。唯一的非零标志是MYSQL_FTFLAGS_NEED_COPY,这意味着mysql_add_word()必须保存该单词的副本(也就是说,它不能使用指向该单词的指针,因为该单词位于将被覆盖的缓冲区中.)

MySQL 可能会在调用解析器插件之前,解析器插件本身或mysql_parse()函数之前设置或重置此标志。

  • mode:解析模式。该值将是以下常量之一:

  • MYSQL_FTPARSER_SIMPLE_MODE:以快速简单的模式进行解析,该模式用于索引和自然语言查询。解析器应仅将应索引的单词传递给服务器。如果解析器使用长度限制或停用词列 table 来确定要忽略的单词,则不应将此类单词传递给服务器。

    • MYSQL_FTPARSER_WITH_STOPWORDS:以停用词模式解析。在布尔搜索中用于短语匹配。解析器应将所有单词传递给服务器,甚至包括停用词或超出任何正常长度限制的单词。

    • MYSQL_FTPARSER_FULL_BOOLEAN_INFO:以布尔模式解析。这用于解析布尔查询字符串。解析器不仅应识别单词,还应识别布尔模式运算符,并使用mysql_add_word回调将它们作为令牌传递给服务器。为了告诉服务器传递哪种令牌,插件需要填写MYSQL_FTPARSER_BOOLEAN_INFO结构并向其传递指针。

Note

对于MyISAM,在标记器内检查停用词列 table 以及ft_min_word_lenft_max_word_len。对于InnoDB,在标记生成器之外检查停用词列 table 和等效词长变量设置(innodb_ft_min_token_sizeinnodb_ft_max_token_size)。结果,InnoDB插件解析器不需要检查停用词列 tableinnodb_ft_min_token_sizeinnodb_ft_max_token_size。相反,建议将所有单词都返回InnoDB。但是,如果要在插件解析器中检查停用词,请使用MYSQL_FTPARSER_SIMPLE_MODE,它用于全文本搜索索引和自然语言搜索。对于MYSQL_FTPARSER_WITH_STOPWORDSMYSQL_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:令牌类型。下 table 显示了允许的类型。

table28.3 全文分析器令牌类型

Token ValueMeaning
FT_TOKEN_EOF数据结束
FT_TOKEN_WORD普通字
FT_TOKEN_LEFT_PAREN组或子 table 达式的开始
FT_TOKEN_RIGHT_PAREN组或子 table 达式的结尾
FT_TOKEN_STOPWORDA stopword
  • yesno:是否必须存在单词才能进行匹配。 0table 示该单词是可选的,但如果存在则增加匹配的相关性。大于 0 的值 table 示该单词必须存在。小于 0 的值 table 示该单词不能出现。

  • weight_adjust:一个加权系数,用于确定单词计数的匹配程度。它可用于在相关性计算中增加或减少单词的重要性。零值 table 示没有重量调整。大于或小于零的值分别 table 示较高或较低的重量。 第 12.9.2 节“Boolean 全文本搜索”处的示例使用<>运算符说明了加权的工作方式。

  • wasign:加权系数的符号。负值的作用类似于~布尔搜索运算符,它使单词对相关性的贡献为负。

  • trunc:是否应该像给出了布尔模式*截断运算符一样进行匹配。

  • position:单词在文档中的开始位置,以字节为单位。由InnoDB全文搜索使用。对于以布尔模式调用的现有插件,必须添加对 position 成员的支持。

插件不应使用MYSQL_FTPARSER_BOOLEAN_INFO结构的prevquot成员。

Note

插件解析器框架不支持:

  • @distance布尔运算符。

  • 前导加号(+)或减号(-)布尔运算符,后跟一个空格,然后是一个单词('+ apple''- apple')。前导正号或负号必须直接与单词相邻,例如'+apple''-apple'

有关 Boolean 全文本搜索运算符的信息,请参见第 12.9.2 节“Boolean 全文本搜索”

  • 设置插件界面功能。

库 Descriptors 中的常规插件 Descriptors 指定服务器在加载和卸载插件时应调用的初始化和取消初始化函数。对于simple_parser,这些函数不执行任何操作,但返回零 table 示它们已成功:

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的特定于类型的插件 Descriptors 描述了使用插件时服务器调用的初始化,取消初始化和解析功能。对于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);
}

在这里,由于这些功能什么也没做,因此您可以忽略它们,并在插件 Descriptors 中为每个函数指定 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);
}

对于布尔模式分析,如先前在st_mysql_ftparser_boolean_info结构的讨论中所述,add_word()填充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                  |
+----------------------+--------------------+
  • 要编译和安装插件库文件,请使用第 28.2.4.3 节“编译和安装插件库”中的说明。要使该库文件可供使用,请将其安装在插件目录(由plugin_dir系统变量命名的目录)中。对于simple_parser插件,当您从源代码构建 MySQL 时会对其进行编译和安装。它也包含在二进制发行版中。构建过程将生成一个共享对象库,其名称为mypluglib.so(后缀.so可能因平台而异)。

  • 要使用插件,请在服务器上注册。例如,要在运行时注册插件,请使用以下语句,并根据需要调整平台的.so后缀:

INSTALL PLUGIN simple_parser SONAME 'mypluglib.so';

有关插件加载的其他信息,请参见第 5.5.1 节“安装和卸载插件”

创建一个包含字符串列的 table,并将解析器插件与该列上的FULLTEXT索引相关联:

mysql> CREATE TABLE t (c VARCHAR(255),
    ->   FULLTEXT (c) WITH PARSER simple_parser
    -> ) ENGINE=MyISAM;
Query OK, 0 rows affected (0.01 sec)

在 table 中插入一些文本,然后尝试进行一些搜索。这些应该验证解析器插件将所有非空白字符都视为单词字符:

mysql> INSERT INTO t VALUES
    ->   ('latin1_general_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                                               |
+-------------------------------------------------+
| latin1_general_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)

“大小写”和“不区分大小写”都不会像对内置解析器那样匹配“不区分大小写”。