// Opens an in-memory SQLite database through its C API and reads one row back.
//
// nikaia run
// opened 0
// prepared 0
// stepped 100
// hello, C
//
// It needs libsqlite3 and `pkg-config` on the machine. The `extern` block names
// the library, `nikaia.toml` says how it is found (`[library.sqlite3]`), and
// the build links it: nothing is written for it anywhere else (Part III 13.4).
//
// Shows: an `extern` block that names its library, opaque handles that clean up after themselves,
// out-parameters, text owned by C, and `unsafe`.
//
use std::foreign
// Declared here and linked from libsqlite3. A handle `released by` a function
// has that function called when the handle goes out of scope, so nothing below
// closes the database or finalizes the statement.
//
// `ref Array[u8]` lends a buffer for the call. `ref mut sqlite3?` is an
// out-parameter: a slot that C fills with a handle, or leaves empty.
// `foreign::CStr` is text that SQLite owns; `to_string()` copies it.
extern(library: "sqlite3") {
opaque type sqlite3 released by sqlite3_close
opaque type sqlite3_stmt released by sqlite3_finalize
fn sqlite3_open(path: ref Array[u8], out: ref mut sqlite3?) -> i32
fn sqlite3_close(db: sqlite3) -> i32
fn sqlite3_prepare_v2(db: sqlite3, sql: ref Array[u8], bytes: i32, out: ref mut sqlite3_stmt?, tail: ref mut foreign::CStr?) -> i32
fn sqlite3_finalize(stmt: sqlite3_stmt) -> i32
fn sqlite3_step(stmt: sqlite3_stmt) -> i32
fn sqlite3_column_text(stmt: sqlite3_stmt, column: i32) -> foreign::CStr?
}
enum Refused {
NoDatabase,
NoStatement
}
impl Error for Refused {
fn message(ref self) -> String {
match self {
Refused::NoDatabase => "sqlite3_open handed back no database"
Refused::NoStatement => "sqlite3_prepare_v2 handed back no statement"
}
}
}
fn main() throws {
// Every call into C must be inside `unsafe`. C strings end in a zero
// byte, hence the `\0`.
let mut slot: sqlite3? = null
let opened = unsafe { sqlite3_open(":memory:\0", slot) }
println(f"opened {opened}")
let db = slot ?? throw Refused::NoDatabase
let mut ready: sqlite3_stmt? = null
let mut tail: foreign::CStr? = null
let prepared = unsafe { sqlite3_prepare_v2(db, "select 'hello, C'\0", -1, ready, tail) }
println(f"prepared {prepared}")
let stmt = ready ?? throw Refused::NoStatement
// 0 is SQLITE_OK, 100 is SQLITE_ROW: a row is ready to read.
let stepped = unsafe { sqlite3_step(stmt) }
println(f"stepped {stepped}")
let text = unsafe { sqlite3_column_text(stmt, 0) }
println(text?.to_string() ?? "no row")
}
The file itself · all the examples