[SQLite]CでSQLiteのユーザ定義関数を作る その2

2016年11月7日月曜日

sqlite

その1では拡張ライブラリのビルドをしてみたけど、今回は実際に自分で関数を作ってみる。
複数の引数を渡されるとそれらの積を返すスカラー関数を作ってみよう。

動作イメージ
> select product(2, 3, 4);
24

productfunctions.c
#include <stddef.h>
#include "sqlite3ext.h"      // 1.

SQLITE_EXTENSION_INIT1       // 2.

// 3.
static void productFunc(sqlite3_context *context, int argc, sqlite3_value **argv) {
    if (argc == 0) {
        sqlite3_result_int64(context, 0);
        return;
    }

    sqlite_int64 product = 1;
    int i;
    for (i = 0; i < argc; i++) {
        if (sqlite3_value_type(argv[i]) == SQLITE_NULL) return;
        product *= sqlite3_value_int64(argv[i]);
    }
    sqlite3_result_int64(context, product);
}

// 4. 
int sqlite3_extension_init(
    sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi){
    SQLITE_EXTENSION_INIT2(pApi);

    int res = sqlite3_create_function(
              db, "product", -1, SQLITE_ANY, NULL, productFunc, NULL, NULL);
    if (res != SQLITE_OK) {
        *pzErrMsg = sqlite3_mprintf("Can't create product function.");
        return 1;
    }

    return 0;
}

ポイントは以下な感じ。
  1. "sqlite3ext.h"をインクルードする。
  2. SQLITE_EXTENSION_INIT1マクロを宣言する。
    これはSQLiteの拡張モジュールを作成するときの決まりごとなので、はじめに書こう。
    実際はこの中でグローバル変数が定義されていて、後で使うSQLITE_EXTENSION_INIT2で値が代入される。
  3. 今回、実装するproduct関数の本体だね。
    Cのmain関数と同様に引数はargcとargvで渡ってくる。
    今回はsqlite_int64型が渡ってくることを前提として、型チェックは最小限しかしていない。
    引数からの値の取り出しは型に応じたsqlite3_value_*関数で行う。
    関数からの戻り値も型に応じたsqlite3_result_*関数で設定する。
  4. 作成した関数をSQLiteの関数として登録する。
    最初にSQLITE_EXTENSION_INIT2を呼び出す。
    sqlite3_create_functionを使って、関数を登録する。
    第2引数にSQLでの関数名、第3引数に期待する引数の数、第6引数に実行される関数を指定する。
    今回は引数の数は可変のため-1を指定する。
各関数の詳細は以下を参照してほしい。

An Introduction To The SQLite C/C++ Interface
C-language Interface Specification for SQLite

これをその1でやったsqlite-extensionと同じ要領でビルドする。

$ gcc -I../sqlite-amalgamation-3150000gcc -I../sqlite-amalgamation-3150000 -L../sqlite-amalgamation-3150000 -fPIC -shared -Os productfunctions.c -o libproductfunctions.so -L../sqlite-amalgamation-3150000
$ LD_LIBRARY_PATH=./../sqlite-amalgamation-3150000/ ../sqlite-amalgamation-3150000/sqlite3
SQLite version 3.15.0 2016-10-14 10:20:30
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> .load libproductfunctions
sqlite> select product(2, 3, 4);
24
sqlite>

product関数が動作したね!