SQLite を読んで再入門
前回、勢いでデバッガから入ったけど、このままでは整理されないので全体を把握したりインタフェースを確認したりした。
公式のIntroduction, Tutorial, Architecture, C/C++ Interfaceは最初に目を通した方が早い。 ちょっと無理やり進めた感が出てしまうし無駄が大幅に増えた。上を押さえてこそ読みやすくなる。
読んでおく
アーキテクチャ要点メモ
アーキテクチャの内容をメモする。
Interface
インタフェースはmain.c
,legacy.c
, and vdbeapi.c
など多くのファイルに散らばっている。
名前の衝突などを除き、基本的にsqlite3
プレフィックスがつく関数として公開されている。
一覧があるのでここを読む。
SQL Command Processor
バイトコード生成を行う。 全体は Tokenizer, Parser, CodeGeneratorに分かれている。
Tokenizer
Tokenizer がParser を呼び出す。
確かにsqlite3RunParser()
に先頭のToken情報を渡してParseを開始している。
sqlite3RunParser()
内でLemonで生成されたsqlite3Parser()
を呼び出している。
tokenizer.cに書かれている。
Parser
LALR(1)でLemonで生成される。 シンタックスエラーでもメモリリークが起きずスレッドセーフらしい。 parse.yに書かれている。
CodeGenerator
VM用のバイトコード(Opcode)を生成する。
生成されたバイトコードはsqlite3_stmtとして扱われる。
expr.cが全体をハンドリングしている。
attach.c
,auth.c
,build.c
,delete.c
,
expr.c
,insert.c
,pragma.c
,select.c
,trigger.c
,update.c
,vacuum.c
and where.c
などが具体的な各ステートメントに対応する。
sqlite3_stmtのライフサイクル
オブジェクトの説明によるとstmt
のライフサイクルは下の通り。
またsqlite3_stmt
はvoid*的に扱えるらしい。
この仕様はどこで定義されているかよく分からなかった。
- Create the prepared statement object using sqlite3_prepare_v2().
- Bind values to parameters using the sqlite3_bind*() interfaces.
- Run the SQL by calling sqlite3_step() one or more times.
- Reset the prepared statement using sqlite3_reset() then go back to step 2. Do this zero or more times.
- Destroy the object using sqlite3_finalize().
VirtualMachine
vdbe.c
に全てのコードが含まれている。vdbe.h
がインタフェース
vdbeInt.h
が内部定義を定めている。
vdbemem.c
にstrings, integer, floating point, numbers, and BLOBs といった単一値が定義されている。
Cのcallbackを呼び出す機構が存在しビルトイン関数も同様の仕組みで実装されている。
具体的にはfunc.c
,date.c
に定義されている。
グローバル関数の登録について
sqlite3RegisterGlobalFunctions()
関数の中でFUNCTION
マクロなどを使いFuncDef
配列のaBuiltinFunc
を定義している。
そしてsqlite3FuncDefInsert()
を使いグローバル関数を登録している。
グローバル関数はハッシュテーブルで管理されており、そのハッシュテーブル自体はGLOBALマクロ経由でsqlite3_wsd_find()
によりpGlobal
から取得される。
pGlobal
にはsqilte3GlobalFunctions
をキーで登録されている。
またpGlobal
は静的なプロセス固有のストレージになっている。
B-Tree
SQLite はB-Treeを用いてDiskに維持される。実装はbtree.c
にある。
全てのB-Treeが単一ファイルに格納される。詳細な形式はコメント(btree.c
)に記載されている。
PageCache
B-Treeは実際には固定データサイズ単位(default:1KiB, 512〜65536B)で操作を行う。
この単位で変更・コミット・ロールバックを依頼しPageCacheがDiskに反映する。
ファイルシステムの下のブロックデバイスみたいにSQLite中のB-Treeに対するPageCacheの様な位置付け。
pager.c
,pager.h
に書かれている。
OS Interface
to provide portability between POSIX and Win32 interfaces.
コアの実動作
実行部分は以下の関数が呼ばれる。
高水準関数としてsqlite3_exec()
、
低水準関数としてsqlite3_prepare()
,sqlite3_step()
が呼ばれる。
- sqlite3_prepare()
-
バイトコード生成(CodeGenerator)を担う
- sqlite3_step()
-
バイトコードを実行(VirtualMachine)を担う
またsqlite3_prepare()
に生成されたsqlite_stmt()
はsqlite3_finalize()
などで後処理される。
CodeGenarator(sqlite3_prepare)
大雑把な呼び出しフローは下になる。sqlite3Prepare()
がUTF-8
SQL文をバイトコード(プリペアドステートメント)に変換する。
|
|
VirtualMachine(sqlite3_step)
大雑把な呼び出しフローは下になる。
sqlite3VdbeExec()
がバイトコードを実行する仮想マシンの実行ループを持つ。
その中でdoWalCallbacks()
がコールバック関数を呼び出す。
コールバック関数はdb->xWalCallbackに登録されている
|
|
仮想マシンはレジスタ計算機になっている。
Opcodeをみると一覧が押さえられる。
実際のSQLがどのようなOpcodeに翻訳されるかはEXPLAIN
クエリにより確認できる。
|
|
コールバックによる関数呼び出しも可能で、通常関数はOP_Function0
集約関数はOP_AggStep0
,
OP_AggFinal
命令が呼ばれて実現する。
具体的にはP4
レジスタに*FuncDef
が渡されて呼び出し先が特定される。
呼び出しを実際に確認できる。
|
|
.コマンドハンドリング部分
sqlite3 shellではSQL実行以外にもメタコマンドが存在する。
do_meta_command(shell.c l.2638)
関数がコマンドのディスパッチする。
1byte目が.
なのを前提に動く。