MySQL 5.1で日本語の正規表現を使うために ~mregexpインストール奮闘記~

MySQLではWHERE句に正規表現を使うことができるわけですが,日本語を扱う上ではいろいろと不具合があります.
例えば . が任意の1バイトとして扱われるため,任意の日本語1文字にはマッチしません.

mysql> select 'あいう' regexp 'あ.う';
+------------------------------+
| 'あいう' regexp 'あ.う'      |
+------------------------------+
|                            0 |
+------------------------------+

次の例になるとどう説明すればいいのかよくわからないです…1

mysql> select '(笑)' regexp '([^>]+)'; select '(?)' regexp '([^>]+)';
+------------------------------------+
| '(笑)' regexp '([^>]+)'       |
+------------------------------------+
|                                  1 |
+------------------------------------+

+------------------------------------+
| '(?)' regexp '([^>]+)'       |
+------------------------------------+
|                                  0 |
+------------------------------------+

まぁ日本語(全角文字)の正規表現の扱いにはどの言語にしても多少なりとも問題があるものです.
MySQLで日本語の正規表現を扱うためにはmregexpというユーザ定義関数(UDF)が公開されています.
今回はそのmregexpのインストールに”挑戦”してみました.

README.txtに素直に従ってインストールしてみた

何せREADME.txtが作成されたのが2006年9月29日なんで,README.txtに従って突き進んでもうまく行きませんでした.
とにかくインストールできればいいよ!って方は「インストール方法まとめ」まで読み飛ばしてください.

とりあえず必要なものは

  • MySQLの、開発者用ファイル (libmysqlとヘッダファイル。rpm版だとMySQL-develに含まれています)
    MySQLのバージョンは、4.0、4.1、5.0で動作確認をしています。
  • Oniguruma 4.X系

とのことなのでlibmysqlとoniguruma 4.Xをインストールします.
※以下の内容は32ビット版Ubuntu 10.04の場合です

MySQLの開発用パッケージをインストール

$ sudo apt-get install libmysql++-dev

これで/usr/include/mysqlや/usr/lib/mysqlなどが作成され,ヘッダファイルやライブラリがインストールされます.

Oniguruma 4.Xをインストール

Onigurumaは正規表現のライブラリです.

$ wget http://www.geocities.jp/kosako3/oniguruma/archive/onig-4.7.1.tar.gz
$ tar zxvf onig-4.7.1.tar.gz
$ cd onig-4.7.1/
$ ./configure
$ make
$ sudo make install
$ sudo ldconfig

/usr/local/libにライブラリがインストールされます.

mregexpをインストール

$ wget http://www.irori.org/dl/tool/mregexp-1.0.tar.gz
$ tar zxvf mregexp-1.0.tar.gz
$ cd mregexp-1.0/
$ make
cc -DDEFAULT_ENCODING=ONIG_ENCODING_SJIS -Wall -I/usr/include/mysql  -DBIG_JOINS=1  -fno-strict-aliasing
   -DUNIV_LINUX -DUNIV_LINUX -I/usr/local/include -shared -o mregexp.so mregexp.c -L/usr/local/lib -lonig
mregexp.c: In function ‘mregexp_init’:
mregexp.c:112: warning: pointer targets in passing argument 2 of ‘onig_new’ differ in signedness
/usr/local/include/oniguruma.h:807: note: expected ‘const OnigUChar *’ but argument is of type ‘char *’
mregexp.c:112: warning: pointer targets in passing argument 3 of ‘onig_new’ differ in signedness
/usr/local/include/oniguruma.h:807: note: expected ‘const OnigUChar *’ but argument is of type ‘char *’
mregexp.c: In function ‘mregexp’:
mregexp.c:138: warning: pointer targets in passing argument 1 of ‘strncpy’ differ in signedness
/usr/include/string.h:130: note: expected ‘char * __restrict__’ but argument is of type ‘unsigned char *’
mregexp.c:163: warning: pointer targets in passing argument 1 of ‘strlen’ differ in signedness
/usr/include/string.h:397: note: expected ‘const char *’ but argument is of type ‘unsigned char *’
mregexp.c: In function ‘mregexp_version’:
mregexp.c:199: warning: pointer targets in assignment differ in signedness
mregexp.c:201: warning: pointer targets in assignment differ in signedness
mregexp.c:203: warning: pointer targets in assignment differ in signedness
mregexp.c:205: warning: pointer targets in assignment differ in signedness

