こんにちは。FenrirFS 開発担当の福満です。
今日は Delphi を使って SQLite 用の拡張を作成し、正規表現関数を追加してみたいと思います。
ソースコードとコンパイル済 DLL は こちら からダウンロードしてご利用いただけます。
* ご利用に際しては自己責任にてお願いいたします。
* 今回作成した DLL は、Delphi XE2 でコンパイルし、 sqlite3.exe で動作確認をしています。
正規表現ライブラリ
Delphi の最新版、Delphi XE2 には、正規表現ライブラリ(System.RegularExpressions の TRegEx)が含まれています。
試してみた感じではサードパーティ製のものと比較しても使いやすく、パフォーマンスも優秀なので、今日はこれを使いたいと思います。
TRegEx の使用例は下記の通りです。
var reg : TRegEx; begin // コンストラクタで正規表現を指定する。 reg := TRegEx.Create( 'お$' ); // IsMatch で検査 ShowMessage( BoolToStr( reg.IsMatch( 'あいうえお' ), True)); // True ShowMessage( BoolToStr( reg.IsMatch( 'かきくけこ' ), True)); // False
SQLite 拡張の仕組み
sqlite3.exe から 下記の SQL を実行すると、引数に指定した DLL 内の sqlite3_extension_init() という関数が呼び出されるので、その中で必要な処理を行います。
SELECT load_extension( "DLL のパス");
パスは相対パスでも絶対パスでも良いようです。
sqlite3.dll から拡張をロードするには、上記の SQL を実行する前に sqlite3_enable_load_extension() エクスポート関数を使って拡張をロードする機能を有効にしておく必要があります。
正規表現関数の実装
sqlite3_extension_init() に sqlite3_api_routines 構造体へのポインタが渡ってくるので、そのメンバ関数の create_function() を使って 正規表現関数の追加を行います。
var pApi : PSqlite3_Api_Routines; procedure regex_destructor( aux_data: Pointer); cdecl; var p : PRegEx; begin if aux_data <> nil then begin p := aux_data; Dispose( p ); end; end; procedure regex( context: TSqlite3_Context; argc: Integer; args: PSqlite3ValueArray); cdecl; var target, pattern : PAnsiChar; wtarget, wpattern : string; preg : PRegEx; nRet : Integer; s : UTF8String; begin try nRet := 0; if argc >= 2 then begin preg := pApi.get_auxdata( context, 1 ); if preg = nil then begin wpattern := ''; pattern := pApi.value_text(args^[1]); if pattern <> nil then begin wpattern := UTF8ToUnicodeString( pattern ); end; New( preg ); preg.Create( wpattern, [roIgnoreCase]); pApi.set_auxdata( context, 1, preg, @regex_destructor ); end; target := pApi.value_text( args^[0]); if (target <> nil) then begin wtarget := UTF8ToUnicodeString( target ); if wtarget <> '' then begin if preg.IsMatch( wtarget ) then begin nRet := 1; end; end; end; end; pApi.result_int( context, nRet); except on E: Exception do begin s := UTF8Encode( 'regex error!! ' + E.Message); pApi.result_error( context, Pointer(s), -1 ); end; end; end; function sqlite3_extension_init(db: Pointer; pzErrMsg: PPAnsiChar; p: PSqlite3_Api_Routines): Integer; cdecl; var sFuncName : AnsiString; begin pApi := p; sFuncName := 'regex'; pApi.create_function( db, PAnsiChar(sFuncName), 2, SQLITE_UTF8, nil, @regex, nil, nil ); Result := 0; end;
get_auxdata() 、set_auxdata() 使って正規表現のデータをキャッシュし、正規表現のコンパイルが何度も走らないようにしています。
set_auxdata() にはデストラクタ関数を渡すことが出来、これを利用してデータの破棄を行っています。
関数名を regexp とすると演算子としても動作するようですが、1 レコードごとにデストラクタが呼ばれ auxdata が破棄されてしまう現象があったため、今回は別の名前にしています。
使い方とまとめ
追加した関数 regex() は下記のように、第 1 引数にカラム名、第 2 引数に正規表現を指定して使用します。
SELECT filename FROM files WHERE regex(filename, "Sleipnir.*test.*\.exe");
SQLite にユーザー定義関数を追加する際には本記事を参考にしていただけたら幸いです。
今後もフェンリルへの応援をよろしくお願いいたします。
フェンリルのオフィシャル Twitter アカウントでは、フェンリルプロダクトの最新情報などをつぶやいています。よろしければフォローしてください!
Follow @fenrir_official
フェンリルの開発者アカウントでは、フェンリルプロダクトの最新開発情報などをつぶやいています。よろしければフォローしてください! Follow @fenrir_dev