SQLiteでLoadable Extensionsを静的リンクする
about
この記事は Run-Time Loadable Extensions で書かれている Statically Linking A Run-Time Loadable Extension
をやる。
SQLiteのversionは3.40以降
Loadable Extension とは
Loadable ExtensionはSQLiteに関数とか何やかんやを足すことができる機能である。
基本的な使用方法としてはsqlite cliから .load ライブラリへのpath
を使うとloadすることができる。
今回、Loadable Extension を作ることはせずに既存のextensionを使っていく。
そもそもの発端としては sqlite内でbase64のencode/decodeしたかったので以下を使う。
なんでsqlite内でbase64のencode/decodeをしたかったのか、という点は直接聞いてもらえれば。
ビルド編
実は最近はLoadable Extensionの仕様が変わっており、上記のcodeは現代的な書き方ではないので変更していく
変更し終わったコードは以下。
nix build .#sqlite3-base64
でビルドできるはず。
macの場合、 export DYLD_LIBRARY_PATH=$(nix build .#sqlite3-base64 --print-out-paths --no-link)/lib
のようにしてあげれば
sqlite cli 内で.load libbasesixtyfour
で loadできるようになる。
勿論、 .load 絶対PATH
でも良い
以下、変更点解説など
変更点: #include <sqlite3ext.h>
拡張用のheaderになっている
変更点: sqlite3_basesixtyfour_init
ちょっと未確認 だが、.load libxxxx.so
で呼び出した際に、 sqlite3_xxxx_init
が呼び出されるようだが、xxxxに数字が含まれるとうまくいかなかったので名前を変えている。
libファイルの名前の制約のように感じる・
変更点: SQLITE_EXTENSION_INIT*
静的リンクする場合に備えたmacroで SQLITE_CORE
が定義されているかどうかで挙動が変わるようになっている
静的リンクに向けて
実はsqlite 3.40までは既存のコードに変更を加えずに素直に静的ビルドするのが難しい
ここで良い話と悪い話がある。
良い話は上記のcommitでビルド時にフラグを追加するだけでビルドできるようになる。
悪い話は上記のcommitでbase64の機能が追加されたので、実は上のextensionをloadしなくても良くなった。
つまり sqlite3でbase64を使いたいだけの人間は そのうちリリースされる予定のsqlite 3.41を使えば良い
今回はモチベーションを失いつつも静的リンクに向けてやっていく
静的リンクビルド解説
元々どういった手法でビルドすれば良いかを明確に解説したドキュメントはないのだが、
SQLite Forum: Decimal extension in amalgamation?
SQLite Forum: Latest trunk/extensions & UDF's on tap
を見ると、とにかく shell.c
に拡張用のcを結合しつつ SQLITE_EXTRA_INIT
に init用の関数を指定しろ、と言うふうに読める。
ちなみに SQLiteは amalgamationという全てのソースコードを1ファイルにまとめる操作を Tcl という言語を使いつつやっているのだが
「とんでもないコードが出てますよ今」
という印象を受ける
そんな操作をしているので cat ext.c >> shell.c
すれば良いという雰囲気になっている。
しかし、直近は SQLITE_EXTRA_INIT
無くなったようで、上記commitを見ると, SQLITE_SHELL_EXTFUNCS
や SQLITE_SHELL_EXTSRC
や以下のmacroが追加されている。
#define SHELL_SUB_MACRO(base, variant) base ## _ ## variant #define SHELL_SUBMACRO(base, variant) SHELL_SUB_MACRO(base, variant) /* Let custom-included extensions get their ..._init() called. * The WHATEVER_INIT( db, pzErrorMsg, pApi ) macro should cause * the extension's sqlite3_*_init( db, pzErrorMsg, pApi ) * inititialization routine to be called. */ { int irc = SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, INIT)(p->db); /* Let custom-included extensions expose their functionality. * The WHATEVER_EXPOSE( db, pzErrorMsg ) macro should cause * the SQL functions, virtual tables, collating sequences or * VFS's implemented by the extension to be registered. */ if( irc==SQLITE_OK || irc==SQLITE_OK_LOAD_PERMANENTLY ){ SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, EXPOSE)(p->db, 0); } #undef SHELL_SUB_MACRO #undef SHELL_SUBMACRO } #endif
これは目を細めて凝視すると、
#define BASESF_INIT(db) sqlite3_basesixtyfour_init(db, 0, 0) #define BASESF_EXPOSE(db, pzErr)
という initを呼ぶ macroを定義しつつ SQLITE_SHELL_EXTFUNCS=BASESF
と SQLITE_SHELL_EXTSRC=拡張関数.cへのpath
のフラグを渡せばい良い、ということがおぼろげながら浮かんでくる。
あとはやるだけなので以下のnixファイルからビルドする。
sqlite.overrideAttrs (final: prev: { src = pkgs.fetchurl { url = "https://sqlite.org/snapshot/sqlite-snapshot-202302052029.tar.gz"; sha256 = "sha256-MNjujQGJ/QlKs/3gUGna2G6Fmc/gAZ4qxmXflWH5DH0="; }; buildInputs = prev.buildInputs ++ [ pkgs.libb64 ]; NIX_CFLAGS_COMPILE = prev.NIX_CFLAGS_COMPILE + " -DSQLITE_CORE=1 -lb64 -isystem ${pkgs.libb64}/include/b64 -DSQLITE_SHELL_EXTSRC=${sqlite3-base64}/sqlite3_base64.c -DSQLITE_SHELL_EXTFUNCS=BASESF"; }))
実行結果
❯ sqlite3 SQLite version 3.41.0 2023-02-05 20:29:10 Enter ".help" for usage hints. Connected to a transient in-memory database. Use ".open FILENAME" to reopen on a persistent database. sqlite> SELECT BASE64(X'010203'); AQID sqlite> SELECT HEX(BLOBFROMBASE64('AQID')); 010203 sqlite>
よかったですね
SAIYO JOHO