本文へ移動
Hikari 仕様

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/sqliteimport 自体が「このコードはデータベース (ファイル) に触れる」ことの能力宣言になる (../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 §11File と同一 (可変 scratch ハンドル: 束縛 immutable・内部状態のみ前進・値型ではない・== / compare なし・std:map / std:set のキー不可)。Dbstd:db/sqlite の型メンバーとして export される (sqlite.Db)。

transaction が block へ渡すハンドルの型 Tx型メンバーとして export される (sqlite.Tx)。transaction の宣言型がブロックパラメーターの型として Borrowed(Tx) を持ち (../db.md §5)、その綴りが注釈位置にも現れるためである。Db と同じ不透明ハンドル型で、内部表現は露出しない。

Dbtransaction が渡すトランザクションハンドル Tx非 Copyable である (../../language-spec.md の複製の節)。ホストの接続を持つ可変ハンドルで、作り直しても同じものにならないためである。copy は panic し、spawn / std:parallel の捕獲検査も弾く。

DbExactlyOnce の多重度型である (../../language-spec.md の多重度型の節)。closeConsuming メソッドで、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)。
  • ネスト: txtransaction メソッドを持たない (呼べば Error)。block 内で同じ Dbdb.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 のみ Nonedb.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 §1read と同じ注意)。エンコーディングの不確かなデータは BLOB で扱う。

7. 並行モデルと busy

  • Db 1 個は単一接続である。同じ 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 関数