ノータブルコード15 - MySQLの全文検索でplease, could_you - 2021-05-25 - ククログ

ククログ

株式会社クリアコード > ククログ > ノータブルコード15 - MySQLの全文検索でplease, could_you

ノータブルコード15 - MySQLの全文検索でplease, could_you

最近、MroongaMySQL 8.0対応を進めている須藤です。あとはコンディションプッシュダウンまわりを実装すればMySQL 8.0対応は完了しそうです。MySQL 8.0対応をしていて「お!」という思うコードがあったことを思い出したので15回目のノータブルコードとして紹介します。

MySQLの全文検索機能はMATCH (...) AGAINST (...)という構文を使います。この構文をWHEREで書けば絞り込み条件になり、SELECT直後の出力リストのところやORDER BYのところに書けばその全文検索でのスコアーになります。

たとえば、次のSQLでは3つ同じMATCH (...) AGAINST (...)がありますが、2番目のMATCH (...) AGAINST (...)だけが条件で残りのMATCH (...) AGAINST (...)は全文検索でのスコアーになります。

SELECT MATCH (content) AGAINST ('+Hello' IN BOOLEAN MODE)
  FROM memos
 WHERE MATCH (content) AGAINST ('+Hello' IN BOOLEAN MODE)
 ORDER BY MATCH (content) AGAINST ('+Hello' IN BOOLEAN MODE) DESC;

1つのSELECT中に違う種類のMATCH (...) AGAINST (...)を複数書くこともできます。たとえば、次のように書くこともできます。

SELECT MATCH (content) AGAINST ('+Hello' IN BOOLEAN MODE),
       MATCH (content) AGAINST ('+World' IN BOOLEAN MODE)
  FROM memos
 WHERE MATCH (content) AGAINST ('+Hello' IN BOOLEAN MODE) AND
       MATCH (content) AGAINST ('+World' IN BOOLEAN MODE)
 ORDER BY MATCH (content) AGAINST ('+Hello' IN BOOLEAN MODE) DESC;

このため、内部的には全文検索の種類(↑の例だとHelloWorldでの全文検索2種類)ごとにそれぞれリソースを確保します。このリソースには検索結果やどのレコードがどのくらいのスコアーかといった情報を格納することになります。

このリソースは各ストレージエンジンがFT_INFO構造体を親にした構造体の領域を確保してMySQLに返します。FT_INFO構造体は次のように定義されています。_ft_vft構造体の中は「次のレコードを取得」・「対象レコードのスコアーを取得」といった機能を実装する関数ポインターの集まりになっていて、各ストレージエンジンの実装をMySQLがコールバックできるようになっています。

struct FT_INFO {
  struct _ft_vft *please; /* INTERCAL style :-) */
};

MySQLはストレージエンジンからFT_INFO構造体を受け取り、必要に応じてストレージエンジンにコールバックして必要な情報を得ます。たとえば、スコアーを取得する場合は次のようになっています。ft_handlerFT_INFO構造体を参照している変数です。

ft_handler->please->get_relevance(ft_handler);

なんかplease付きでスコアー(relevance)を取得していて丁寧な感じがしますね!

ところで、FT_INFO構造体の定義のコメントの「INTERCAL style :-)」には気づいていましたか?実は、これはINTERCALというプログラミング言語のマネをしています。INTERCALでは命令を実行する前に「DO」か「PLEASE」か「PLEASE DO」を指定しないといけません1FT_INFO構造体のメンバー名をpleaseにするとplease->XXX()と呼び出さないといけなくなるのでINTERCALのような書き方になるというわけです。なお、WikipediaによるとINTERCALは1972年に開発されたプログラミング言語だそうなのでその頃からPLEASEと言っていたことになります。一方、FT_INFO構造体は2001年に導入されている2のでINTERCALから30年ほど遅れてスタイルを踏襲したことになります。

MySQLはINTERCALと違って今でも活発に開発が続いています。全文検索まわりも改良されています。2012年には次のようなFT_INFO_EXT構造体が追加されました。InnoDBの全文検索機能のためです。

struct FT_INFO_EXT {
  struct _ft_vft *please; /* INTERCAL style :-) */
  struct _ft_vft_ext *could_you;
};

FT_INFO構造体との違いは_ft_vft_ext構造体のメンバーが増えていることだけです。ft_vft_ext構造体も_ft_vft構造体と同じようにコールバック用の関数ポインターが集まっています。追加のコールバックを互換性を壊さずに追加するためにFT_INFO_EXT構造体を新設したというわけです。

MySQLが_ft_vft_ext構造体のコールバックを呼ぶときは次のようになります。(キャストは省略しています。)

ft_handler->could_you->count_matches(ft_handler);

please->XXX()じゃなくてcould_you->XXX()になっているのでFT_INFO構造体のときよりさらに丁寧になっている気がしますね!10年ちょいの時を経ている3のに、INTERCALのスタイルをマネするという既存のコードのスタイルを壊さずに拡張しています。OSSプロジェクトにコントリビュートするときは一貫性を壊さないという話はよくありますが、こういうことなんですね!みなさんも、既存のコードを変更するときは既存のコードの意図をしっかり読み取ってそれに合わせて拡張していってください。

さらに10年経ってPLEASE SELECTが書けるようになっているので、MySQLはまだまだINTERCALの影響を受け続けていそうですね。

MySQL 8.0対応をしていたらMySQLのおもしろコードを思い出したのでノータブルコードとして紹介しました。みなさんも「お!」と思うコードがあったらノータブルコードとして紹介してみてください。

クリアコードはユーザーがソースコードから学習する自由がある自由なソフトウェアを推進しています。今回のMroongaのMySQL 8.0対応のように業務の一環で自由なソフトウェアのソースコードを読むことも多いです。そんな仕事がいいなと思う人は採用情報を確認してみてください。最近、採用情報ページを刷新したんです!

  1. http://www.muppetlabs.com/~breadbox/intercal/intercal.txt の「4.3 IDENTIFIERS AND QUALIFIERS」を参照。

  2. https://github.com/mysql/mysql-server/commit/3d3ef6528a48f5fba7bfcef10970c89bfb88e420

  3. https://github.com/mysql/mysql-server/commit/444034b40cc6d3e4c1cb198b8b4a156d1cf75121