sqlite3-ruby1.2.1にsqlite3_load_extension()がない!

あれこれ見ていて気がついた。現状では、C言語正規表現処理を書いてもRubyからSQLiteに組み込めない、ってことかな。sqlite3-ruby経由だと".load"も"select load_extencion()"もエラーになるわけで。
よって選択肢は2つ。

  • SQLite3に正規表現処理を埋め込み、ビルドして使う。
  • sqlite3-rubyを改造してsqlite3_load_extensionを追加する。

DBを改造・ビルドして使う、というのは荒技すぎるかも。まずはRubyライブラリ側の修正でなんとかしてみませう。

sqlite3-rubyを改造する:作業メモ:あとでまとめる。

とりあえずOSXオンリーで、とにかく動くだけの改造。


gemでインストールされた部分(/opt/local/lib/ruby/gems/1.8/gems/sqlite3-ruby-1.2.1/)を覗いてみる。すると

/opt/local/lib/ruby/gems/1.8/gems/sqlite3-ruby-1.2.1/ext/sqlite3_api/
sqlite3_api.i
sqlite3_api_wrap.c

がある。.iってインターフェース定義だよね。ビルドでは不要かな・・・えーい面倒だ、両方とも切れ切れ、切り捨てい!(最低)

sqlite3_api.i (create_functionの上に差し込み)

int sqlite3_load_extension(sqlite3*,const char*name,char*entrypoint,char**errmsg);

sqlite3_api_wrap.c (create_functionの上に差し込み)

/* ちょう適当。エラーメッセージ文字列を無視。 */
static VALUE
_wrap_sqlite3_load_extension(int argc, VALUE *argv, VALUE self) {
    sqlite3 *arg1 = (sqlite3 *) 0 ;
    char *arg2 = (char *) 0 ;
    char *arg3 = (char *) 0 ;
    char *arg4 = (char *) NULL ;
    int result;
    VALUE vresult = Qnil;
    
    if ((argc < 4) || (argc > 4))
    rb_raise(rb_eArgError, "wrong # of arguments(%d for 4)",argc);
    SWIG_ConvertPtr(argv[0], (void **) &arg1, SWIGTYPE_p_sqlite3, 0);
    arg2 = StringValuePtr(argv[1]);
    arg3 = StringValuePtr(argv[2]);
    result = (int)sqlite3_load_extension(arg1,(char const *)arg2,arg3,NULL);
    
    vresult = INT2NUM(result);
    return vresult;
}

このように書き換えて、

$ sudo make clean
$ sudo make all

これで sqlite3_api.bundle がビルドされるので、sqlite3-ruby-1.2.1/lib にコピー。


つぎ、sqlite3-ruby-1.2.1/lib/sqlite3 で

$ mate .

と打ち込んでTextMateで開き、プロジェクト検索で「create_function」定義を含むファイルを抽出。

sqlite3-ruby-1.2.1/lib/sqlite3/database.rb
sqlite3-ruby-1.2.1/lib/sqlite3/driver/dl/driver.rb
sqlite3-ruby-1.2.1/lib/sqlite3/driver/dl/api.rb
sqlite3-ruby-1.2.1/lib/sqlite3/driver/native/driver.rb

の4本。かってに改蔵

database.rb (create_functionの上に差し込み)

    def load_extension( name )
puts "begin load_extension(database.rb)..." 
      result = @driver.load_extension( @handle, name, nil, nil )
puts "end   load_extension(database.rb): #{result}" 
#      Error.check( result, self )
      self
    end

※エラーチェックを呼ぶと落ちるのでコメントアウト

dirver/dl/api.rb (create_functionの上に差し込み)

    extern "int sqlite3_load_extension(db,const char*,const char*,const char**)"

dirver/dl/driver.rb (create_functionの上に差し込み)

    def load_extension( db, name, entrypoint, msg )
puts "begin load_extension(dl/driver.rb) [#{name}]..." 
      result = API.sqlite3_load_extension( db, name, '', '' )
puts "end   load_extension(dl/driver.rb): #{result}" 
      return result
    end

driver/nateve/driver.rb (create_functionの上に差し込み)

    def load_extension( db, name, entrypoint, msg )
puts "begin load_extension(native/driver.rb) [#{name}]..." 
      result = API.sqlite3_load_extension( db, name, '', '' )
puts "end   load_extension(native/driver.rb): #{result}" 
      return result
    end

改造は以上。


ためしにRailsから呼んでみる。ApplicationControllerに強引にテストコードを追加。

app/controllers/application.rb

class ApplicationController < ActionController::Base
# ...
  db = ActiveRecord::Base.connection.instance_variable_get(:@connection)
  db.load_extension('a')  
end

として、適当にページを表示してコンソールを見ると、

begin load_extension(database.rb)...
begin load_extension(native/driver.rb) [a]...
end   load_extension(native/driver.rb): 1
end   load_extension(database.rb): 1

と出た。load_extension を呼び、失敗値を返しているように見える。とりあえず動作した。
あとは実際に機能する拡張モジュールを書いて試してみれば良い。が、疲れたので本日はここまで。