The Architecture Of SQLite

SQLiteのソースを見る前に、読んどいたほうが良さそうなドキュメントを読んでおく。

はじめに

このドキュメントは、SQLiteアーキテクチャを記述したものである。SQLiteの内部を理解し、修正したい人のためのドキュメントである。右にある図には、メインコンポーネントのブロック線図が書かれている。続いて、これらのコンポーネントの概要について述べる。このドキュメントは、V3について述べたものである。V2.8より前に比べると、似ているが細部違う。

インターフェース

ほとんどのインターフェースは、main.c, legacy.c, vdbeapi.cソースファイルで発見できる。ただし、いくつかの関数は、ファイルのスコープでしか見ることのできないデータ構造にアクセスするため、別ファイルに散らばっている。sqlite3_get_table()は、table.cに、sqlite3_mprintf()は、printf.cに、sqlite3_complete()は、tokenize.cに記述されている。Tclインターフェースは、tclsqlite.cに実装されている。名前の衝突を避けるために、sqlite3をプレフィクスとして外向けシンボルは持っている。

句解析器

SQL分を含む文字列が実行されるとき、インターフェースはまずその文字列を句解析器に渡す。句解析器は、入力文字列を句に分解して、構文解析器に渡す。句解析器は、tokenizer.cにハードコードされている(lexを使わないことをいいたいのかな)。注意するべきは、句解析器が構文解析器を呼び出すことである。YACC/BISONを使ってこのようなことができるが、こちらでは構文解析器が句解析器を呼び出している。SQLiteの開発者は、両方のパターンを実装してみたところ、現状の実装が良いことが分かった。YACCは、backwardsとして機能を持っている(多分、パーサから読み出す場合、字句解析に対して一文字先読みをしなければならない。パーサからするとスタックをひとつ持ってないといけないので、実装が複雑ということかなと思う)。

構文解析器(長いのでパーサ)

パーサは、文脈にしたがってトークンに意味を与える。SQLiteのパーサは、Lemon LALR(1)パーサ生成器によって作成されている。Lemonは、YACC/BISONと同じ働きをするものである。でも、YACC/BISONは、入力文法が違っていてミスをしやすい。Lemonは、再入可能でスレッドセーフなパーサを作成できる。lemonは、途中状態での終了時にデストラクタの概念を定義してあって、文法エラーで終了するときにメモリリークしなくて済む。Lemonに入力するファイルは、parse.yにある。lemonは、誰でも持っているものでもないので、lemonが出力するCのコードは、SQLiteの配布物に含まれている。lemonに関する記述は、docディレクトリにある。

コード生成器

パーサがトークンを組み合わせてSQL文として構成すると、パーサは仮想マシン上で動作するコードを生成するコード生成器を呼び出す。コード生成器は、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 から作成される。ファイル名から大体想像できるので、以下略。

仮想マシン

コード生成器によって作成さいれたプログラムは、仮想マシン上で動作する。仮想マシンは、データベースを扱うことに特化したエンジンである。マシンは、スタックを持っており中間ストレージとして利用される。各命令は、オペコードと3つのオペランドを持つ。仮想マシン自体は、vdbe.cに全て含まれている。ヘッダファイルは、vdbe.hであり、仮想マシンのインターフェースが定義されている。vdbelnt.hは、仮想マシンのプライベートな構造が定義されている。vdbeaux.cには、仮想マシンで利用されるユーティリティ関数と他のライブラリが仮想マシン用のプログラムを作成するためのインターフェースが定義されている。vdbeapi.cには、sqlite3_bind_...ファミリの関数が含まれていて、これは仮想マシンの外部向けインターフェースである。システム上の型は、vdbemem.cに実装されていて、Memと呼ばれる内部オブジェクトに格納されている。SQLiteでは、SQL関数をCの関数へのコールバックを利用して実装している。ビルトイン関数もそのように実装されている。ほとんどのビルトイン関数(coalesce(), count(), substr())は、func.cにある。Dateやtimeの変換関数は、date.cにある。

B-Tree

B-treeの実装を用いて、ディスク上のデータを管理しており、btree.cにある。各テーブルとインデックスに対して、B-treeは作成され、ひとつのファイル上に格納される。btree.cの最初に詳細な説明がある。btree.hに、インターフェースがある。

Page Cache

B-treeモジュールは、ディスク上のデータを固定長のチャンクをとして取得する。デフォルトのチャンクサイズは、1024だが、512-65536の間で変更可能。ベージキャッシュは、チャンクに対して読み書きとキャッシュを提供する。また、ロールバックやアトミックコミットの抽象化、データベースへのロックを提供する。B-treeドライバは、あるページ群に対して、修正、コミット、ロールバックなどをページキャッシュに通知する。pager.c、pager.hに実装とインターフェースがある。

OS Interface

POSIXとWin32のポータビリティを提供するため、オペレーティングシステムの呼び出しを行うレイヤーで抽象化を行っている。os.hでインターフェースを定義している。サポートするOSように、os_unix.c、os_win.cなどを提供する。

Utilities

メモリアロケータと文字列比較の関数は、util.cにある。パーサが利用するシンボルテーブルは、hash.cにある。UTF変換は、utf.cにある。独自のprintf()は、printf.cにあり、ランダム関数は、random.cにある。

Test Code

リグレッションテストスクリプトは、トータルコード量の半分にのぼる。たくさんのassert()がある。test1.c-test5.cは、テストのためだけに存在する。os_test.cは、バックエンドのインターフェース用スタブであり、クラッシュリカバリをpagerで模擬するために電源オフをシュミレーションする。