std:db/sqlite — SQLite ドライバー
標準ライブラリモジュール (../index.md)。本書中の裸の §N は本書の節を指す。ドライバー横断の契約 (プレースホルダー・行と値・エラー kind・トランザクション) は ../db.md を正とし、本書は SQLite 固有の面のみを定める。言語の意味論は ../../language-spec.md を参照。
import sqlite := "std:db/sqlite" はコンストラクター open と不透明ハンドル型 Db / Tx を持つ namespace object を sqlite に束縛する。std:db/sqlite の import 自体が「このコードはデータベース (ファイル) に触れる」ことの能力宣言になる (../index.md)。
std:fs と同じ外部リソース I/O であり、操作は Future(Result(...)) を返す (prelude.md §9.4)。
import sqlite := "std:db/sqlite" export {} # ハンドルは多重度型なので公開面に出せない ([static-analysis.md](../../static-analysis.md#2-静的型検査)) type UserRow := { name: Option(String), age: Option(Int) |} db := sqlite.open("app.db")!.unwrap! db.exec("create table if not exists users (id integer primary key, name text not null, age integer)", [])!.unwrap! db.exec("insert into users (name, age) values (?, ?)", ["Alice", 30])!.unwrap! db.exec("insert into users (name, age) values (?, ?)", ["Bob", None])!.unwrap! # None → NULL rows: List(UserRow) := db.query("select name, age from users where age > ?", [20])!.unwrap! rows.each { r: UserRow | println "${r.name.unwrap!}: ${r.age.or 0}" } (db.transaction { tx | tx.exec("update users set age = age + 1 where name = ?", ["Alice"])!? Ok(()) })!.unwrap! db.close()!
1. コンストラクター
| 名前 | 形 | 効果 | 意味 |
|---|---|---|---|
open |
sqlite.open(path) |
Fs |
DB ファイルを開く (不在なら作成) → Future(Result(Db)) |
pathは String。非 String は呼び出し位置でError。- 相対パスは プロセスの cwd 基準 (fs.md §12 と同じ)。
":memory:"でオンメモリ DB (close で消える)。 - URI ファイル名 (
file:...?mode=ro等)・read-only オープンは将来枠 (§12)。 - 権限不足・親ディレクトリ不在・ファイルが DB でない等の失敗は
Err(§11)。
2. Db のメソッド
| メソッド | 形 | 効果 | 返り |
|---|---|---|---|
query |
db.query(sql, params) |
Fs |
Future(Result(List(Row))) (§3) |
exec |
db.exec(sql, params) |
Fs |
Future(Result({changes, last_insert_rowid})) (§4) |
transaction |
db.transaction(block) |
Fs(ブロックを透過) |
Future(Result(T)) — T は block の Ok payload (§5) |
close |
db.close() |
Fs |
Future(Result(()))。呼んだ時点でハンドルは封印され、以降どのメソッドも Error (use after close) |
ハンドル規律は fs.md §11 の File と同一 (可変 scratch ハンドル: 束縛 immutable・内部状態のみ前進・値型ではない・== / compare なし・std:map / std:set のキー不可)。Db は std:db/sqlite の型メンバーとして export される (sqlite.Db)。
transaction が block へ渡すハンドルの型 Tx も型メンバーとして export される (sqlite.Tx)。transaction の宣言型がブロックパラメーターの型として Borrowed(Tx) を持ち (../db.md §5)、その綴りが注釈位置にも現れるためである。Db と同じ不透明ハンドル型で、内部表現は露出しない。
Db と transaction が渡すトランザクションハンドル Tx は非 Copyable である (../../language-spec.md の複製の節)。ホストの接続を持つ可変ハンドルで、作り直しても同じものにならないためである。copy は panic し、spawn / std:parallel の捕獲検査も弾く。
Db はExactlyOnce の多重度型である (../../language-spec.md の多重度型の節)。close が Consuming メソッドで、query / exec / transaction は借用である。
3. query
- 引数:
(sql, params)。sqlは String。paramsはバインド値の List (省略できない — 束縛が無いなら[]と書く。../index.md)。sql非 String・params非 List・受理集合 (db.md §5) 外の要素は呼び出し位置でError。 - プレースホルダーは
?(SQLite ネイティブ。db.md §2)。?の個数とparamsの長さの不一致はバグ層 → panic (Error) が!で表面化する (SQLite は未バインドの?を暗黙に NULL にするが、本モジュールはこれを許さない)。 - resolve 値は
Result(List(Row)): - 成功時:
Ok(rows)— 行 record の List (db.md §3)。0 行はOk([])。全行を実体化する (巨大な結果には LIMIT を使う。ストリーミングは将来枠 §12)。 - 失敗時:
Err(e)—{kind, message}(§11)。 - 列値は
Some(v)∣None(db.md §4)。vの型は値の storage class (§6) で決まる — SQLite は値レベル動的型付けであり、カラム宣言型 (affinity) は基準にしない。 - SELECT 以外の文も実行できる (行を返さない文は
Ok([]))。
4. exec
- 引数:
queryと同じ(sql, params)(paramsは省略できない・検査規則も同じ)。 - resolve 値は
Result(record): - 成功時:
Ok({changes, last_insert_rowid})— closed・immutable なレコード: changes— 行を変えうる文 (INSERT / UPDATE / DELETE) が変えた行数 (Int)。db.md §6 の契約スロット。1 行も該当しなければ0になるlast_insert_rowid— 直近に INSERT された rowid (Int)。INSERT 以外では意味を持たない (SQLite のsqlite3_last_insert_rowidに対応する sqlite 拡張スロット)- 失敗時:
Err(e)—{kind, message}(§11)。 - 行を返す文 (SELECT 等) も実行できる (行は捨てられる)。
- 行を変えられない文 (SELECT・DDL・PRAGMA 等) の後の
changesは、その接続で最後に行を変えうる文が出した値のままである (この接続の計数は行を変えうる文だけが更新する)。last_insert_rowidが「INSERT 以外では意味を持たない」のと同じ規律で、0へ戻りはしない。読むのは行を変えうる文の直後に限ること。
5. transaction
契約は db.md §9 (Ok → commit / Err → rollback して透過 / panic → rollback して伝播 / block は Result を返す義務 / ネスト非対応)。SQLite 固有の規定:
- BEGIN の種別は実装既定 (DEFERRED 相当)。種別の指定口は将来枠 (§12)。
- ネスト:
txはtransactionメソッドを持たない (呼べばError)。block 内で同じDbのdb.transactionを再度呼ぶと、接続の直列化 (§7) により待ちの期限超過でErr(busy)になる (内側の BEGIN には到達しない)。 - トランザクション中の
db直接操作:transactionの実行中、同じDbへの直接のquery/exec(block 内・他フローを問わず) は接続の解放を待ち、実装既定の待ち時間 (§7) を超えるとErr(busy)で resolve する。block 内の操作はtx経由が正道。 txは block の評価が終わった時点で封印され、block の外へ持ち出して使うとError(use after close と同じ規律)。
6. 型対応
読み (SQLite storage class → Hikari)。列値は Some(v) に包まれる (NULL のみ None。db.md §4):
| storage class | Hikari |
|---|---|
| INTEGER | Int |
| REAL | Float |
| TEXT | String |
| BLOB | Bytes |
| NULL | None (包みなし) |
書き (Hikari → SQLite)。生値と Some(v) の v に適用 (db.md §5):
| Hikari | SQLite |
|---|---|
| Int | INTEGER。64bit に収まらない Int は Error (SQLite の INTEGER は 64bit。Hikari の Int は任意精度、language-spec.md §7.1) |
| Float | REAL。NaN は Error (SQLite は NaN を NULL に変換するため、暗黙の None 化に当たる。±Inf は格納可) |
| String | TEXT |
| Bytes | BLOB |
None |
NULL |
| Bool ほか受理集合外 | Error (db.md §5。Bool は to_int 等で明示変換) |
TEXT 列に不正な UTF-8 バイト列が含まれる場合の挙動は実装定義 (fs.md §1 の read と同じ注意)。エンコーディングの不確かなデータは BLOB で扱う。
7. 並行モデルと busy
Db1 個は単一接続である。同じDbへの操作は (協調スケジューラー上の複数フローから同時に発行しても) 内部で直列化され、データ競合は起きない。- 他プロセスとのファイルロック競合、および接続の直列化待ち (§5) には実装既定の待ち時間 (5 秒相当の busy timeout) を適用し、超過は
Err(busy)。exec("pragma busy_timeout=...")で上書きできる。 - 並列度を上げる用途 (複数接続・プール) は将来枠 (§12)。
8. PRAGMA と設定
専用の設定 API は持たない。PRAGMA は exec でそのまま通す:
db.exec("pragma journal_mode=wal", [])!.unwrap!
journal mode 等は SQLite の既定のまま (本モジュールが変更するのは busy timeout の既定 §7 のみ)。
9. embed バイナリ (hikari build) での挙動
SQLite エンジン (C 実装を同梱ビルドする) の同梱は条件付きである。hikari build (build.md) は import グラフを静的解決するため、std:db/sqlite を import するプログラムに限りエンジンを link する。import しないプログラムの生成バイナリは 1 byte も増えない。開発用 hikari CLI 本体には常時同梱され、hikari run / REPL でいつでも使える。
10. 対応プラットフォーム
hikari build の検証済みターゲット (build.md「対応ターゲット」) をすべて支える。wasm では未提供 — import は解決するが open が常に Err(io_error) で resolve する。
11. エラー
失敗は Err({kind, message}) (db.md §7 の正規 kind)。SQLite との対応:
| kind | SQLite 側 |
|---|---|
sql_error |
SQL 構文エラー・実行時 SQL エラー |
constraint |
SQLITE_CONSTRAINT 系 (UNIQUE / NOT NULL / FOREIGN KEY / CHECK) |
busy |
SQLITE_BUSY / SQLITE_LOCKED・接続直列化待ちの期限超過 (§7) |
io_error |
open 失敗・ディスク I/O・DB ファイル破損ほか |
引数の型・範囲違反、プレースホルダー個数不一致、識別子でない/重複カラム名、封印後使用は Error (db.md §7)。
12. 将来枠
- open オプション (read-only・URI ファイル名)・BEGIN 種別 (IMMEDIATE) の指定
- 明示 prepared statement・savepoint (ネストトランザクション)・行のストリーミング読み
- 複数接続・プール
- backup / serialize・ユーザー定義 SQL 関数