SQLite3の拡張DLLをMinGWで作ってみる

SQLite はアプリケーション組み込み等の用途に使われているデータベースエンジン。2chビューアとかにも使われてるかな。version 3.3.9 から拡張DLLでユーザ定義のSQL関数等を定義できるようになって、直接バイナリに手を入れられない状況でも拡張性が高くなった。たとえばWebサーバのPHPからSQLiteを扱う人にはいいニュースなんじゃないかな。え、PHPに組み込まれてるのはSQLite version 2だって?かっこ悪いねー。

個人的にはWindowsSQLiteを扱う機会が多いので、MinGWSQLiteの拡張DLLを作成する手順を調べてみたよ。

MinGWの入手

MinGW-3.1.0-1.exe MSYS-1.0.10.exe msysDTK-1.0.1.exe をこの順番で普通にインストールして、PATH環境変数を適当に設定した。

バイナリとヘッダのダウンロード

SQliteの公式サイトのダウンロードページから次のファイルを入手する。

MinGW用のインポートライブラリの生成

拡張DLLを作るだけなら不要なんだけど、MinGWからsqlite3.dllを使う時に必要になる。
dllに同梱されているdefファイルをMinGWのdlltoolで
dlltool --dllname sqlite3.dll --input-def sqlite3.def --output-lib libsqlite3.a

コードとMakefile

下記のコードとMakefileをまとめてmake test 。

Makefile
all : doSQL.exe myExtension.dll

test : all
	rm -fr test.db
	./doSQL.exe  test.db "create table t1(id integer primary key,i1 integer,t1 text)"
	./doSQL.exe  test.db "insert into t1(i1)values(1)"
	./doSQL.exe  test.db "insert into t1(i1)values(256)"
	./doSQL.exe  test.db "insert into t1(i1)values(65536)"
	./doSQL.exe  test.db "insert into t1(i1)values(16777216)"
	./doSQL.exe  test.db "insert into t1(i1)values(4294967296)"
	./doSQL.exe  test.db "insert into t1(i1)values(1099511627776)"
	./doSQL.exe  test.db "SELECT load_extension('myExtension.dll');select *,half(i1) from t1"
	./sqlite3.exe test.db <test2.sql

doSQL.exe : doSQL.c
	gcc -g -O2 -I.. -L.. -o $@ $< -lsqlite3

myExtension.dll : myExtension.c myExtension.def
	gcc -g -O2 -I.. -c myExtension.c
	dllwrap -k -def myExtension.def --driver-name gcc -o myExtension.dll myExtension.o
	# 拡張DLLはインポートしないのでインポートライブラリは不要
doSQL.c
#include <stdio.h>
#include <string.h>
#include <sqlite3.h>

#define ENABLE_SQLITE_KEY 0

#if ENABLE_SQLITE_KEY
extern int sqlite3_key(sqlite3 *db, const void *pKey, int nKey);
#endif

static int callback(void *NotUsed, int argc, char **argv, char **azColName){
	int i;
	for(i=0; i<argc; i++){
		printf("%s=%s%c",azColName[i],(argv[i] ? argv[i] : "NULL"),(i+1==argc?&#39;\n&#39;:&#39;,&#39;));
	}
	return 0;
}
int main(int argc, char **argv){
	int retval = 1;
	if( argc < 3 ){
		fprintf(stderr, "Usage: %s DATABASE SQL-STATEMENT [password]\n", argv[0]);
	}else{
		char* tok;
		sqlite3* db;
		do{
			int rc = sqlite3_open(argv[1], &db);
			if( rc != SQLITE_OK ){
				fprintf(stderr, "Can&#39;t open database: %s\n", sqlite3_errmsg(db));
				break;
			}
#if ENABLE_SQLITE_KEY
			if( argc > 3 ){
				rc = sqlite3_key(db,argv[3],strlen(argv[3]));
				if(rc != SQLITE_OK ){
					fprintf(stderr, "sqlite3_key failed: %s\n", sqlite3_errmsg(db));
					break;
				}
			}
#endif
			rc = sqlite3_enable_load_extension(db,1);
			if(rc != SQLITE_OK ){
				fprintf(stderr, "sqlite3_enable_load_extension failed: %s\n", sqlite3_errmsg(db));
				break;
			}
			for(tok = strtok(argv[2],";");tok;tok=strtok(NULL,";")){
				char* zErrMsg =0;
				rc = sqlite3_exec(db,tok, callback, 0, &zErrMsg);
				if( zErrMsg ){
					fprintf(stderr, "SQL error: %s\n", zErrMsg);
					sqlite3_free(zErrMsg);
					fprintf(stderr, "sql=%s\n",tok);
				}
				if(rc != SQLITE_OK ){
					break;
				}
			}
			retval =0;
		}while(0);
		sqlite3_close(db);
	}
	return retval;
}
myExtension.c
#include <sqlite3ext.h>
SQLITE_EXTENSION_INIT1

// SQLユーザ関数 half() の実装例
// 入力値の半分の値を返す
static void halfFunc(
	sqlite3_context *context,
	int argc,
	sqlite3_value **argv
){
	sqlite3_result_double(context, 0.5*sqlite3_value_double(argv[0]));
}

// SQLite拡張の初期化。DLLロード時に呼ばれる。
// 共有ライブラリ中の拡張シンボルはこれ一つで十分ですよ。
// ここでユーザ関数、照合順序、仮想テーブルモジュールを定義すること。
int __stdcall sqlite3_extension_init(
  sqlite3* db,
  char** pzErrMsg,
  const sqlite3_api_routines *pApi
){
	SQLITE_EXTENSION_INIT2(pApi)
	sqlite3_create_function(db, "half", 1, SQLITE_ANY, 0, halfFunc, 0, 0);
	return 0;
}
myExtension.def
LIBRARY myExtension.dll
EXPORTS
sqlite3_extension_init@12
test2.sql
SELECT load_extension(&#39;myExtension.dll&#39;);
select *,half(i1) from t1;