// 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") }