延伸 MySQL 9.0  /  ...  /  編寫全文解析器外掛程式

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_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() 會按照先前關於 st_mysql_ftparser_boolean_info 結構的討論所述,填寫 bool_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 匹配。