Paradigm Shift Design

ISHITOYA Kentaro's blog.

iPhoneSDKのsqlite3でユーザー定義関数を使えるようにする

sqlite3にはlog関数がない。


ただ、SQLite3 Contributionsというのがあって、そこにextension-functions.cがある。これを使えればlog/log10だけじゃなくて、

Provide mathematical and string extension functions for SQL queries using the loadable extensions mechanism. Math: acos, asin, atan, atn2, atan2, acosh, asinh, atanh, difference, degrees, radians, cos, sin, tan, cot, cosh, sinh, tanh, coth, exp, log, log10, power, sign, sqrt, square, ceil, floor, pi. String: replicate, charindex, leftstr, rightstr, ltrim, rtrim, trim, replace, reverse, proper, padl, padr, padc, strfilter. Aggregate: stdev, variance, mode, median, lower_quartile, upper_quartile.

とか色々使えるようになるらしいんだけど、iphoneSDK上でdylib作ってインクルードする方法がいまさんくらい分からない*1ので、あきらめて、
Distance function for sqlite | This Much I KnowのDaveさんがやっている方法で行く。


とりあえず、てきとうなC言語のヘッダファイルとソースファイルに、目的の関数を作る。
logとlog10が必要なだけなので、ヘッダファイルは以下の通り

//  sqlite3_extension_functions.h
#ifndef SQLITE3_EXTENSION_FUNCTIONS
#define SQLITE3_EXTENSION_FUNCTIONS
void sqlite3_extension_log(sqlite3_context *context, int argc, sqlite3_value **argv);
void sqlite3_extension_log10(sqlite3_context *context, int argc, sqlite3_value **argv);
#endif


実装は引数のチェック後に、標準関数を呼び出しているだけ。

//  sqlite3_extension_functions.c

#include "sqlite3.h"
#include "sqlite3_extension_functions.h"
#include "math.h"
#include "assert.h"

void sqlite3_extension_log(sqlite3_context *context, int argc, sqlite3_value **argv)
{
    assert(argc == 1);
    if (sqlite3_value_type(argv[0]) == SQLITE_NULL){
        sqlite3_result_null(context);
        return;
    }
    double value = sqlite3_value_double(argv[0]);
    sqlite3_result_double(context, log(value));
}

void sqlite3_extension_log10(sqlite3_context *context, int argc, sqlite3_value **argv)
{
    assert(argc == 1);
    if (sqlite3_value_type(argv[0]) == SQLITE_NULL){
        sqlite3_result_null(context);
        return;
    }
    double value = sqlite3_value_double(argv[0]);
    sqlite3_result_double(context, log10(value));
}


で、sqlite3_openの直後に、sqlite3_create_functionを呼び出して関数を登録する。

int err = sqlite3_open([databasePath fileSystemRepresentation], &db );
if(err != SQLITE_OK) {
    NSLog(@"error opening!: %d", err);
    return NO;
}

//functionの読み込み
sqlite3_create_function(db, "log", 1, SQLITE_UTF8, NULL, &sqlite3_extension_log, NULL, NULL);
sqlite3_create_function(db, "log10", 1, SQLITE_UTF8, NULL, &sqlite3_extension_log10, NULL, NULL);


第2引数が、SQL内での関数名。第3引数が引数の数。
あとは特に気にしなくていいのでは。


で、

SELECT log(1), log10(1)

とかできる。


まぁ、できればextension-functions.c使いたいけど、あまりがんばるところじゃないので保留。技術力が足りない…orz

*1:sqlite_load_extensionでライブラリのエラーが出る。コンパイルオプションの問題だとは思うのだけど