warningがいっぱい出てますがとりあえず無視しておきましょう.gccのあるバージョンから型の管理が厳しくなったとか.
ちなみに使っているgccのバージョンは4.4.3です.

$ sudo make install
abort:no such directory /usr/local/lib/mysql
make: *** [check] Error 1

あ,そうそう,インストール先のディレクトリを変更しないといけないんだった.
ついでにデフォルトの文字コードも変えておきましょう.

Makefile一部抜粋

#INSTALL_LIBDIR = /usr/local/lib/mysql
INSTALL_LIBDIR = /usr/lib/mysql

# DEFAULT_ENCODING = ONIG_ENCODING_SJIS
# DEFAULT_ENCODING = ONIG_ENCODING_EUC_JP
DEFAULT_ENCODING = ONIG_ENCODING_UTF8

ということで,気を取り直して

$ make clean
$ make
cc -DDEFAULT_ENCODING=ONIG_ENCODING_UTF8 -Wall -I/usr/include/mysql  -DBIG_JOINS=1  -fno-strict-aliasing
   -DUNIV_LINUX -DUNIV_LINUX -I/usr/local/include -shared -o mregexp.so mregexp.c -L/usr/local/lib -lonig

※warningは省略しました

$ sudo make install
install -m 444 -p mregexp.so /usr/lib/mysql/mregexp.so

っで,/etc/init.d/mysqlに次の行を追加して

export LD_LIBRARY_PATH=/usr/lib/mysql

MySQLを再起動します.

$ sudo /etc/init.d/mysql restart

これでMySQLが起動する度に環境変数がセットされ…

$ env | grep LD_LIBRARY_PATH

てない!環境変数がセットされてない!なんで???

今いちCのプログラムの仕組みは理解できていませんが,
MySQLサーバを再起動しろと書いてあるからには,起動する前にLD_LIBRARY_PATHが定義されていなきゃいけないはず!
このためだけに起動スクリプトを作成するのも何なんで,とりあえず/etc/ld.so.conf.d/mregexp.confというファイルを作成します.

$ sudo echo /usr/lib/mysql > /etc/ld.so.conf.d/mregexp.conf

リンクを更新します.

$ sudo ldconfig

MySQLを再起動します.

$ sudo /etc/init.d/mysql restart

さてさて,いよいよmregexp関数を作成します.

$ mysql -uroot -p
mysql> CREATE FUNCTION mregexp RETURNS INTEGER SONAME 'mregexp.so';
ERROR 1126 (HY000): Can't open shared library 'mregexp.so'
 (errno: 22 /usr/lib/mysql/plugin/mregexp.so: cannot open shared object file: No such file or directory)

???????
なんで/usr/lib/mysql/plugin/mregexp.soを読み込もうとしてるの?????

どうやらMySQL 5.1から関数作成の際はplugin_dirから共有オブジェクトを探すようになったみたいです.(参考)
ということでplugin_dirを確認してみます.

mysql> show variables like 'plugin_dir';
+---------------+-----------------------+
| Variable_name | Value                 |
+---------------+-----------------------+
| plugin_dir    | /usr/lib/mysql/plugin |
+---------------+-----------------------+

なるほど,/usr/lib/mysql/pluginになってるんで,/usr/lib/mysql/plugin/mregexp.soを読み込もうとするのも納得です.
/etc/mysql/my.cnfでもplugin_dirを定義できるみたいですが,ファイルを移すことにします.

$ sudo mv /usr/lib/mysql/mregexp.so /usr/lib/mysql/plugin/

よし,今度こそ!

mysql> CREATE FUNCTION mregexp RETURNS INTEGER SONAME 'mregexp.so';
ERROR 1126 (HY000): Can't open shared library 'mregexp.so'
 (errno: 22 /usr/lib/mysql/plugin/mregexp.so: failed to map segment from shared object: Permission denied)

