SQLiteを使ってみる

目標は、普通にDBを使える。拡張モジュールを作れるようになる。 そのために拡張モジュールロード、ユーザーSQL関数の登録のコードを少し追う。

使い方の資料

これらを読んで下のようなサンプルを書いて動く事を確認する。

C Interface sample

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <sqlite3.h>
#include <stdio.h>
#include <string.h>

int main(void) {
  sqlite3 *pDB = NULL;
  int err = sqlite3_open("./test.data", &pDB);
  if (err != SQLITE_OK) {
    printf("err %d\n", err);
  }
  const char *sql = "SELECT id FROM test";
  sqlite3_stmt *stmt=NULL;
  sqlite3_prepare(pDB, sql, strlen(sql), &stmt, NULL);

  sqlite3_reset(stmt);
  int r;
  while (SQLITE_ROW == (r=sqlite3_step(stmt))){
    double  id = sqlite3_column_double(stmt, 0);
    printf("%f\n", id);
  }
  if (SQLITE_DONE != r){
    printf("in ERROR SQLITE_DONE\n");
  }
  sqlite3_finalize(stmt);
  return 0;
}
1
2
3
4
5
6
7
[vagrant@pit(10.0.2.2) bld]$ gcc ./main.c -lsqlite3 -o sample; ./sample
1.000000
2.000000
3.000000
5.000000
5.800000
-5.800000

拡張モジュールでできる事

Run-Time Loadable Extensionsを読んでサンプルを真似して拡張モジュールを作成できる。 拡張モジュールで実現できる事は主に4つ。

拡張モジュールのロード

Cから行う場合、拡張モジュールはsqlite3_load_extension()で読み込む。 (事前にsqlite3_enable_load_extensionを使いloadを許可する必要がある。SQLiteシェルは事前に実行している。) コマンドとして.loadコマンド、SQL関数としてload_extension()関数も提供されている。

共有ライブラリのバイナリの中で次のどちらかの関数を含めると機能する。

  1. sqlite3_extension_init
  2. sqlite3_(file名 - lib)_init

また、モジュールの内容を静的リンクしたSQLite シェルを作りたい場合、 上記エントリポイントをsqlite3_auto_extension に渡しておくと機能するように挟み込める。

拡張モジュール・サンプル

簡単なモジュールを作ってみた。 稼働させると下のように引数を無視して12345を返す。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
[vagrant@pit(10.0.2.2) hoge]$ ../.libs/lt-sqlite3 ../test.data
SQLite version 3.9.0 2015-10-14 12:29:53
Enter ".help" for usage hints.
sqlite> .loadd hoge
Error: unknown command or invalid arguments:  "loadd". Enter ".help" for help
sqlite> .load hoge
sqlite> select * FROM test;
1
2
3
5
5.8
-5.8
sqlite> select masumi_num(id) from test;
12345
12345
12345
12345
12345
12345

ロード実装

sqlite3_load_extension()内部でsqlite3LoadExtension()を呼んでいてこれがsqlite3OsDlOpen()を呼び拡張モジュールファイルをオープンする。 sqlite3OsDlOpen()はOSなどを隠蔽するだけでUNIXの場合unixDlOpenが実体となりdlopen()により呼び出している。

sqlite3LoadExtension()の返値は動的ライブラリのハンドルでhandleに格納される。 そして初期化処理としてsqlite3OsDlSymの呼び出しに使われる。 UNIX系では内部でdlsymにより初期化用関数のアドレスを取得している。 初期化関数の探索sqlite3OsDlSymは 関数内部でsqlite3_extension_init,sqlite3_(file名 - lib)_initの順序で試みており dlsymで得られたアドレスを関数ポインタへキャストしてxInitに格納し実行している。 そのため拡張モジュール開発者は上記の関数を提供する必要がある。

ユーザー関数定義

dbに関数定義を行うには下の関数を使えばよい。eTextRepにはエンコード文字種別を登録する。 通常の関数はxFuncのみ、集約関数はxStep, xFinalのみを用いる。それぞれ使わない引数にはNULLを入れる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int sqlite3_create_function(
  sqlite3 *,
  const char *zFunctionName,
  int nArg,
  int eTextRep,
  void*,
  void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
  void (*xStep)(sqlite3_context*,int,sqlite3_value**),
  void (*xFinal)(sqlite3_context*)
);
int sqlite3_create_function16(
  sqlite3*,
  const void *zFunctionName,
  int nArg,
  int eTextRep,
  void*,
  void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
  void (*xStep)(sqlite3_context*,int,sqlite3_value**),
  void (*xFinal)(sqlite3_context*)
);
#define SQLITE_UTF8     1
#define SQLITE_UTF16    2
#define SQLITE_UTF16BE  3
#define SQLITE_UTF16LE  4
#define SQLITE_ANY      5

SQLユーザー関数で使えるSQL用APIには以下がある。

sqlite3_create_functionの挙動について

sqlite3_create_functionsqlite3_create_function_v2をラップしている。

内部ではsqlite3CreateFuncを経由してsqlite3FindFunctionが呼び出されている。 これが呼び出されこれが登録処理の中核になっている。

名前としてはsqlite3FindFunctionとFindになっているが、 末尾引数に 0or1を与えられる様になっており存在しない場合に関数の登録先を確保する事も出来る。 そして実際に作る際は、事前に重複するインタフェースや名前を持つ関数が無い事を確認した後、再度Findで格納場所を確保している。

またビルトイン関数と同様FuncDef構造体として保存される。

comments powered by Disqus