CentOS 7+PostgreSQL9.4 でゆるいtextsearch_jaの動作を確認する

ぬこは激怒した。
必ず、かの杓子定規のtextsearchをユルくしなければならぬと決意した。
ぬこにはPostgreSQL内部実装がわからぬ。
ぬこは、ただのユーザである。
SQLを書き、psqlと遊んで暮して来た。
けれども検索結果に対しては、人一倍に敏感であった。



2月はばたばたしていて、MySQL 5.7の形態素解析全文検索と、PostgreSQLtextsearch_jaとの比較に手が回らなかった。
pg_bigmとpgronngaの比較も結局できなかった。
そして2月末にはdocomoシェアパックの速度制限喰らってDLもままならんかったし・・・。
(そして2月末に測定したJSONB/MongoDBの1件更新の測定結果のblogアップもサボってる・・・)

3月になったので、改めてやってみることに。
まずはtextsearch_jaから。といっても、今使っているCF-SX4上のCentOS7上にmecabから入れなおす必要がある。

Mecabのインストール

インストールするバージョン。3年前にインストールしたものと変わってない。そういう意味ではMecabはもうオワコンなのかしらん。

  • エンジン:mecab-0.994
  • 辞書:mecab-ipadic-2.7.0-20070801

これはCentOS7上のgcc 4.8.2でもフツーにビルドはできた。
(辞書は configure時に--with-charset=utf8オプションをつけて構築している)

textsearch_jaのインストール

ということで次はtextsearch_jaのビルドである。
せっかくなので、先日ビルドしたPostgreSQLの最新版、9.4.1上に組み込んでみることにする。

[nuko@localhost textsearch_ja-9.0.0]$ make USE_PGXS=1
gcc -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -O2 -fpic -I. -I./ -I/home/nuko/pgsql-9.4.1/include/server -I/home/nuko/pgsql-9.4.1/include/internal -D_GNU_SOURCE   -c -o textsearch_ja.o textsearch_ja.c
textsearch_ja.c: 関数 ‘ja_analyze’ 内:
textsearch_ja.c:403:4: 警告: 関数 ‘heap_form_tuple’ の暗黙的な宣言です [-Wimplicit-function-declaration]
    tuple = heap_form_tuple(tupdesc, values, nulls);
    ^
textsearch_ja.c:403:10: 警告: 代入で整数からキャスト無しにポインタを作成しています [デフォルトで有効]
    tuple = heap_form_tuple(tupdesc, values, nulls);
          ^
textsearch_ja.c:424:2: 警告: 関数 ‘heap_copytuple’ の暗黙的な宣言です [-Wimplicit-function-declaration]
  result = heap_copytuple(tuple);
  ^
textsearch_ja.c:424:9: 警告: 代入で整数からキャスト無しにポインタを作成しています [デフォルトで有効]
  result = heap_copytuple(tuple);        ^

9.3以降(だったかな?)、DLしたtextsearch_jaをそのままビルドすると上記の警告が出てしまうので、
textsearch_ja.c の最初のほうにあるinclude文のところに、access/htup_details.h のinclude文を追加する。

#include "textsearch_ja.h"
#include <mecab.h>
#include "access/htup_details.h" /* add */
#include "pgut/pgut-be.h"

これで警告は出なくなる。
なお、この警告を放置すると、textseach_jaの ja_analyze() とかでbackend異常終了することがあったはず。

なお、念のため?に9.5-develでもビルドしてみたが、今のところ警告なくビルドはできるっぽい。
まあ、検証自体は9.4.1でやるけど。

ビルドができたのでインストール・・・の前に。
textsearch_ja.sql 自体の修正をしないと、データベースへの組み込み時に(たしか)PostgreSQL 9.1以降はエラーになる。
具体的には、LANGUAGE 'C'となっている箇所をLANGUAGE 'c'に変更しないといけない(10箇所)
例えばこんな感じで修正する。

CREATE FUNCTION ts_ja_start(internal, int4)
    RETURNS internal
    AS '$libdir/textsearch_ja'
    LANGUAGE 'c' STRICT;

で、インストール。

[nuko@localhost textsearch_ja-9.0.0]$ make USE_PGXS=1 install
/usr/bin/mkdir -p '/home/nuko/pgsql-9.4.1/lib'
/usr/bin/mkdir -p '/home/nuko/pgsql-9.4.1/share/contrib'
/usr/bin/install -c -m 755  textsearch_ja.so '/home/nuko/pgsql-9.4.1/lib/textsearch_ja.so'
/usr/bin/install -c -m 644 uninstall_textsearch_ja.sql textsearch_ja.sql '/home/nuko/pgsql-9.4.1/share/contrib/'
[nuko@localhost textsearch_ja-9.0.0]$

あとはデータベースに組み込めばいい。
今回は青空文庫太宰治作品を幾つかロードしてためしてみる。。
utf8エンコーディングで作成したdazaiデータベースにtextsearch_jaをインストールする。
なお、DL版だとEXTENSIONに対応していないので textsearch_ja.sqlpsqlで実行しなければならない。
(libmecab.soにLD_LIBRARY_PATHを通しておかないと、上記の textsearch_ja.sql 実行時にエラーになるので注意)

