mroonga_escapeを使ってわかりやすい、メンテナンスしやすい検索クエリにする方法

> もっとgroongaを知ってもらおう!ということで週刊groongaをはじめました。毎週木曜にgroongaやmroonga、rroongaのトピックを投稿予定です。

[gihyo.jp](http://gihyo.jp/)さんでgroongaの隔週更新連載が始まっています。第10回の記事も公開されたので、一読をおすすめします。

* [「第10回　[実録] MySQL向け全文検索エンジン「Tritonn」から「mroonga」への移行ガイド（3）](http://gihyo.jp/dev/clip/01/groonga/0010)

第1回から第9回までの過去記事については、[隔週連載groonga](http://gihyo.jp/dev/clip/01/groonga)のページを参照してください。

# はじめに

MySQLで高速に全文検索するためのオープンソースのストレージエンジンとして[mroonga](http://mroonga.org/ja/) (むるんが)を公開しています。
最新のバージョンは2013年8月29日にリリースした[3.07](http://mroonga.org/ja/docs/news.html#release-3-07-2013-8-29)です。

今回は、mroongaで新規に追加されたmroonga_escapeというUDFを使ってわかりやすく、メンテナンスしやすい検索のクエリを書く方法を紹介します。

# mroonga_escapeとは

mroongaでは実験的なものを含め、独自に機能拡張するためのUDFをいくつか提供しています。

現在実験的なものを含め4つのUDFを提供しています。

* last_insert_grn_id (最後に挿入したデータのIDを取得する)
* mroonga_snippet (スニッペットを取得する)
* mroonga_command (groongaのコマンドを実行する)
* mroonga_escape (エスケープを補助する)

このうち、mroonga_escapeは3.07で新規に追加されました。

全文検索を行うときなど、クエリの内容によっては特別扱いする文字の存在により、適宜エスケープをしなければならない場面があります。
mroongaはgroongaをストレージとして採用しているので、MySQL、mroonga、groongaと3つ階層があることに注意してクエリを書かないといけません。

そのあたりの話については、以前 [mroongaで記号類を含むクエリのシンタックスエラーを回避する方法](http://qiita.com/groonga/items/0ae7f637525c7b5f2cb1) として記事を書きました。

例えば、その記事でも例として使っていた ``(仮)`` を前方一致検索する場合でどうなるかを示します。

サンプルのスキーマは次の通りです。      

```
CREATE TABLE test_escape (
  id int(11) NOT NULL AUTO_INCREMENT,
  content varchar(255) NOT NULL COLLATE 'utf8_unicode_ci',
  PRIMARY KEY (id),
  FULLTEXT INDEX(content) COMMENT 'parser "TokenDelimit"'
) ENGINE=mroonga DEFAULT CHARSET=utf8;
```

これにサンプルデータを投入します。

```
INSERT INTO test_escape(content) VALUES ("(括弧)");
INSERT INTO test_escape(content) VALUES ("括弧");
INSERT INTO test_escape(content) VALUES ('"ダブルクォート"');
INSERT INTO test_escape(content) VALUES ("ダブルクォート");
INSERT INTO test_escape(content) VALUES ("'シングルクォート'");
INSERT INTO test_escape(content) VALUES ("シングルクォート");
INSERT INTO test_escape(content) VALUES ("(仮)*");
INSERT INTO test_escape(content) VALUES ("(仮)テスト");
INSERT INTO test_escape(content) VALUES ("仮*");
INSERT INTO test_escape(content) VALUES ("仮面");
INSERT INTO test_escape(content) VALUES ("<meta>");
INSERT INTO test_escape(content) VALUES ("meta");
INSERT INTO test_escape(content) VALUES ("\\バックスラッシュ");
```

# mroonga_escapeを試してみる

では、mroonga_escapeをひとまず試してみましょう。実際の例を使って説明します。

mroonga_escapeでは、ひとつめの引数に対象となるクエリを書いて、通常の文字として扱いたいエスケープが必要な文字をふたつめの引数に指定します。

つまり、``(仮)``の ``(``は通常の文字として扱いたい場合には次のようになります。

```
mysql> SELECT mroonga_escape("(仮)*", "()");
+--------------------------------+
| mroonga_escape("(仮)*", "()")  |
+--------------------------------+
| \(仮\)*                        |
+--------------------------------+
1 row in set (0.00 sec)
```

エスケープされた結果が取得できていることがわかります。

# ``(仮)`` を前方一致検索してみる

mroonga_escapeの使いかたがわかったので、``(仮)`` を前方一致検索してみましょう。

mroonga 3.06以前では、``(仮)`` を前方一致検索したい場合には、次のようにエスケープしてあげる必要がありました。

```
mysql> SELECT * FROM test_escape WHERE MATCH(content) AGAINST ("\\(仮\\)*" IN BOOLEAN MODE);
+----+----------------+
| id | content        |
+----+----------------+
|  8 | (仮)テスト     |
|  7 | (仮)*          |
+----+----------------+
2 rows in set (0.00 sec)
```

きちんと前方一致検索ができていますが、``\\`` というエスケープをいれる必要があるので、クエリが見にくくなってしまっています。

次は、mroonga 3.07で追加された、mroonga_escape を使った場合にどうなるかを示します。

```
mysql> SELECT * FROM test_escape WHERE MATCH(content) AGAINST (mroonga_escape("(仮)*", "()") IN BOOLEAN MODE);
+----+----------------+
| id | content        |
+----+----------------+
|  8 | (仮)テスト     |
|  7 | (仮)*          |
+----+----------------+
2 rows in set (0.01 sec)
```

こちらも当然前方一致検索ができています。
検索したいクエリとエスケープ対象とを分けているので、エスケープのせいでごちゃごちゃしていたクエリがすっきりしていることがわかります。

# まとめ

今回は、mroongaで新規に追加されたmroonga_escapeというUDFを使ってわかりやすく、メンテナンスしやすい全文検索のクエリを書く方法を紹介しました。

mroongaの基本的な動作を知るための[ユーザガイド](http://mroonga.org/ja/docs/userguide.html)もあります。インストールしたら試してみてください。