sqlite3 * (c + rust + golang)
Table of Contents
joke
表題にした怪しげな数式であるけど、中学生なら展開できるかな? もしでき なかったらソフトの力を借りよう。ああ、今ならChatGPTに縋る人が多いか。 どうせ、オイラーは旧式よ。昔からある数式処理システムね。バックエンドは、 泣く子も黙るLispです。
[sakae@arch ~]$ maxima Maxima 5.46.0 https://maxima.sourceforge.io using Lisp SBCL 2.3.1 : (%i1) expand( sqlite3 * (c + rust + golang) ); (%o1) rust sqlite3 + golang sqlite3 + c sqlite3
数学界では、加法より乗法が多数出現するので、乗法記号は省略するのがお約 束になってる。
同じことはpythonだって出来るんです。万能ですから。
>>> import sympy >>> s, c, r, g = sympy.symbols('sqlite c rust golang') >>> sympy.expand( s * (c + r + g) ) c*sqlite + golang*sqlite + rust*sqlite
前回は、sqlite + rubyとか sqlite + python をやった。インタープリタだか ら、簡単にsqliteを切り離せる。対して今回は括弧の中は、いずれもコンパイ ル型の言語。sqliteを組み合わせた時点で、もう分離できない、運命共同体に なる。まあ、そういう事だ。
explain sqlite3
前回は大騒ぎしてPostgreSQLとかを入れた。で、そのマニュアルを閲覧してた ら、explainなんてのが目に止った。ひょっとしてsqlite3にも有るかと調べて みたら、あった。
下記は組み込みコマンドのdate()の実行。selectって必ずfromが必要かと思っ ていたけど、そうでもないのね。こういう知識がふえるのは嬉しい事だ。
sqlite> EXPLAIN select date(); addr opcode p1 p2 p3 p4 p5 comment ---- ------------- ---- ---- ---- ------------- -- ------------- 0 Init 0 6 0 0 Start at 6 1 Once 0 3 0 0 2 Function 0 0 2 date(-1) 0 r[2]=func() 3 Copy 2 1 0 0 r[1]=r[2] 4 ResultRow 1 1 0 0 output=r[1] 5 Halt 0 0 0 0 6 Goto 0 1 0 0
The SQLite Bytecode Engine の説明によると、命令のオペランドが5個もある、 凄い仮想マシン。コメントが親切だけど、これはsqlite3のコンパイル時に disableにできるらしい。ArchLinuxのメンテナさんは親切であった。
sqlite> explain SELECT datetime(1677367156, 'unixepoch', 'localtime'); addr opcode p1 p2 p3 p4 p5 comment ---- ------------- ---- ---- ---- ------------- -- ------------- 0 Init 0 9 0 0 Start at 9 1 Once 0 6 0 0 2 Integer 1677367156 3 0 0 r[3]=1677367156 3 String8 0 4 0 unixepoch 0 r[4]='unixepoch' 4 String8 0 5 0 localtime 0 r[5]='localtime' 5 Function 7 3 2 datetime(-1) 0 r[2]=func(r[3..5]) 6 Copy 2 1 0 0 r[1]=r[2] 7 ResultRow 1 1 0 0 output=r[1] 8 Halt 0 0 0 0 9 Goto 0 1 0 0
Date And Time Functions に、日時の扱いの詳細がでてた。
trace
こういう楽しい資料が提示されたら、見逃す事はできない。ソースを取り寄せ て、–enable-debug オプションをつけてコンパイル。できあがったsqlite3を トレースしてみる。
sqlite3はlibsqlite3.so相当をとりこんだアプリ。重要な部分はライブラリー に格納されてる。で、その中のエンジンにBPを配置。
(gdb) bt #0 sqlite3VdbeExec (p=0x193fda8) at sqlite3.c:90946 #1 0x0055d8bf in sqlite3Step (p=0x193fda8) at sqlite3.c:88487 #2 0x0055dc4a in sqlite3_step (pStmt=0x193fda8) at sqlite3.c:88548 #3 0x004eb2d8 in exec_prepared_stmt (pArg=0xbf9e8ba4, pStmt=0x193fda8) at shell.c:19007 #4 0x004ebf5c in shell_exec (pArg=0xbf9e8ba4, zSql=0x19273b0 "select date();", pzErrMsg=0xbf9e8ac0) at shell.c:19324 #5 0x004fd327 in runOneSqlLine (p=0xbf9e8ba4, zSql=0x19273b0 "select date();", in=0x0, startline=3) at shell.c:26443 #6 0x004fda5e in process_input (p=0xbf9e8ba4) at shell.c:26609 #7 0x004ff9de in main (argc=1, argv=0xbf9e9ec4) at shell.c:27495
OP_Function
にカーソルを置いてから C-x C-a C-t して、テンポラリィーなBPを設定して、少
し進める。
(gdb) p *pCtx $3 = { pOut = 0x0, pFunc = 0x6eef30 <aDateTimeFuncs.1842+80>, pMem = 0x0, pVdbe = 0x0, iOp = 3, isError = 0, enc = 0 '\000', skipFlag = 0 '\000', argc = 0 '\000', argv = {0x0} }
これがエンジンの制御パネルト言うかダッシュボードになるのかな。ちゃんと 実行すべき手続がセットされてた。
echo 0| sudo tee /proc/sys/kernel/yama/ptrace_scope
ArchLinuxでアタッチしようとすると拒否されたので、上記のお呪いを実行。 恒久的にやるなら下記。但し、emacs + gdb だと、debug用のソースを取り込 みに行ってしまって、実質使用不可能。余計な事はしなくていいのに。プンプ ン。
[sakae@arch ~]$ cat /etc/sysctl.d/10-ptrace.conf kernel.yama.ptrace_scope = 0
with python
ふとpythonと、どう連携してるかなと思った。で、こんなコードを用意してか ら、straceしたよ。
import sqlite3 cx = sqlite3.connect("doctor.db") cu = cx.cursor() for row in cu.execute("select * from procs"): print(row) cx.close()
libsqlite3を呼ぶ為のsoファイルを用意して、高速に交信するようになってる のか。
sakae@deb:/tmp$ grep sqlite LOG | grep openat openat(AT_FDCWD, "/usr/local/lib/python3.11/sqlite3/__pycache__/__init__.cpython-311.pyc", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3 openat(AT_FDCWD, "/usr/local/lib/python3.11/sqlite3", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_CLOEXEC|O_DIRECTORY) = 3 openat(AT_FDCWD, "/usr/local/lib/python3.11/sqlite3/__pycache__/dbapi2.cpython-311.pyc", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3 openat(AT_FDCWD, "/usr/local/lib/python3.11/lib-dynload/_sqlite3.cpython-311-i386-linux-gnu.so", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3 openat(AT_FDCWD, "/lib/i386-linux-gnu/libsqlite3.so.0", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
c * sqlie3
on OpenBSD
ちらちらとOpenBSDの案内を見ていたら、素敵なオプションを発見した。
vbox$ sqlite3 -stats doctor.db sqlite> select * from Procs; : Memory Used: 153080 (max 157016) bytes Number of Outstanding Allocations: 306 (max 377) Number of Pcache Overflow Bytes: 4104 (max 4104) bytes Largest Allocation: 85120 bytes Largest Pcache Allocation: 4104 bytes Lookaside Slots Used: 60 (max 93) : Virtual Machine Steps: 54 Reprepare operations: 0 Number of times run: 1 Memory used by prepared stmt: 6288
それから、当たり前のように、man sqlite3_exec
なんて出きたりする。ちゃ
んと尊厳を尊重してるって偉いよ。普通は、ライブラリィーなんて下僕扱いだ
からね。
rust * sqlite3
答が出ているので、後追いするのを止めて、違う角度から攻めてみる。そう、 どうやってsqliteと接続してるかね。
まずは、階層構造を把握してみる。
[sakae@fb ~/testdb]$ cargo tree testdb v0.1.0 (/usr/home/sakae/testdb) └── sqlite v0.30.4 ├── libc v0.2.139 └── sqlite3-sys v0.14.0 ├── libc v0.2.139 └── sqlite3-src v0.4.0 [build-dependencies] ├── cc v1.0.79 └── pkg-config v0.3.26
sqliteというモジュールは自己完結してるんだな。これを使うだけで、ソース を取ってきてコンパイルして、rust側から利用できるようにしてくれてる(と 想像)思う。次は、それらを一式手元に用意。
[sakae@fb ~/testdb]$ cargo vendor [sakae@fb ~/testdb]$ cd vendor/ [sakae@fb ~/testdb/vendor]$ ls cc/ pkg-config/ sqlite3-src/ libc/ sqlite/ sqlite3-sys/
目当ては、とりあえずsqlite/src/の所。
[sakae@fb ~/testdb/vendor/sqlite/src]$ ls connection.rs error.rs statement.rs cursor.rs lib.rs value.rs
ざっと見すると、rustの厳格さが、あちこちに噴出。そう、得体の知れないや つを呼ぶのは自己責任だからねって、宣言させられるんだ。たとえば、こんな の。
/// Reset the internal state. #[inline] pub fn reset(&mut self) -> Result<()> { unsafe { ok!(self.raw.1, ffi::sqlite3_reset(self.raw.0)) }; Ok(()) }
SQLite C Interface で、該当する関数を探せばよい。一体どんな関数が利用 されているか、探しだしてみる。
cat *.rs | grep ffi:: | sed -e 's/.*ffi:://' | sed -e 's/(.*//' | grep sqlite3_ | sort | uniq
得意のパイプ戦法を発動。ゆきあたりばったりでやったので、無駄が有るのは 承知の助だけど、許せ。ザクッと作るのがオイラー流だ。
sqlite3_bind_blob sqlite3_bind_double sqlite3_bind_int64 : sqlite3_step sqlite3_stmt { sqlite3_total_changes
ちょいと塵が混入してるけど、30種類の関数が使われていた。
go * sqlite
github.com/mattn/go-sqlite3 こんなのを入れて調べてみる。
vbox$ grep '<sqlite3.h>' * backup.go:#include <sqlite3.h> callback.go:#include <sqlite3.h> error.go:#include <sqlite3.h> sqlite3.go:#include <sqlite3.h> sqlite3_context.go:#include <sqlite3.h> sqlite3_load_extension.go:#include <sqlite3.h> sqlite3_opt_column_metadata.go:#include <sqlite3.h> sqlite3_opt_preupdate_hook.go:#include <sqlite3.h> sqlite3_opt_userauth.go:#include <sqlite3.h> sqlite3_opt_vtable.go:#include <sqlite3.h> sqlite3_trace.go:#include <sqlite3.h> sqlite3_type.go:#include <sqlite3.h>
トレースなんてのに眼が行った。探ってみたら、素晴しいサイトに遭遇。
python again
上のrustの所でlibsqliteを呼び出す所がわかったんで、pythonではどうかと プローブを当ててみた。題材はこれまた上の方にあった簡単なコードね。
#0 0xb7144cc0 in sqlite3_open_v2 () from /lib/i386-linux-gnu/libsqlite3.so.0 #1 0xb71c221b in pysqlite_connection_init_impl (factory=<optimized out>, uri=0, cache_size=128, check_same_thread=1, isolation_level=0xb71caef0 "", detect_types=0, timeout=5, database=<optimized out>, self=0xb72ba280) at /home/sakae/src/Python-3.11.2/Modules/_sqlite/connection.c:185 #2 pysqlite_connection_init (self=<optimized out>, args=0xb72a8e80, kwargs=0x0) at /home/sakae/src/Python-3.11.2/Modules/_sqlite/clinic/connection.c.h:100 #3 0xb7bd5c4a in type_call (type=<optimized out>, args=0xb72a8e80, kwds=0x0) at Objects/typeobject.c:1112 #4 0xb7b67e93 in _PyObject_MakeTpCall (tstate=0xb7f93a4c <_PyRuntime+84940>, callable=0x454468, args=0xb7fc1040, nargs=1, keywords=0x0) at Objects/call.c:214 : #25 0xb7cc9762 in pymain_main (args=0xbffff500) at Modules/main.c:710 #26 Py_BytesMain (argc=2, argv=0xbffff614) at Modules/main.c:734 #27 0x00401087 in main (argc=2, argv=0xbffff614) at ./Programs/python.c:15
随分と深い所だなあ。