sqlite/src/main.nika

// 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

This site is open source. Improve this page.