Developer's Blog

【Delphi】 SQLite に正規表現関数を追加してみる

こんにちは。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 アカウントでは、フェンリルプロダクトの最新情報などをつぶやいています。よろしければフォローしてください!

 

フェンリルの開発者アカウントでは、フェンリルプロダクトの最新開発情報などをつぶやいています。よろしければフォローしてください!

 

 

Copyright © 2019 Fenrir Inc. All rights reserved.