Guides
Local database
Structured local data with SQLite, written once in the core. The platform owns the file and the thread; the core sends bound statements and gets typed rows back.
Turn it on#
Db is an opt-in port over the SQLite each platform already has: the system library on iOS, android.database.sqlite on Android, a JDBC driver on the JVM, node:sqlite in Node and wa-sqlite in a worker on the web. The core stays small and never blocks: the platform owns the database and its thread. On the web the files live in the origin private file system, which one tab holds at a time: a second tab of the same app gets Unavailable.
[dependencies]
undra = { version = "0.1", features = ["db"] }Open and migrate#
Migrations are numbered SQL. The platform runs every one the database has not seen yet, in one transaction: if one fails, none of them happened.
const MIGRATIONS: &[Migration] = &[
Migration::new(1, "CREATE TABLE notes (id INTEGER PRIMARY KEY, title TEXT NOT NULL, done INTEGER NOT NULL DEFAULT 0)"),
Migration::new(2, "CREATE INDEX notes_done ON notes (done)"),
];
let db = Database::open(&ctx, "notes", MIGRATIONS).await?;Values are parameters#
SQL is a &'static str, so a value can only reach a statement as a bound parameter. Cells come back typed.
db.execute("INSERT INTO notes (title) VALUES (?)", params![title]).await?;
let rows = db.query("SELECT id, title, done FROM notes WHERE done = ?", params![false]).await?;
for row in rows.iter() {
let id: i64 = row.get("id")?;
let title: String = row.get("title")?;
}Transactions#
A transaction commits when its body returns Ok and rolls back on Err, or when its task is dropped half-way.
db.transaction(|tx| async move {
for title in titles {
tx.execute("INSERT INTO notes (title) VALUES (?)", params![title]).await?;
}
Ok(())
}).await?;Errors you can match#
| Error | When |
|---|---|
Constraint { kind, .. } | a UNIQUE, NOT NULL, foreign-key or CHECK constraint failed |
Busy | the database stayed locked past five seconds |
Migration { version, .. } | a migration failed and everything rolled back, or the file is newer than the app |
Sql | a syntax error, a missing table, a cell read as the wrong type |
Corrupt, Full, Unavailable | the file, the disk, or no database to talk to |
In tests#
FakeDb answers by SQL fragment, injects failures and records every statement, migration and transaction, so the core's database logic is tested without a device.
fakes.db.fail_on("INSERT", DbError::Full);
assert_eq!(notes.add("milk".into()).await, Err(DbError::Full));Design: ADR-048.