ゆるいtextsearch_jaの組み込み

元々、mecab/textseach_ja環境を改めて構築したのは、これからMySQL 5.7の形態素解析全文検索機能と比較するためだったんだけど、せっかくなので(?)、自作の「だいたいあってる」textsearch_jaも確認してみた。
2年くらい前に、開催されたPostgreSQL Unconferenceで「ゆるいテキスト検索」というタイトルで、曖昧な比較が可能な文字型と、その曖昧な比較関数を(無理やり)textsearch_ja組み込んで、曖昧な全文検索を行うというのを発表した。
ゆるいテキスト検索

そのときはCentOS 6+PostgreSQL 9.2という環境でやってみたけど、CentOS7+PostgreSQL 9.4環境でも動作するのかどうかを確認してみた。

仕組みとしては、文字列同士を比較して「だいたいあってる」なら0を、その他なら正値あるいは負値を返却する(memcmpと入出力IFを合わせた)独自の比較関数を作成。

extern void _PG_init(void);
・・・
/*
 * TsearchCompare_hook
 * Custom approx compare function
 */
static int
TsearchCompareApprox(char* a, char* b, int len)
{
・・・実際の近似評価用のコード
}
・・・
/*
 * Module initialization function
 */
void
_PG_init(void)
{
        /* activate hook module is loaded */
        TsearchCompare_hook = TsearchCompareApprox;
}

PostgreSQLのソース(backend/util/adt/tsvector_op.c)を、ちょっと修正して、その比較関数をHOOK関数として組み込み可能にする。

int (*TsearchCompare_hook) (char* a, char* b, int len) = NULL;  // add by nuko
・・・
                if (TsearchCompare_hook == NULL)
                {
                    cmp = memcmp(a, b, Min(lena, lenb));
                }
                else
                {
                    // Custom Compare Hook Functoin Call
                    cmp = (*TsearchCompare_hook) (a, b, Min(lena, lenb));
                }
・・・
                else if ((TsearchCompare_hook == NULL) && cmp == 0 && lena != lenb) // modify by nuko
                {
                        cmp = (lena < lenb) ? -1 : 1;
                }

なお、この部分の修正方式はPostgreSQL 9.2とPostgreSQL 9.4でも特に変更はなかった。

で、HOOK関数を有効にするために、postgresql.conf に作成した比較関数のライブラリ読み込みのパラメータを追加する。

shared_preload_libraries = 'ts_compare_approx'

これで再起動すると、「だいたいあってる」ワードで全文検索ができる。
通常の textsearch_ja だと、当たり前だけど「セリヌンティウス」を含む文書を「センヌリティウス」や「セリンティウス」で検索することはできない。
が、この「だいたいあってる」比較関数を組み込んだ textsearch_ja だと、こんな感じで「だいたいあってる」ワードでも引っ掛けることができる。

以下は「センヌリティウス」で「セリヌンティウス」をヒットさせた例。

dazai=# SELECT ts_headline('japanese', t, 'センヌリティウス')  FROM  dazai WHERE to_tsvector('japanese', t) @@ to_tsquery('japanese', 'センヌリティ ウス');
                                                                              ts_headline

----------------------------------------------------------------------------------------------------------------------------------------------------
-------------------
 <b>セリヌンティウス</b>である。今は此のシラクスの市で、石工をしている。その友を、これから訪ねてみるつもりなのだ。久しく逢わ
 <b>セリヌンティウス</b>という石工がいます。私の無二の友人だ。あれを、人質としてここに置いて行こう。私が逃げてしまって、三日目の日暮まで、ここに帰っ
 <b>セリヌンティウス</b>は無言で首肯うなずき、メロスをひしと抱きしめた。友と友の間は、それでよかった。<b>セリヌンティウス</b>は、縄打た
 <b>セリヌンティウス</b>。よくも私を信じてくれた。それを思えば、たまらない。友と友の間の信実は、この世で一ばん誇るべき宝なのだからな。<b>セリヌンテ
ウス</b>
 <b>セリヌンティウス</b>様の弟子でございます。」その若い石工も、メロスの後について走りながら叫んだ。「もう、駄目でございます。むだでございます。走る
のは、やめて下さい
 <b>セリヌンティウス</b>は、徐々に釣り上げられてゆく。メロスはそれを目撃して最後の勇、先刻、濁流を泳いだように群衆を掻き
 ゆく友の両足に、齧かじりついた。群衆は、どよめいた。あっぱれ。ゆるせ、と口々にわめいた。<b>セリヌンティウス</b>の縄は、ほどかれたのである。
 <b>セリヌンティウス</b>。」メロスは眼に涙を浮べて言った。「私を殴れ。ちから一ぱいに頬を殴れ。私は、途中で一度、悪い
 <b>セリヌンティウス</b>は、すべてを察した様子で首肯うなずき、刑場一ぱいに鳴り響くほど音高くメロスの右頬を殴っ
  メロスは腕に唸うなりをつけて<b>セリヌンティウス</b>の頬を殴った。
(10 rows)

実案件で使い物になるのかどうかはともかく、自分では結構気に入っている拡張なので、最新環境でも動作が確認できて嬉しかった。