????何ですと!?インストール時にパーミッションはしっかりしてほしいものだ.

$ ls -l /usr/lib/mysql/plugin/mregexp.so 
-r--r--r-- 1 root root 12920 2010-08-26 21:29 /usr/lib/mysql/plugin/mregexp.so

はて?共有オブジェクトって実行権限とかも必要なもの?とりあえず全権限を与えてみよう.

$ sudo chmod 777 /usr/lib/mysql/plugin/mregexp.so

これでやっと関数が作成できるはずだ!

mysql> CREATE FUNCTION mregexp RETURNS INTEGER SONAME 'mregexp.so';
ERROR 1126 (HY000): Can't open shared library 'mregexp.so'
 (errno: 22 /usr/lib/mysql/plugin/mregexp.so: failed to map segment from shared object: Permission denied)

えええええぇぇぇ?????これ以上ないくらいの権限を与えてますけど!?

っで,調べてみると,どうやらAppArmorというやつの設定をいじらなければならないらしい.(参考)
AppArmorはUbuntuだと7.10から導入されたらしく,それはつまり2007年の10月なのでREADME.txtが作成された約1年後ということになります.
他のLinuxでも2006年の時点だとあまり導入されていなかったのかもしれませんね.
あと,SELinuxを使っているOSだとSELinuxの設定をいじる必要があるかもしれません.

