擴充 MySQL 8.4  /  ...  /  編寫全文解析器外掛程式

4.4.4 編寫全文解析器外掛程式

MySQL 支援使用 MyISAMInnoDB 的伺服器端全文解析器外掛程式。有關全文解析器外掛程式的介紹資訊,請參閱 全文解析器外掛程式

全文解析器外掛程式可用於取代或修改內建的全文解析器。本節說明如何編寫名為 simple_parser 的全文解析器外掛程式。此外掛程式會根據比 MySQL 內建全文解析器使用的規則更簡單的規則來執行解析:單字是空白字元的非空執行。

這些說明使用 MySQL 原始碼發行版中的 plugin/fulltext 目錄中的原始碼,因此請將位置變更到該目錄。以下程序說明如何建立外掛程式庫

  1. 若要編寫全文解析器外掛程式,請在外掛程式原始檔中包含以下標頭檔。根據外掛程式的功能和需求,可能還需要其他 MySQL 或一般標頭檔。

    #include <mysql/plugin.h>

    plugin.h 定義 MYSQL_FTPARSER_PLUGIN 伺服器外掛程式類型以及宣告外掛程式所需的資料結構。

  2. 為外掛程式庫檔案設定庫描述符。

    此描述符包含伺服器外掛程式的一般外掛程式描述符。對於全文解析器外掛程式,類型必須為 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 PLUGINUNINSTALL PLUGIN 等陳述式中用來參考外掛程式的名稱。這也是 SHOW PLUGINSINFORMATION_SCHEMA.PLUGINS 所顯示的名稱。

    如需更多資訊,請參閱第 4.4.2.1 節「伺服器外掛程式庫與外掛程式描述符」

  3. 設定特定類型的外掛程式描述符。

    程式庫描述符中的每個一般外掛程式描述符都會指向特定類型的描述符。對於全文解析器外掛程式,特定類型的描述符是 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_VERSION。對於全文解析器外掛程式,符號為 MYSQL_FTPARSER_INTERFACE_VERSION。在原始碼中,您會在 include/mysql/plugin_ftparser.h 中找到全文解析器外掛程式的實際介面版本號碼。目前的介面版本號碼為 0x0101

    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_parsemysql_add_word 回呼的第一個引數傳遞。

    • cs:指向文字字元集資訊的指標,如果沒有資訊可用,則為 0。

    • doc:指向要解析的文字的指標。

    • length:要解析的文字長度 (以位元組為單位)。

    • flags:解析器旗標。如果沒有特殊的旗標,則此值為零。唯一非零的旗標是 MYSQL_FTFLAGS_NEED_COPY,表示 mysql_add_word() 必須儲存單字的複本(也就是說,它無法使用指向單字的指標,因為單字位於將被覆寫的緩衝區中)。

      MySQL 可以在叫用解析器外掛程式之前、由解析器外掛程式本身或由 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_lenft_max_word_len 在權杖化器內部進行檢查。對於 InnoDB,停用字列表和等效的單字長度變數設定(innodb_ft_min_token_sizeinnodb_ft_max_token_size)在權杖化器外部進行檢查。因此,InnoDB 外掛程式解析器不需要檢查停用字列表、innodb_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:符記類型。下表顯示了允許的類型。

      表 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 結構的 prevquot 成員。

    注意

    外掛程式解析器框架不支援

    • @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)

    “case”“insensitive” 都無法像內建解析器那樣比對 “case-insensitive”