28.2.4.4 编写全文分析器插件

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

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

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

#include <mysql/plugin.h>

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

该 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 是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 语句的开始和结束时调用初始化和取消初始化功能。但是,在语句处理期间,服务器以特定于上下文的方式调用主解析函数:

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

插件 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;

结构成员的用法如下:

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

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

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

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

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

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

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;

解析器应按以下方式填充结构成员:

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

Token Value Meaning
FT_TOKEN_EOF 数据结束
FT_TOKEN_WORD 普通字
FT_TOKEN_LEFT_PAREN 组或子 table 达式的开始
FT_TOKEN_RIGHT_PAREN 组或子 table 达式的结尾
FT_TOKEN_STOPWORD A stopword

插件不应使用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结构的成员。

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

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

首页