扩展 MySQL 8.0  / 第 4 章 MySQL 插件 API  / 4.4 编写插件  /  4.4.4 编写全文解析器插件

4.4.4 编写全文解析器插件

MyISAMMySQL 支持带有和 的服务器端全文解析器插件 InnoDB。有关全文解析器插件的介绍性信息,请参阅 全文解析器插件

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

这些说明使用 plugin/fulltextMySQL 源代码分发目录中的源代码,因此请将位置更改为该目录。以下过程描述了如何创建插件库:

  1. 要编写全文解析器插件,请在插件源文件中包含以下头文件。根据插件功能和要求,可能还需要其他 MySQL 或通用头文件。

    #include <mysql/plugin.h>

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

  2. 为插件库文件设置库描述符。

    该描述符包含服务器插件的通用插件描述符。对于全文解析器插件,类型必须是MYSQL_FTPARSER_PLUGIN. 这是在WITH PARSER创建FULLTEXT索引时将插件标识为在子句中合法使用的值。(对于此条款,其他插件类型均不合法。)

    例如,包含一个名为的全文解析器插件的库的库描述符 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成员 ( ) 表示在or simple_parser等​​语句中用于引用插件的 名称。这也是或 显示的名称。 INSTALL PLUGINUNINSTALL PLUGINSHOW PLUGINSINFORMATION_SCHEMA.PLUGINS

    有关详细信息,请参阅 第 4.4.2.1 节,“服务器插件库和插件描述符”

  3. 设置特定于类型的插件描述符。

    库描述符中的每个通用插件描述符都指向一个特定类型的描述符。对于全文解析器插件,特定于类型的描述符是文件中st_mysql_ftparser结构的一个实例plugin.h

    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_VERSIONinclude/mysql/plugin_ftparser.h0x0101

    和成员应该指向一个函数initdeinit 或者如果不需要该函数则设置为 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,将调用内置解析器。(插件与特定的索引相关联。如果没有索引,则不使用插件。)

    通用插件描述符中的插件声明具有指向初始化initdeinit 取消初始化函数的成员,它指向的特定类型的插件描述符也是如此。但是,这些函数对具有不同的目的,并且出于不同的原因被调用:

    • 对于通用插件描述符中的插件声明,在插件加载和卸载时会调用初始化和去初始化函数。

    • 对于特定于类型的插件描述符,初始化和取消初始化函数是根据使用插件的 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_parseor 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_lenft_max_word_len分词器内部进行检查。对于 InnoDB,停用词列表和等效字长变量设置(innodb_ft_min_token_sizeinnodb_ft_max_token_size)在分词器之外进行检查。因此, InnoDB插件解析器不需要检查停用词列表 innodb_ft_min_token_size、 或 innodb_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: 令牌类型。下表显示了允许的类型。

      表 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 成员的支持。

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

    笔记

    插件解析器框架不支持:

    • @distance布尔运算符 。

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

    有关布尔全文搜索运算符的信息,请参阅布尔全文搜索

  4. 设置插件接口函数。

    库描述符中的通用插件描述符命名了服务器在加载和卸载插件时应调用的初始化和取消初始化函数。对于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 结构讨论中所述。

  5. 设置状态变量。对于 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                  |
    +----------------------+--------------------+
  6. 要编译和安装插件库文件,请使用 第 4.4.3 节“编译和安装插件库”中的说明。要使库文件可用,请将其安装在插件目录(由 plugin_dir系统变量命名的目录)中。对于simple_parser 插件,它是在您从源代码构建 MySQL 时编译和安装的。它也包含在二进制发行版中。构建过程会生成一个名称为mypluglib.so.so后缀可能因平台而异)的共享对象库。

  7. 要使用该插件,请将其注册到服务器。例如,要在运行时注册插件,请使用此语句,.so根据需要调整平台的后缀:

    INSTALL PLUGIN simple_parser SONAME 'mypluglib.so';

    有关插件加载的其他信息,请参阅 安装和卸载插件

  8. 要验证插件安装,请检查 INFORMATION_SCHEMA.PLUGINS 表或使用SHOW PLUGINS语句。请参阅 获取服务器插件信息

  9. 测试插件以验证它是否正常工作。

    创建一个包含字符串列的表,并将解析器插件与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)

    caseinsensitive 都不像内置解析器那样 匹配case-insensitive ” 。