1. 無料アクセス解析
PGKissシェアードライブラリを使用してSQLiteに独自関数を追加する方法 2012/4/30

シェアードライブラリを使用してSQLiteに独自関数を追加する方法

SQLiteはBLOB(Binary Large Object)を扱えるので、アプリケーション固有のデータをそのままDBに保存することができる。しかし、アプリケーションにとって意味のある演算をBLOBに対して行うことができるSQL関数はほとんどない。そのため、BLOBを操作する場合、コードをゴリゴリ書かなければならない。しかし、BLOBを操作するSQL関数を一揃え用意しておけば、コードをゴリゴリ書かなくても、SQLだけである程度の処理を記述できるようになる。 例えば、BLOBに保存されたJPEGイメージから、撮影されたGPS位置情報を取り出す関数を用意しておけば、SQLを書くだけである場所の近隣で撮影された画像を検索できる。

SQLiteは簡単にSQL関数を拡張(追加)することができる。ここでは、例として2次元の2点間の距離を求める関数distanceを追加してみる。関数の使い方は以下のとおり。

distance(x1, y1, x2, y2)

この関数をシェアードライブラリ(共有ライブラリ、動的リンクライブラリ)として実装しておくと、SQLiteのSQL関数load_extensionを使用して、実行時に読み込むことができる。このように実装することで、自作のアプリケーション以外の、例えばコマンドラインツールsqlite3のような既存のプログラムに対しても、SQL関数を追加できる。

実装

実際のプログラムは下記のようになる。
sqlite_ext.c

SQLiteのwikiに書いてあるように、まず、次のように記述する。これは、シェアードライブラリ内ではSQLiteの名前解決の方法が通常と異なるためで、sqlite3ext.hの中身を見れば、何を行っているかすぐにわかる。

  • sqlite3.hの代わりにsqlite3ext.hをインクルードする。
  • プログラムの先頭、関数の外側にSQLITE_EXTENSION_INIT1を記述する。
  • エントリーポイントの関数の先頭に、エントリーポイントの第三引数を引数としてSQLITE_EXTENSION_INIT2を記述する。

関数sqlite3_extension_initは、この拡張のエントリーポイントである。任意の名前でよいがsqlite3_extension_initと言う名前にしておけば、load_extensionでこの拡張をロードする際に、エントリーポイント名を省略できる。 エントリーポイントのシグネチャは、上記のとおりでなければならない。正常終了の場合は0を返す。エラーの場合は、第二引数にsqlite3_mprintfを使用してエラーメッセージを設定して、0以外を返す。

エントリーポイントの関数内では、sqlite3_create_function_v2を使用して関数を登録する。

distanceFunc関数が、今回追加するSQL関数の実装となる。引数のいずれかがNULLなら、結果もNULLとする。 sqlite3_value_numeric_typeの結果、数値型に変換できない引数を含む場合は、エラーとする。 正常に処理できたら、sqlite3_result_doubleを使用して、戻り値を設定する。

コンパイル、リンク

ここでは、Linuxでシェアードライブラリとしてコンパイル、リンクする方法を示す(詳細はこちらを参照)。

gcc -shared -fPIC sqlite_ext.c -o libsqlite_ext.so.1.0.0 -Wl,-soname,libsqlite_ext.so.1 -L/usr/lib/i386-linux-gnu -lsqlite3 -lm

-Lオプションの直後には、sqlite3のライブラリが置いてあるディレクトリを指定する。

実行

カレントディレクトリに上記で作成したlibsqlite_ext.so.1.0.0があるとすると、次のようにしてロード、実行できる。 環境変数LD_LIBRARY_PATHで、シェアードライブラリの検索パスにカレントディレクトリを指定している。

LD_LIBRARY_PATH=. sqlite3 test.db
SQLite version 3.7.7 2011-06-23 19:49:22
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> select load_extension("libsqlite_ext.so.1.0.0");   -- または .load libsqlite_ext.so.1.0.0
sqlite> select distance(0, 2, 1, 2);
1.0
sqlite> select distance(0, 2, 1, "ddd");
Error: Invalid argument.
sqlite> .quit
inserted by FC2 system