ってことで/etc/apparmor.d/usr.sbin.mysqldに次の1行を追加して,
mysqldが/usr/lib/mysql/plugin以下のファイルを読み込んで(r),メモリにマッピングして実行可能にします(m).
(この説明であってるか微妙なんで,詳細を知りたい方はこちら

/usr/lib/mysql/plugin/* mr,

AppArmorの再起動.

$ sudo /etc/init.d/apparmor restart

ようやくこれで解決…

mysql> CREATE FUNCTION mregexp RETURNS INTEGER SONAME 'mregexp.so';
ERROR 1126 (HY000): Can't open shared library 'mregexp.so'
 (errno: 22 libonig.so.1: cannot open shared object file: No such file or directory)

…できてない!今度はlibonig.so.1って…

$ sudo updatedb
$ locate libonig.so.1
/usr/local/lib/libonig.so.1
/usr/local/lib/libonig.so.1.0.0

あれ?ちゃんと存在するけど….あ,/usr/lib/mysql/pluginにおかないといけないのかな?

$ sudo ln -s /usr/local/lib/libonig.so.1 /usr/lib/mysql/plugin/

よし,これでいけるはず!

mysql> CREATE FUNCTION mregexp RETURNS INTEGER SONAME 'mregexp.so';
ERROR 1126 (HY000): Can't open shared library 'mregexp.so'
 (errno: 22 libonig.so.1: cannot open shared object file: No such file or directory)

……………もうイヤ….

ダイナミックリンカの問題?

$ ldconfig -p | grep libonig
        libonig.so.1 (libc6) => /usr/local/lib/libonig.so.1
        libonig.so (libc6) => /usr/local/lib/libonig.so

違うみたいですね.誰か同じことでハマってないかなぁと探してみるも,見つからず.
諦めかけた時,気付きました.

あ,AppArmorの設定!?

ってことで,次の行を/etc/apparmor.d/usr.sbin.mysqldに追加.

/usr/local/lib/* mr,

AppArmorの再起動

$ sudo /etc/init.d/apparmor restart

おそるおそる…

mysql> CREATE FUNCTION mregexp RETURNS INTEGER SONAME 'mregexp.so';
Query OK, 0 rows affected (0.02 sec)

で,できたーーーーーーー!!!!!!!!!!!!!!!!!!!

続いてmregexp_versionの作成.

mysql> CREATE FUNCTION mregexp_version RETURNS STRING SONAME 'mregexp.so';
ERROR 1127 (HY000): Can't find symbol 'mregexp_version_init' in library

…………MySQL 5.1からUDFの作成方法も変わったのかな?まぁmregexp_versionは必要ないよね.うん.

——–2010/08/31追記———————————————–
エラーにあるとおり,mregexp_version を作成するためには mregexp_version_initを定義しなければいません.
MySQLのマニュアルにはオプショナルと書いてあるんですが….
UDFとC言語に関する知識が全然ないので放置していましたが,ここに書かれているように,次のような関数を作成すればいけるみたいです.
コメントがうっとうしいかもしれませんが,まぁ気にしないでください…

my_bool mregexp_version_init(UDF_INIT *initid,
                             UDF_ARGS *args,
                             char *message) {
  // DEBUGが定義されていればstderrに出力,されていなければ無視(実質不要)
  DPRINT(">>mregexp_version_init");

  // mregexp_version に引数がセットされた場合  
  if (args->arg_count > 0) {
    // 1 (エラー) を返した場合に MySQL からのエラーメッセージとともに message の内容が表示される
    strcpy(message, "mregexp_version(): requires no arguments.");

    // エラー!
    return 1;
  }

  // char *mregexp_version が NULL を返してはいけない場合は0にする
  initid->maybe_null = 0;

  return 0;
}

ちなみにUDFの初期化関数は引数の数や型をチェックしたり,メモリを確保するためなどに使われます.
mregexp_versionではメモリを確保する必要がない(はずな)ので引数のチェックだけしています.
UDFではhogefunc_init(UDF_INIT *initid, UDF_ARGS *args, char *message)という形で初期化関数を作成するみたいです.
initid->maybe_nullですが,1にセットして mregexp_version に return NULL; や return 0; を記述しても普通にNULLと表示さるだけだったので,0がいいのか1(デフォルト?)がいいのかよくわかりません…
UDFに関してはMySQLのドキュメントを参照.

あとはライブラリを作成し直してMySQLを再起動すればよいです.

$ cd /path/to/mregexp-1.0
$ make clean
$ make
$ sudo make install
$ sudo /etc/init.d/mysql restart
$ mysql -uroot -p
mysql> CREATE FUNCTION mregexp_version RETURNS STRING SONAME 'mregexp.so';
Query OK, 0 rows affected (0.00 sec)

mysql> select mregexp_version();
+---------------------------------------+
| mregexp_version()                     |
+---------------------------------------+
| mregexp-1.0 [UTF-8] (oniguruma-4.7.1) |
+---------------------------------------+

うん,できてる.

参考: CentOS 5.5 MariaDB 5.1.49にUDF mregexpを入れてみる - イノベートな非日常
———————————————————————

インストール方法まとめ

以下,32ビット版Ubuntu 10.04の場合です.

MySQLの開発用パッケージをインストール

$ sudo apt-get install libmysql++-dev

これで/usr/include/mysqlや/usr/lib/mysqlなどが作成され,ヘッダファイルやライブラリがインストールされます.

Oniguruma 4.Xをインストール

$ wget http://www.geocities.jp/kosako3/oniguruma/archive/onig-4.7.1.tar.gz
$ tar zxvf onig-4.7.1.tar.gz
$ cd onig-4.7.1/
$ ./configure 
$ make
$ sudo make install
$ sudo ldconfig

これで/usr/local/libにライブラリがインストールされます.

mregexpをインストール

mregexp_version を作成できるように,mregexp.c を変更する部分を追加しました - 2010/08/31

$ wget http://www.irori.org/dl/tool/mregexp-1.0.tar.gz
$ tar zxvf mregexp-1.0.tar.gz
$ cd mregexp-1.0/

mregexp.c に次の関数を追加

my_bool mregexp_version_init(UDF_INIT *initid,
                             UDF_ARGS *args,
                             char *message) {
  DPRINT(">>mregexp_version_init");

  if (args->arg_count > 0) {
    strcpy(message, "mregexp_version(): requires no arguments.");
    return 1;
  }

  initid->maybe_null = 0;
  return 0;
}

コンパイルしてコピー

$ make
$ sudo install -m 444 -p mregexp.so /usr/lib/mysql/plugin/

※デフォルトの文字コードを変更したい場合はMakefileを変更する
※/usr/lib/mysql/pluginはMySQLのplugin_dir

次の2行を/etc/apparmor.d/usr.sbin.mysqldに追加してmysqldがライブラリを使用できるようにします.

/usr/lib/mysql/plugin/* mr,
/usr/local/lib/* mr,

※/usr/lib/mysql/pluginはmregexp.soの存在するディレクトリで/usr/local/libはlibonig.so.1の存在するディレクトリ
※セキュリティを考慮するとlibonig.so.1のディレクトリは変えた方がいいのかも

AppArmorの再起動.

$ sudo /etc/init.d/apparmor restart

mregexp関数の作成

mysql> CREATE FUNCTION mregexp RETURNS INTEGER SONAME 'mregexp.so';
mysql> CREATE FUNCTION mregexp_version RETURNS STRING SONAME 'mregexp.so';

※mregexp_versionはMySQL 5.1だと作成できないようなのでmregexpだけ

とりあえず使ってみる

mregexpは

mregexp(expr,pat[,encoding])

という形で使用します.exprがフィールド名,patが正規表現,encodingがexprとpatの文字コードです.
encodingは省略可能で,その場合はMakefileで指定したデフォルト文字コードが使用されます.

ではでは,実際に使ってみましょう.

まずはテスト用のデータベース・テーブルを作成します.
※全てUTF-8で統一してあります

mysql> create database mregexp_test;
mysql> use mregexp_test;
mysql> create table sample (id int primary key auto_increment, text varchar(10));
mysql> insert into sample (text) values('あいう'), ('<笑>'), ('<?>');
mysql> select * from sample;
+----+-----------+
| id | text      |
+----+-----------+
|  1 | あいう    |
|  2 | <笑>    |
|  3 | <?>    |
+----+-----------+

それでは . を試してみます.

mysql> select * from sample where text regexp 'あ.う';
Empty set (0.00 sec)

mysql> select * from sample where mregexp(text, 'あ.う');
+----+-----------+
| id | text      |
+----+-----------+
|  1 | あいう    |
+----+-----------+

おおぉぉぉ,mregexpだとちゃんと抽出できてますね!

お次は<[^>]+>です.

mysql> select * from sample where text regexp '<[^>]+>';
+----+-----------+
| id | text      |
+----+-----------+
|  2 | <笑>    |
+----+-----------+

mysql> select * from sample where mregexp(text, '<[^>]+>');
+----+-----------+
| id | text      |
+----+-----------+
|  2 | <笑>    |
|  3 | <?>    |
+----+-----------+

おぉ!!!!!すばらしい!インストールに苦労しただけに感動ものです!!

おまけ

MySQL 4.1ではEUC-JPで正規表現を使った場合のおもしろいバグが紹介されていますが,MySQL 5.1では解決されているみたいです.
MySQL 5.1のドキュメントにはそのような説明がないですし.

mysql> select hex('い'); select hex('イあ');
+-----------+
| hex('い')   |
+-----------+
| A4A4      |
+-----------+

+-------------+
| hex('イあ')     |
+-------------+
| A5A4A4A2    |
+-------------+

mysql> select 'イあ' like '%い%';
+--------------------+
| 'イあ' like '%い%'       |
+--------------------+
|                  0 |
+--------------------+

‘イあ’のEUC-JPコードA5A4A4A2に’い’のEUC-JPコードであるA4A4が含まれているので,MySQL 4.1だとマッチするみたいなんですけどねぇ….

以上,mregexpをインストールするためにいろいろ迷走したわけですが,
2006年に作成されたREADMEに従ってもなかなかうまく行かないところに時代の流れを感じましたね・・・

  1. 理屈としては,’[^>]+’だと’>’に含まれる16進数値のいずれも含まていなければマッチするということかと思います.使用している文字コードがサポートされていればマルチバイト文字でも1文字として扱われます. 

広告
F値に調和平均を使う理由 Auto Complete ModeのPerl辞書を作ってみた
※このエントリーははてなダイアリーから移行したものです。過去のコメントなどはそちらを参照してください