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をしたかったのか、という点は直接聞いてもらえれば。

github.com

ビルド編

実は最近はLoadable Extensionの仕様が変わっており、上記のcodeは現代的な書き方ではないので変更していく

変更し終わったコードは以下。

github.com

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までは既存のコードに変更を加えずに素直に静的ビルドするのが難しい

ここで良い話と悪い話がある。

SQLite: Check-in [07543d23]

良い話は上記の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_EXTFUNCSSQLITE_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=BASESFSQLITE_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

github.com