Guide
Ratify the contract
Implementing Driver is how a backend joins the moondb family. You write five operations; the other two are given to you.
§1
Install
moon add moonbitstack/moondb
moondb is a library package. It defines the interface; it opens no connections itself. A driver package depends on it and implements Driver.
§2
Implement the trait
A driver is a struct that owns the connection handle, plus an impl Driver block. You must supply execute, query, begin, commit, rollback, and close.
pub struct MyConn { dsn : String mut closed : Bool } pub fn open(dsn : String) -> MyConn { { dsn, closed: false } } impl @moondb.Driver for MyConn with fn execute( self, sql, params, ) -> @moondb.ExecResult raise @moondb.DbError { ignore(params) if self.closed { raise @moondb.Closed } raise @moondb.QueryError("execute is not wired to a backend yet: " + sql) } impl @moondb.Driver for MyConn with fn query( self, sql, params, ) -> Array[@moondb.Row] raise @moondb.DbError { ignore(params) if self.closed { raise @moondb.Closed } raise @moondb.QueryError("query is not wired to a backend yet: " + sql) } impl @moondb.Driver for MyConn with fn begin(self) -> Unit raise @moondb.DbError { raise @moondb.QueryError("transactions are not wired yet: " + self.dsn) } impl @moondb.Driver for MyConn with fn commit(self) -> Unit raise @moondb.DbError { raise @moondb.QueryError("transactions are not wired yet: " + self.dsn) } impl @moondb.Driver for MyConn with fn rollback(self) -> Unit raise @moondb.DbError { raise @moondb.QueryError("transactions are not wired yet: " + self.dsn) } impl @moondb.Driver for MyConn with fn close(self) -> Unit { self.closed = true } extend MyConn with @moondb.Driver::{ping, close} pub fn healthy(conn : MyConn) -> Bool { conn.ping() } pub fn shutdown(conn : MyConn) -> Unit { conn.close() }
§3
Values & parameters
Parameters arrive as Array[Value] — one per positional placeholder, in order. Never interpolate them into the SQL string; bind them out-of-band. The placeholder token is the driver's choice (? for SQLite/MySQL, $1 for PostgreSQL).
pub(all) enum Value {
Null | Bool(Bool) | Int(Int32) | Int64(Int64)
| Double(Double) | Text(String) | Blob(Bytes)
}
Use Value::kind and Value::is_null when classifying an inbound value.
§4
Reading rows
A Row is read positionally or by name, with one typed accessor per Value case. Each raises DbError::TypeError if the column is not the requested shape.
pub fn read(row : @moondb.Row) -> Unit raise @moondb.DbError { let id = row.int64(0) // column 0, as Int64 let email = row.text_by("email") // by column name let unset = row.is_null(2) // SQL NULL check let width = row.width() // column count println("\{id} \{email} \{unset} \{width}") }
§5
Defaults & overrides
Two operations ship with defaults you may keep or override:
ping → Bool
Default issues SELECT 1 and returns whether it round-trips, never raising. Override with a cheaper heartbeat if your backend has one.
query_stream → &Cursor
Default materialises via query and returns an ArrayCursor. A driver with incremental fetch overrides this with a live cursor for bounded memory.
§6
Built on moondb
ORMs such as moonorm write their SQL generation against the Driver trait — not against any one backend. That is the whole point of the contract: one query layer, every ratified driver.