SQLite3におけるREGEXP演算子

ちょっとした用で、SQLite3(via ODBC) を始めてみた。その用件で必要なのは項目に対する正規表現マッチング。LIKE 演算子で済ませなくもないけど、やっぱり楽したい。
SQL における正規表現マッチングに付いて調べてみると、 SQL99 にて、SIMILAR TO 演算子による正規表現のサポートがあるとのこと。だけど、SQLite は SQL92 ベース、そんなもの実装されているはずがない。標準に頼るのは諦めて各 DBMS について見ていくと、Oracle では REGEXP_LIKE 関数、SQL Server では SQL CLR によるサポート、MySQL では REGEXP 演算子PostgreSQL では ~演算子・・・とまちまちなことが分かった。
では、SQLite は・・・というと、MySQL と同じ REGEXP 演算子が備わっていた・・・が、標準の構成だとこの演算子は機能してくれない。このことについては、公式サイトで次のように説明されている。

The REGEXP operator is a special syntax for the regexp() user function. No regexp() user function is defined by default and so use of the REGEXP operator will normally result in an error message. If a user-defined function named "regexp" is added at run-time, that function will be called in order to implement the REGEXP operator.

http://www.sqlite.org/lang_expr.html

つまり、REGEXP 演算子を使いたければ、ユーザ側で正規表現にマッチするか確認する関数を、SQLite に "regexp" という名前のユーザ関数として登録しなければならない。なんという丸投げ・・・。
SQLite では、load_extension 関数を用いて、外部の拡張モジュールをロードすることが出来る。拡張モジュールは、いわばユーザ関数ライブラリで、SQLite3 ODBC Driver には標準で BLOB二次元マッピング拡張(sqlite3_mod_blobtoxy.dll)、外部データ取込・出力拡張(sqlite3_mod_impexp.dll)、全文検索拡張(sqlite3_mod_fts3.dll) が付属している。これらと同様にして、正規表現マッチングを行う regexp ユーザ関数を持つ拡張モジュールを制作し、ロードすれば、お目当ての REGEXP 演算子が使えるわけだ。
付属している拡張モジュールのソースコードを参考にしながら、boost/regexを使ってやっつけてみた。

// for Visual C++
#include <boost/regex.hpp>
#include <sqlite3ext.h>
extern "C" {
	SQLITE_EXTENSION_INIT1
	static void regexp_func(sqlite3_context *context, int argc, sqlite3_value **argv) {
		if (argc >= 2) {
			const char *target  = (const char *)sqlite3_value_text(argv[1]);
			const char *pattern = (const char *)sqlite3_value_text(argv[0]);
			try {
				boost::regex ereg(pattern, boost::regex_constants::perl);
				sqlite3_result_int(context, boost::regex_search(target, ereg));
			} catch (boost::regex_error &e) {
				sqlite3_result_error(context, e.what(), 0);
			}
		}
	}
	__declspec(dllexport) int sqlite3_extension_init(sqlite3 *db, char **errmsg, const sqlite3_api_routines *api) {
		SQLITE_EXTENSION_INIT2(api);
		return sqlite3_create_function(db, "regexp", 2, SQLITE_UTF8, (void*)db, regexp_func, NULL, NULL);
	}
}

boost/regex の char 特殊化版が UTF-8 を扱えたかどうかは微妙だけど当面は ASCII-7bit の範囲でしか使わないので、このままにしておくことに。
ビルドして試しに使ってみると、おお、動いた動いた。

' ---- RegExpTest.vbs ---
' Build the path to the Database file
DBPath = Replace(WScript.ScriptFullName, WScript.ScriptName, "RegExpTest.db")
' Open the connection to test DB
Set DB = CreateObject("ADODB.Connection")
DB.Open "DRIVER=SQLite3 ODBC Driver;Database=" & DBPath & ";"
' Load RegExp extension module
DB.Execute("SELECT load_extension('sqlite3_mod_regexp.dll');")
' Prepare the test data
DB.Execute "CREATE TABLE aliases (pattern, alias);"
DB.Execute "INSERT INTO aliases VALUES ('^anon_\d', 'My alias');"
' Test REGEXP operator
Set RS = DB.Execute("SELECT alias FROM aliases WHERE 'anon_193@localhost' REGEXP pattern;")
WScript.Echo RS.GetString
' Close objects
RS.Close
Set RS = Nothing
DB.Close
Set DB = Nothing
>cscript //NOLOGO RegExpTest.vbs
My alias

>

パフォーマンスはどんな感じなのか、ちょっと気になるなぁ・・・。だけど、いちいち計るのに時間掛けたくないので、これで fix にしておこう。