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

Rust で SQLite を動かしてみる

答が出ているので、後追いするのを止めて、違う角度から攻めてみる。そう、 どうやって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

5.3 SQLiteデータベースの使用

Go言語でSQLite3を使う

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>

トレースなんてのに眼が行った。探ってみたら、素晴しいサイトに遭遇。

SQLite3ドットコマンド(.trace)

SQLite3 Command Line Shell dot-commands マニュアル(完全版)

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

随分と深い所だなあ。

tools and etc