snippet関数を実装しました
はじめに
snippet関数という新しいSQL関数を実装しました! 現在、Tritonnの開発レポジトリにcommitされています。以下のコマンドでソースを入手して試すことができます。
svn checkout http://svn.sourceforge.jp/svnroot/tritonn/mysql-5.0.45-tritonn-1.0.5
snippet関数は特に問題がでなければtritonn-1.0.5のリリース時に含まれる予定です。
snippet関数って何?
snippet関数とは、KWIC(keyword in context)表示を行うためのSQL関数です。簡単に言うと、Google検索を行ったときに出てくる「ヒットした各ページごとの100文字くらいかたまり」を作るための関数になります。
従来はTritonnではsnippet関数は提供しておらず、Senna本体からMySQLのユーザ定義関数(UDF)として提供されていました。しかしユーザ定義関数にはPrepared Statementで使用できないなどを始めとするいくつかのデメリットもあり、以前からsnippet UDFをMySQLのnativeなSQL関数として実装したいという思いがありました。
今回、snippet関数をTritonnで実装したことにより、これまで以上に手軽にKWIC表示機能をアプリケーションで実装することができます。またUDFと比べた場合の性能面の向上も期待できます!
snippet関数の使用例
ここでは"埼玉"をキーワードとして、その前後あわせて60バイトのsnippetを作成しています。またsnippetの最後尾に" ..."を付与しています(Googleの真似)。
[test] > set names utf8; Query OK, 0 rows affected (0.00 sec) [test] > create table t1 (c1 text) default charset utf8; Query OK, 0 rows affected (0.00 sec) [test] > insert into t1 values ("今日は東京に行きます。明日は埼玉に行きます。明後日は千葉に行きます。その後、横浜に戻ります。"); Query OK, 1 row affected (0.00 sec) [test] > select snippet(c1, 60, 1, 0, "", " ...", "埼玉", "<span id=word>", "</span>") from t1; +---------------------------------------------------------------------------------------+ | snippet(c1, 60, 1, 0, "", " ...", "埼玉", "<span id=word>", "</span>") | +---------------------------------------------------------------------------------------+ | に行きます。明日は<span id=word>埼玉</span>に行きます。明後日 ... | +---------------------------------------------------------------------------------------+ 1 row in set (0.00 sec)
"埼玉"の前後にタグを付けていますので、cssなどで強調表示したりすることも可能です。
この例ではwhere句に条件を付けずに実行していることにお気づきになると思います。snippet関数は文字列関数の一種であり、FULLTEXTインデックスによる検索とは無関係に使用することもできます。
とはいえ、今回snippet関数を追加したのはもちろん、以下のSQL文のようにMATCH検索と絡めて使用するためです。
SELECT snippet(c1, 60, 1, 0, "", " ...", "埼玉", "<span id=word>", "</span>") FROM t1 WHERE MATCH(c1) AGAINST("埼玉");
これにより、Tritonnによる日本語全文検索を利用したアプリケーションの開発が、より一層、手軽にできるようになると思います。
snippet関数の構文
snippet関数は大変引数の多い関数です。
SELECT snippet(文書, snippetの長さの最大バイト数, snippetの最大個数, htmlエンコーディングの有無, snippetの開始タグ, snippetの終了タグ, 単語1, 単語1の前につけられるタグ, 単語1の後につけられるタグ, 単語2, 単語2の前につけられるタグ, 単語2の後につけられるタグ, ...);
注意点:snippet UDFでは文書の文字コードを引数で指定する必要がありましたが、snippet関数では不要となりました!
htmlエンコーディングの有無は、0か1を指定ください。 1の場合は文書中の<,>をそれぞれ<,>に変換して出力します。
例は以下のとおりです。
SELECT snippet(body, 120, 3, 1, '...', '...<br>\n', '検索', '<span class="word1">', '</span>', '実験', '<span class="word2">', '</span>' ) FROM contents WHERE MATCH(body) AGAINST('*D+ 検索 実験' IN BOOLEAN MODE) LIMIT 0,10;
不正な引数に対するエラーと警告について
snippet UDFでは構文にマッチしない引数が渡された場合、主にエラーを返すことでユーザに通知していました。しかし今回新しく実装したsnippet関数では、引数の数が足りない場合や不正な値が渡された際、エラーや警告を返すことはしない実装にしています。
このあたりの仕様は悩んだところなのですが、MySQLの文字列関数の実装に倣うことにしました。(郷に入れば郷に従え、ということで。)
簡単にまとめると以下のような仕様を決めて実装しています。
- 引数にnullが含まれる場合(1つでも)、nullを返します。
- 引数の数が不適切な場合(引数の数は、9個、12個、15個...である必要があります)、空文字""を返します。
- 引数の値が不適切な場合(長さを指定する場所で0以下の値を与えるなど)、空文字""を返します。
これはconcat関数やleft関数、right関数などの既存のMySQLの文字列系SQL関数の振舞いと同じです。
ただしsnippet関数は引数の大変多いSQL関数ですから、これだけだと慣れないうちはアプリケーション開発の際に戸惑うこともあるかもしれません。
そこで、snippet関数にこうした不正な引数が与えられた場合にはログファイルにその内容を出力することとしました。不正な引数に対するログ出力はレベル"WARN"で行われます。従って、"INFO"等の標準的なログレベルを設定しておけば、senna.logで確認することが可能になります。
./mysqld --senna-log --senna-log-level=info &
不正な引数が指定された場合、ログには以下のような出力が行われます。
これは9番目の引数がnullであったために、戻り値としてnullを返したことを示すログです。
09/01:00:46:48.041479|w|1| snippet argument #9 is null
これは引数が8個しか指定されなかったために、戻り値として空文字が返されたことを示すログです。
09/01:00:47:03.089207|w|1| Incorrect number of arguments for snippet: 8
これは3番目の引数(snippetの最大個数、1以上の値が指定されている必要がある)が-1であったために、戻り値として空文字が返されたことを示すログです。
09/01:00:47:25.655209|w|1| snippet argument #3 must be positive value, passed -1