moondb

API · generated

The interface

522 declarations across 14 packages — the contract and the three drivers that ratify it — read out of pkg.generated.mbti and described from the /// docs in the sources.

moonbitstack/moondb

moondb

Types 28

pub struct ArrayCursor {
  rows : Array[Row]
  mut pos : Int32
}

A Cursor over an already-materialised Array[Row]. The fallback the default Driver::query_stream hands back for any driver that has not overridden it: it preserves the streaming interface (callers pull rows one at a time) even though the rows were fetched up front. The mock driver and the reference query layer test against it.

pub struct AsyncArrayCursor {
  rows : Array[Row]
  mut pos : Int32
}

An AsyncCursor over already-materialised rows — the async twin of ArrayCursor, and what the default AsyncDriver::query_stream hands back. It preserves the streaming interface (rows are pulled one at a time) even though they were all fetched up front.

It is a separate type from ArrayCursor rather than a second trait impl on it: one type implementing both Cursor and AsyncCursor makes every cursor.next() on the concrete type ambiguous between the two traits, which would break existing callers.

pub(open) trait AsyncCursor {
  async fn next(Self) -> Row? raise DbError
  async fn close(Self) -> Unit
}

The async counterpart of Cursor — a forward-only cursor whose next is awaited. A wire driver reads the next row off the socket when it is asked for, which is an I/O round trip and therefore async; the synchronous Cursor cannot express that, so a networked backend has no way to stream through it.

The resource discipline is Cursor's: MoonBit has no finalizer, so a driver-backed cursor MUST be drained (next until None) or [closed], or the statement leaks and the connection is left mid-result. AsyncArrayCursor is the materialised fallback, for a driver with no incremental fetch.

close does not raise, mirroring Cursor::close: abandoning a cursor is best-effort. A driver whose close has to do I/O — draining an unread result off the wire — swallows the failure, because a connection whose drain failed is already unusable and the next statement on it will say so.

pub(open) so out-of-tree drivers implement it.

pub(open) trait AsyncDriver {
  async fn execute(Self, String, Array[Value]) -> ExecResult raise DbError
  async fn query(Self, String, Array[Value]) -> Array[Row] raise DbError
  async fn query_stream(Self, String, Array[Value]) -> &AsyncCursor raise DbError = _
  async fn begin(Self) -> Unit raise DbError
  async fn commit(Self) -> Unit raise DbError
  async fn rollback(Self) -> Unit raise DbError
  async fn close(Self) -> Unit
  async fn ping(Self) -> Bool = _
  async fn prepare(Self, String) -> &AsyncStmt raise DbError = _
  fn dialect(Self) -> Dialect = _
  fn capabilities(Self) -> Capabilities = _
}

The contract a backend that talks over a network implements — Driver with every round trip awaited.

## Why there are two driver traits

Driver is synchronous, which fits a backend whose calls block in C: moonsqlite steps a prepared statement and returns. A backend reached over TCP cannot be written that way in MoonBit, because the only socket stack (moonbitlang/async) is async-only and an async fn cannot be called from a synchronous one. A wire driver forced to conform to Driver can therefore do nothing but raise — and, worse, it raises from ping too, so a Pool with pre_ping judges every one of its connections permanently unhealthy.

AsyncDriver is the seam for those backends. It is a peer of Driver, not a replacement: a synchronous backend keeps implementing Driver and does not become async to satisfy anything here. The two traits are deliberately identical method for method, so a query layer can offer a sync and an async path that read the same.

Everything Driver documents about the calling convention holds unchanged: sql carries positional placeholders in the backend's own spelling, params supplies one Value per placeholder in order, and values are bound out-of-band rather than interpolated.

This trait declares async methods but pulls in no async runtime — moondb stays dependency-free and still compiles on every backend. Only a driver that implements it, and a caller that runs it inside an event loop, need the runtime.

pub(open) so out-of-tree drivers (moonpostgres, moonmysql, …) implement it.

pub struct AsyncExecutor[T] {
  inner : T
}

The narrow view of an AsyncDriver. Obtain one with AsyncExecutor::new.

pub(open) trait AsyncQueryExecutor {
  async fn execute(Self, String, Array[Value]) -> ExecResult raise DbError
  async fn query(Self, String, Array[Value]) -> Array[Row] raise DbError
  async fn query_stream(Self, String, Array[Value]) -> &AsyncCursor raise DbError
  fn dialect(Self) -> Dialect
  fn capabilities(Self) -> Capabilities
}

The asynchronous counterpart of QueryExecutor, for a backend reached over a socket.

pub(open) trait AsyncStmt {
  async fn bind(Self, Array[Value]) -> Unit raise DbError
  async fn execute(Self) -> ExecResult raise DbError
  async fn query(Self) -> Array[Row] raise DbError
  async fn query_stream(Self) -> &AsyncCursor raise DbError = _
  async fn close(Self) -> Unit
  fn sql(Self) -> String
  fn params(Self) -> Array[String]
  async fn bind_named(Self, Map[String, Value]) -> Unit raise DbError = _
}

The asynchronous counterpart of Stmt, for a backend reached over a socket.

pub struct AsyncTxn[T] {
  driver : T
  mut depth : Int32
}

The asynchronous counterpart of Txn, for a backend reached over a socket.

let tx = @moondb.AsyncTxn::new(conn)
tx.run(async fn(tx) {
tx.driver().execute("INSERT INTO hero VALUES ($1, $2)", [Int(1), Text("Nova")])
|> ignore
})
pub struct BufferedAsyncStmt[T] {
  driver : T
  text : String
  mut params : Array[Value]
  mut closed : Bool
}

The statement handle AsyncDriver::prepare returns when the backend has nothing server-side to offer — the asynchronous twin of BufferedStmt.

pub struct BufferedStmt[T] {
  driver : T
  text : String
  mut params : Array[Value]
  mut closed : Bool
}

The statement handle Driver::prepare returns when a driver has nothing server-side to offer: it keeps the text and the bound values and re-runs the driver's ordinary execute/query on every call. It makes the Stmt contract available to every existing driver without changing it.

pub(all) struct Capabilities {
  dialect : Dialect
  prepared_statements : Bool
  named_parameters : Bool
  server_cursors : Bool
  streaming_copy : Bool
  notifications : Bool
  transactions : Bool
  savepoints : Bool
  multiple_result_sets : Bool
  unsigned_integers : Bool
  returning : Bool
}
pub(open) trait Cursor {
  fn next(Self) -> Row? raise DbError
  fn close(Self) -> Unit
}

A forward-only cursor over a query's rows — the streaming counterpart to Driver::query. Where query materialises the whole result, a cursor yields one Row at a time, Python DB-API's fetchone).

next advances and returns the next row, or None once the result is exhausted; close releases the cursor early (a driver holding a server-side cursor or prepared statement frees it here). A driver with real incremental fetch — moonsqlite steps its prepared statement, an async driver reads rows off the wire on demand — returns a live cursor; the default Driver::query_stream falls back to an ArrayCursor over a materialised result, which honours the same interface without the memory bound.

A live cursor holds a resource (a prepared statement, a busy connection). MoonBit has no finalizer to reclaim it, so a driver-backed cursor MUST be fully drained (next until None) or closed — abandoning it leaks the statement or leaves the connection mid-result. The materialised ArrayCursor holds nothing and is safe to drop.

pub(open) so out-of-tree drivers implement it.

pub(all) suberror DbError {
  ConnectError(String)
  QueryError(String)
  TypeError(String)
  Closed
  Server(ServerDetail)
}

A database-layer failure. Every fallible operation on the interface raises this one error type, so a query layer catches a single kind. Kept deliberately few so drivers can classify without a taxonomy:.

* ConnectError — opening, attaching, or handshaking a connection failed, or the connection dropped mid-flight. * QueryError — the backend rejected a statement, or the driver hit a protocol error, without a structured server response to report. * TypeError — a Row typed accessor was asked to read a column as a type it does not hold (e.g. int on a Text), or a column index/name that does not exist in the row. * Closed — the connection or statement was used after close. * Server — the server rejected the statement and reported a SQLSTATE and/or a numeric error code; see ServerDetail and DbError::kind. An embedded engine reports through this case too, with the numeric code alone.

Declared pub(all) so out-of-tree drivers (moonpostgres, moonmysql, …) that live in their own packages can construct and raise these cases — a plain pub suberror would let them catch a DbError but not build one.

pub(all) enum Dialect {
  Sqlite
  Postgres
  Mysql
  Clickhouse
  Duckdb
  Sqlserver
}
pub(open) trait Driver {
  fn execute(Self, String, Array[Value]) -> ExecResult raise DbError
  fn query(Self, String, Array[Value]) -> Array[Row] raise DbError
  fn query_stream(Self, String, Array[Value]) -> &Cursor raise DbError = _
  fn begin(Self) -> Unit raise DbError
  fn commit(Self) -> Unit raise DbError
  fn rollback(Self) -> Unit raise DbError
  fn close(Self) -> Unit
  fn ping(Self) -> Bool = _
  fn prepare(Self, String) -> &Stmt raise DbError = _
  fn dialect(Self) -> Dialect = _
  fn capabilities(Self) -> Capabilities = _
}

The contract every database backend implements and every query layer is written against — the seam moondb exists to define. Reduced to the smallest set of operations a relational backend must offer.

## The binding contract

execute and query both take (sql, params). sql carries **positional placeholders** and params supplies one Value per placeholder, in order. A driver is expected to bind values out-of-band rather than interpolate them into the SQL text. A driver that cannot — the MySQL text protocol has no out-of-band binding until prepared statements land — has to escape for the server's actual sql_mode, and must say so in its own documentation.

The placeholder token itself is dialect-specific and chosen by the driver (SQLite/MySQL use ?, PostgreSQL uses $1, $2, …); a query layer that emits SQL for a given backend uses that backend's token. moondb fixes the calling convention (ordered params array), not the spelling.

* execute runs a statement that returns no rows (INSERT/UPDATE/DELETE/DDL) and reports an ExecResult. * query runs a statement that returns rows and materialises every Row. * begin / commit / rollback bracket an explicit transaction. Nesting (savepoints), isolation levels, and read-only hints are driver concerns layered on top; the base contract is the three flat operations. * close releases the connection. It does not raise: closing is best-effort and idempotent.

This trait is pub(open) so out-of-tree drivers (moonsqlite, moonpostgres, moonmysql, …) can implement it.

pub(all) enum ErrorKind {
  Interface
  Database
  Data
  Operational
  Integrity
  Programming
  Internal
  NotSupported
}
pub(all) struct ExecResult {
  rows_affected : Int64
  last_insert_id : Int64
}

The outcome of a non-row statement, aliased from moondb.

pub struct Executor[T] {
  inner : T
}

The narrow view of a Driver: everything needed to run SQL, and nothing that owns the connection. Obtain one with Driver::executor.

pub(open) trait FromValue {
  fn from_value(Value) -> Self raise DbError
}

The conversions are the ones Row's typed accessors make. A refusal is a TypeError whose message reads after "column N is", such as "Text, not Int".

pub struct MockDriver {
  mut rows : Array[Row]
  mut next_id : Int64
  savepoints : Array[Int32]
  named_savepoints : Array[(String, Int32)]
  mut closed : Bool
}

A dependency-free, in-memory reference Driver. It exists for two reasons: to prove the interface is implementable end to end, and to give query layers built on moondb (moonorm and friends) a real test double they can run against with no database and no native backend — it compiles on every target.

It is a deliberately naive echo store, **not** a SQL engine: it does not parse sql. Each execute appends one Row built from the bound params (columns named c0, c1, …) and hands back an ExecResult with a monotonically increasing last_insert_id; each query returns every stored row. What it does model faithfully is the transaction bracket: begin snapshots the store, rollback restores it, commit keeps the changes — so a test can assert real rollback semantics against it.

pub(all) struct Named {
  sql : String
  slots : Array[String]
}

A statement whose :name parameters have been rewritten to the backend's own positional spelling, together with the name of the value each position takes.

The rewrite happens once, before the statement reaches the server: PostgreSQL's extended-query protocol wants $1, $2, …, SQLite and MySQL want one ? per parameter. bind(#bind) turns a name-keyed argument map into the ordered array the driver expects, so a query layer writes SELECT * FROM hero WHERE id = :id AND realm = :realm once and runs it on all three backends.

The rewrite is a lexer, not a search-and-replace: a :name inside a string literal, a quoted identifier, a -- / # / /* */ comment or a PostgreSQL $tag$ … $tag$ body is left alone, and neither :: (a cast) nor := (a named function argument) opens a parameter.

test "named parameters become the backend's own spelling" {
let q = @moondb.rewrite_named(
"SELECT * FROM hero WHERE id = :id AND realm = :realm",
@moondb.Dialect::Postgres,
)
assert_eq(q.sql, "SELECT * FROM hero WHERE id = $1 AND realm = $2")
assert_eq(q.slots, ["id", "realm"])

let m = @moondb.rewrite_named(
"SELECT * FROM hero WHERE id = :id",
@moondb.Dialect::Mysql,
)
assert_eq(m.sql, "SELECT * FROM hero WHERE id = ?")
}
pub(open) trait QueryExecutor {
  fn execute(Self, String, Array[Value]) -> ExecResult raise DbError
  fn query(Self, String, Array[Value]) -> Array[Row] raise DbError
  fn query_stream(Self, String, Array[Value]) -> &Cursor raise DbError
  fn dialect(Self) -> Dialect
  fn capabilities(Self) -> Capabilities
}

The part of a driver a query layer actually needs: run a statement, and ask what the backend is.

Driver bundles three unrelated responsibilities — running SQL, owning a transaction, and owning a connection. A repository method, a query builder, a migration step all want the first and must not be able to close the connection out from under the caller that lent it to them. That is what the narrow view is for: Driver::executor hands out an Executor, and a function that takes &QueryExecutor can run SQL and cannot end the session.

It also carries the two questions a dialect-sensitive layer asks before it emits SQL: dialect(#dialect) for the placeholder spelling and capabilities(#capabilities) for everything the backend can do beyond the base contract. A layer that must prepare statements takes the full Driver instead.

test "a repository only needs to run SQL" {
let db = @moondb.MockDriver::new()
fn count(db : &@moondb.QueryExecutor) -> Int32 raise @moondb.DbError {
db.query("SELECT 1", []).length()
}
assert_eq(count(@moondb.Executor::new(db)), 0)
assert_eq(@moondb.Executor::new(db).dialect(), @moondb.Dialect::Sqlite)
}
pub(all) struct Row {
  columns : Array[String]
  values : Array[Value]
}

One decoded result row (column names + values), aliased from moondb.

pub(all) struct ServerDetail {
  kind : ErrorKind
  sqlstate : String?
  code : Int32?
  message : String
  detail : String?
  hint : String?
  severity : String?
}

What a server-reported failure carries beyond its message: the five-character SQLSTATE, the vendor error number, and the server's severity, detail and hint. Drivers fill what their protocol supplies and leave the rest None. The coarse ErrorKind is derived from the SQLSTATE (and, for MySQL's generic HY000, from the errno).

pub(open) trait Stmt {
  fn bind(Self, Array[Value]) -> Unit raise DbError
  fn execute(Self) -> ExecResult raise DbError
  fn query(Self) -> Array[Row] raise DbError
  fn query_stream(Self) -> &Cursor raise DbError = _
  fn close(Self) -> Unit
  fn sql(Self) -> String
  fn params(Self) -> Array[String]
  fn bind_named(Self, Map[String, Value]) -> Unit raise DbError = _
}

A statement that is prepared once and run many times with new values.

Driver::execute and Driver::query prepare, bind, run and discard in one call, which is right for a one-off and wasteful for a loop: a server that has already parsed and planned a statement can execute it again with nothing but the values. A Stmt is that handle. bind(#bind) replaces the values, execute(#execute) and query(#query) run the statement, and close(#close) releases the server-side resources.

A handle is not a connection: it belongs to the connection that prepared it, and running it after that connection closed raises Closed. It is not safe to share between tasks for the same reason — one wire, one statement at a time.

test "one statement, two bindings" {
let db = @moondb.MockDriver::new()
let stmt = db.prepare("INSERT INTO hero (id, name) VALUES (?, ?)")
stmt.bind([@moondb.Value::Int(1), @moondb.Value::Text("Nova")])
stmt.execute() |> ignore
stmt.bind([@moondb.Value::Int(2), @moondb.Value::Text("Iris")])
stmt.execute() |> ignore
stmt.close()
assert_eq(db.row_count(), 2)
}
pub(open) trait ToValue {
  fn to_value(Self) -> Value
}

Implemented for the type behind each Value case and for T?, where None binds NULL. A type of the program's own implements it to be stored as one of those cases.

pub struct Txn[T] {
  driver : T
  mut depth : Int32
}

A transaction that nests: the outermost run(#run) opens a real transaction, every nested one opens a savepoint, and an error undoes only the work of the level that raised.

Driver::begin / commit / rollback are flat, which is all a backend has to offer, but application code nests: a service method opens a transaction, calls a repository method that opens one too, and the inner failure must not discard the outer work. Txn closes the bracket around a body so the pairing cannot be forgotten, and derives the savepoint name from the nesting depth so two levels never collide.

test "a nested body that fails keeps the outer work" {
let db = @moondb.MockDriver::new()
let tx = @moondb.Txn::new(db)
tx.run(fn(tx) raise @moondb.DbError {
tx
.driver()
.execute("INSERT INTO hero VALUES (?, ?)", [
@moondb.Value::Int(1),
@moondb.Value::Text("Nova"),
])
|> ignore
// The inner level fails; only its own work is undone.
(tx.run(fn(_tx) raise @moondb.DbError {
raise @moondb.QueryError("inner failed")
}) catch {
_ => ()
})
|> ignore
})
assert_eq(db.row_count(), 1)
}
pub(all) enum Value {
  Null
  Bool(Bool)
  Int(Int32)
  Int64(Int64)
  Double(Double)
  Text(String)
  Blob(Bytes)
  Time(String)
  Numeric(String)
  Uuid(Bytes)
  Json(String)
  Array(Array[Value])
}

The dialect-neutral value type this driver binds and decodes. Aliased so the wire code reads Value rather than @moondb.Value throughout; the enum constructors (Int, Text, …) are still spelled @moondb.Int where a value is built.

Functions & methods 100

pub fn ArrayCursor::close(Self) -> Unit

Cursor's close, callable on the value directly.

pub fn ArrayCursor::new(Array[Row]) -> Self

A cursor positioned before the first of rows.

pub fn ArrayCursor::next(Self) -> Row? raise DbError

Cursor's next, callable on the value directly.

pub async fn AsyncArrayCursor::close(Self) -> Unit

AsyncCursor's close, callable on the value directly.

pub fn AsyncArrayCursor::new(Array[Row]) -> Self

A cursor positioned before the first of rows.

pub async fn AsyncArrayCursor::next(Self) -> Row? raise DbError

AsyncCursor's next, callable on the value directly.

pub fn[T : AsyncDriver] AsyncExecutor::capabilities(Self[T]) -> Capabilities

What the driver underneath can do beyond the base contract.

pub fn[T : AsyncDriver] AsyncExecutor::dialect(Self[T]) -> Dialect

The SQL dialect of the driver underneath.

pub fn[T] AsyncExecutor::driver(Self[T]) -> T

The driver underneath.

pub async fn[T : AsyncDriver] AsyncExecutor::execute(Self[T], String, Array[Value]) -> ExecResult raise DbError

Run a non-row statement.

pub fn[T] AsyncExecutor::new(T) -> Self[T]

The narrow view over inner.

pub async fn[T : AsyncDriver] AsyncExecutor::query(Self[T], String, Array[Value]) -> Array[Row] raise DbError

Run a row-returning statement and materialise every row.

pub async fn[T : AsyncDriver] AsyncExecutor::query_stream(Self[T], String, Array[Value]) -> &AsyncCursor raise DbError

Stream a row-returning statement's rows.

pub fn[T] AsyncTxn::depth(Self[T]) -> Int32

How many bodies are currently open, zero when none is.

pub fn[T] AsyncTxn::driver(Self[T]) -> T

The connection this handle opens transactions on.

pub fn[T] AsyncTxn::new(T) -> Self[T]

An async transaction handle over driver, at depth zero and not yet open.

pub async fn[T : AsyncDriver] AsyncTxn::run(Self[T], async (Self[T]) -> Unit raise DbError) -> Unit raise DbError

Run body inside a transaction, or inside a savepoint when one is already open. Same contract as Txn::run, with every round trip awaited.

pub fn[T] BufferedAsyncStmt::new(T, String) -> Self[T]

A buffered async handle over driver for sql, with no values bound yet.

pub fn[T] BufferedStmt::new(T, String) -> Self[T]

A buffered handle over driver for sql, with no values bound yet.

pub fn Capabilities::count(Self) -> Int32

The number of capabilities the backend has, which is the useful shape for a feature matrix in a test: a preset that loses one silently stops adding up.

pub fn Capabilities::for_dialect(Dialect) -> Self

The capabilities of the driver moondb ships for dialect.

pub fn Capabilities::is_for(Self, Dialect) -> Bool

Whether this report describes dialect.

pub fn classify_sqlstate(String) -> ErrorKind

Classify a five-character SQLSTATE into an ErrorKind using the class (its first two characters), per the SQL:2016 / PostgreSQL table of class codes. Unknown, empty, or short states fall back to Database.

pub fn DbError::code(Self) -> Int32?

The vendor error number the server reported (MySQL's errno), or None.

pub fn DbError::kind(Self) -> ErrorKind

The failure class of any DbError, including the ones that carry no server response. ConnectError is operational (a retry may succeed); a plain QueryError is a generic database failure; a TypeError or Closed is interface misuse; a structured Server reports the class its SQLSTATE implies.

pub fn DbError::sqlstate(Self) -> String?

The five-character SQLSTATE the server reported, or None when the failure did not come from a structured server response.

pub fn DbError::to_string(Self) -> String

A one-line rendering of the error, e.g. QueryError: no such table: hero or ServerError: relation "hero" does not exist [42P01].

pub fn Dialect::name(Self) -> String

The dialect's short name in lower case — the spelling used in configuration and in a driver's Capabilities.

pub fn Dialect::parse(String) -> Self?

Look a dialect up by its short name, ignoring case. Accepts the usual aliases (postgresql, pg, mariadb) because a configuration file is written by a person, not by this module.

pub fn Dialect::placeholder(Self, Int32) -> String

The placeholder for the n-th bound parameter (1-based).

PostgreSQL's extended-query protocol numbers parameters, so each one is spelled $n; SQLite and MySQL bind positionally and spell every one ?. The index is ignored by the two positional dialects, which is why a builder can call this unconditionally.

pub fn drain(&Cursor) -> Array[Row] raise DbError

Drain a cursor into an array — the inverse of streaming, for callers that do want every row (and for asserting a cursor yields exactly what query would).

pub async fn drain_async(&AsyncCursor) -> Array[Row] raise DbError

Drain an async cursor into an array — the drain of the async seam, for a caller that wants every row after all, and for asserting a cursor yields exactly what query would.

pub fn ErrorKind::to_string(Self) -> String

The lowercase name of the class, for logs and test output.

pub fn[T : Driver] Executor::capabilities(Self[T]) -> Capabilities

What the driver underneath can do beyond the base contract.

pub fn[T : Driver] Executor::dialect(Self[T]) -> Dialect

The SQL dialect of the driver underneath.

pub fn[T] Executor::driver(Self[T]) -> T

The driver underneath, for the caller that does own the connection.

pub fn[T : Driver] Executor::execute(Self[T], String, Array[Value]) -> ExecResult raise DbError

Run a non-row statement. Present as a plain method as well as a trait method so the view reads like the driver it wraps.

pub fn[T] Executor::new(T) -> Self[T]

The narrow view over inner.

pub fn[T : Driver] Executor::query(Self[T], String, Array[Value]) -> Array[Row] raise DbError

Run a row-returning statement and materialise every row.

pub fn[T : Driver] Executor::query_stream(Self[T], String, Array[Value]) -> &Cursor raise DbError

Stream a row-returning statement's rows.

pub fn MockDriver::begin(Self) -> Unit raise DbError

Driver's begin, callable on the value directly.

pub fn MockDriver::capabilities(Self) -> Capabilities

Driver's capabilities, callable on the value directly.

pub fn MockDriver::close(Self) -> Unit

Driver's close, callable on the value directly.

pub fn MockDriver::commit(Self) -> Unit raise DbError

Driver's commit, callable on the value directly.

pub fn MockDriver::dialect(Self) -> Dialect

Driver's dialect, callable on the value directly.

pub fn MockDriver::execute(Self, String, Array[Value]) -> ExecResult raise DbError

Driver's execute, callable on the value directly.

pub fn MockDriver::in_transaction(Self) -> Bool

Whether a transaction is currently open (at least one un-committed begin).

pub fn MockDriver::is_closed(Self) -> Bool

Whether the connection has been closed.

pub fn MockDriver::new() -> Self

A fresh, empty mock connection.

pub fn MockDriver::ping(Self) -> Bool

Driver's ping, callable on the value directly.

pub fn MockDriver::prepare(Self, String) -> &Stmt raise DbError

Driver's prepare, callable on the value directly.

pub fn MockDriver::query(Self, String, Array[Value]) -> Array[Row] raise DbError

Driver's query, callable on the value directly.

pub fn MockDriver::query_stream(Self, String, Array[Value]) -> &Cursor raise DbError

Driver's query_stream, callable on the value directly.

pub fn MockDriver::rollback(Self) -> Unit raise DbError

Driver's rollback, callable on the value directly.

pub fn MockDriver::row_count(Self) -> Int32

How many rows the store currently holds. A test-facing helper, not part of the Driver contract.

pub fn Named::arity(Self) -> Int32

How many values the statement binds.

pub fn Named::bind(Self, Map[String, Value]) -> Array[Value] raise DbError

Turn a name-keyed argument map into the positional array the statement binds. A missing name raises a TypeError naming the parameter; a key the statement never mentions raises one too, so a typo in a repository method is caught at the call rather than silently ignored.

pub fn Named::is_named(Self) -> Bool

Whether the statement takes named parameters at all.

pub fn Named::names(Self) -> Array[String]

The distinct parameter names, in first-use order.

pub fn rewrite_named(String, Dialect) -> Named raise DbError

Rewrite :name parameters in sql into dialect's positional spelling.

A statement may use either :name or positional ?, never both; mixing them raises a QueryError naming the offset, because the two conventions disagree about what the caller's array means.

pub fn Row::array(Self, Int32) -> Array[Value] raise DbError

Read column idx as an SQL array. Raises TypeError unless the cell is Array.

pub fn Row::array_by(Self, String) -> Array[Value] raise DbError

Read the column named name as an SQL array.

pub fn Row::blob(Self, Int32) -> Bytes raise DbError

Read column idx as an opaque blob. Raises TypeError unless the cell is Blob.

pub fn Row::blob_by(Self, String) -> Bytes raise DbError

Read the column named name as an opaque blob.

pub fn Row::bool(Self, Int32) -> Bool raise DbError

Read column idx as a Bool. Raises TypeError unless the cell is Bool.

pub fn Row::bool_by(Self, String) -> Bool raise DbError

Read the column named name as a Bool.

pub fn Row::by_name(Self, String) -> Value?

The Value of the column named name, or None if the row has no such column. Names match exactly as the driver reported them (case-sensitive).

pub fn Row::double(Self, Int32) -> Double raise DbError

Read column idx as a Double, widening an integer. Raises TypeError unless the cell is Double, Int, or Int64.

pub fn Row::double_by(Self, String) -> Double raise DbError

Read the column named name as a Double.

pub fn Row::get(Self, Int32) -> Value raise DbError

The raw Value at column idx (0-based, projection order). Raises QueryError if idx is out of range.

pub fn Row::index_of(Self, String) -> Int32 raise DbError

Resolve a column name to its index, raising QueryError if absent. The positional accessors are the primitives; the _by-name accessors resolve through this.

pub fn Row::int(Self, Int32) -> Int32 raise DbError

Read column idx as an Int, narrowing an Int64 to its low 32 bits. Raises TypeError unless the cell is Int or Int64.

pub fn Row::int64(Self, Int32) -> Int64 raise DbError

Read column idx as an Int64, widening an Int. Raises TypeError unless the cell is Int or Int64.

pub fn Row::int64_by(Self, String) -> Int64 raise DbError

Read the column named name as an Int64.

pub fn Row::int_by(Self, String) -> Int32 raise DbError

Read the column named name as an Int.

pub fn Row::is_null(Self, Int32) -> Bool raise DbError

Whether column idx is SQL NULL. Raises QueryError if idx is out of range. Call this before a typed accessor when a column is nullable, since the typed accessors treat NULL as a type mismatch.

pub fn Row::is_null_by(Self, String) -> Bool raise DbError

Whether the column named name is SQL NULL. Raises QueryError if the column is absent.

pub fn Row::json(Self, Int32) -> String raise DbError

Read column idx as JSON text. Raises TypeError unless the cell is Json.

pub fn Row::json_by(Self, String) -> String raise DbError

Read the column named name as JSON text.

pub fn Row::numeric(Self, Int32) -> String raise DbError

Read column idx as an exact decimal string. Raises TypeError unless the cell is Numeric.

pub fn Row::numeric_by(Self, String) -> String raise DbError

Read the column named name as an exact decimal string.

pub fn[T : FromValue] Row::read(Self, Int32) -> T raise DbError

Read column idx as any FromValue type, the typed accessors' common form: let age : Int? = row.read(3). A refusal names the column.

pub fn[T : FromValue] Row::read_by(Self, String) -> T raise DbError

Read the column named name as any FromValue type.

pub fn Row::text(Self, Int32) -> String raise DbError

Read column idx as text. Raises TypeError unless the cell is Text.

pub fn Row::text_by(Self, String) -> String raise DbError

Read the column named name as text.

pub fn Row::time(Self, Int32) -> String raise DbError

Read column idx as a temporal value's ISO-8601 text. Raises TypeError unless the cell is Time.

pub fn Row::time_by(Self, String) -> String raise DbError

Read the column named name as temporal ISO-8601 text.

pub fn Row::uuid(Self, Int32) -> Bytes raise DbError

Read column idx as the raw bytes of a UUID. Raises TypeError unless the cell is Uuid.

pub fn Row::uuid_by(Self, String) -> Bytes raise DbError

Read the column named name as UUID bytes.

pub fn Row::width(Self) -> Int32

The number of columns in the row.

pub fn[T] Txn::depth(Self[T]) -> Int32

How many bodies are currently open, zero when none is.

pub fn[T] Txn::driver(Self[T]) -> T

The connection this handle opens transactions on.

pub fn[T] Txn::is_open(Self[T]) -> Bool

Whether a body is currently open.

pub fn[T] Txn::new(T) -> Self[T]

A transaction handle over driver, at depth zero and not yet open.

pub fn[T : Driver] Txn::run(Self[T], (Self[T]) -> Unit raise DbError) -> Unit raise DbError

Run body inside a transaction, or inside a savepoint when a transaction is already open. Returning normally commits (or releases the savepoint); raising rolls back (or rolls back to the savepoint) and re-raises the original error.

A body that catches an inner failure and carries on keeps the outer work, which is the point of the savepoint: only the level that failed is undone.

pub fn Value::from_value(Self) -> Self raise DbError

FromValue's from_value, callable on the value directly.

pub fn Value::is_null(Self) -> Bool

Whether this value is SQL NULL.

pub fn Value::json_text(Self) -> String

Render this value as JSON text. Scalars map to their JSON counterparts, text is quoted and escaped, Time/Numeric become strings, Json is embedded as-is, and Array becomes a JSON array. Drivers use this to bind array and JSON values on protocols that carry parameters as text.

pub fn Value::kind(Self) -> String

The name of a value's variant ("Null", "Int", "Blob", …). Used to build legible DbError::TypeError messages when a typed accessor is asked for the wrong shape.

pub fn Value::to_value(Self) -> Self

ToValue's to_value, callable on the value directly.

moonbitstack/moondb/pool

pool

Types 1

pub struct Pool[D] {
  // private fields
}

A fixed-ceiling connection pool over any Driver. Opening a database connection is expensive — a TCP handshake and auth round trip for a networked backend — so a server hands out a small set of connections and reuses them instead of opening one per request.

The pool is generic in the concrete driver D rather than holding &Driver trait objects, so acquire gives back the real driver type and a caller keeps access to backend-specific methods (SQLite's exec_script, a driver's prepared-statement handle). new takes a make factory that opens one fresh connection; the pool calls it only when no idle connection is available and the open count is still below max_size.

Beyond plain reuse, the pool keeps a connection healthy over its lifetime.

* **pre-ping** (pre_ping) — before an idle connection is handed back it is probed with Driver::ping; a connection that fails the probe is closed and skipped, so a caller never receives a connection the backend has already dropped. * **max lifetime** (max_lifetime_ms) — a connection older than this is retired on acquire and replaced with a fresh one, so long-lived pools recycle connections a load balancer or the server may have aged out. Age is measured with an injected clock, exactly as database/sql swaps nowFunc in tests; the default clock disables the check. * **acquire timeout** (acquire_timeout_ms) — the wait budget an exhausted acquire is allowed. See the note on synchrony below.

It is synchronous and single-threaded by design — moondb's base contract is sync, and MoonBit's pure backends have no shared-memory threads — so acquire never truly blocks: nothing else can return a connection while one call waits. A request that finds the pool exhausted therefore fails immediately rather than sleeping; acquire_timeout_ms is carried into that failure's message and is the budget a driver running on an async runtime honours when it layers real waiting on top. Use try_acquire for the non-raising "give me one only if free" path. The reuse, ceiling, health, and lifecycle bookkeeping all live here.

The counting is moonpool's Pool: live, idle and lent, each lent connection's birth recognised on its way back so max_lifetime counts from creation. So is the optional ward in front of the database: backoff after failed connects, a token bucket spacing reconnections, a breaker, a shedder. This type adds only what needs a driver — opening, closing, probing.

Functions & methods 15

pub fn[D : @moondb.Driver] Pool::acquire(Self[D]) -> D raise @moondb.DbError

Take a connection: reuse an idle one if the pool has a healthy, unexpired one (the common path, avoiding a fresh handshake), otherwise open a new one through make as long as the open count is below max_size.

An idle connection past max_lifetime_ms, or one that fails the Driver::ping probe when pre_ping is on, is closed and dropped rather than handed back; the pool then tries the next idle connection or opens a fresh one. Raises QueryError if the pool is exhausted (all max_size connections are checked out), ConnectError if the ward holds back or refuses a new connection, or Closed if it has been closed. The caller must return the connection with release — or use with_conn, which does so even on error.

pub fn[D] Pool::acquire_timeout_ms(Self[D]) -> Int64

The configured acquire wait budget in milliseconds (0 = fail immediately).

pub fn[D : @moondb.Driver] Pool::close_all(Self[D]) -> Unit

Close every idle connection and mark the pool closed. Connections still checked out are not touched — each is closed as it is released back. After this, acquire raises Closed. Idempotent.

This is the graceful half of a shutdown: nothing in flight is cut off. To wait for the rest, poll in_use_count until it reaches zero — this pool is synchronous, so the waiting belongs to whatever async layer owns it.

pub fn[D] Pool::idle_count(Self[D]) -> Int32

How many connections are currently idle (checked in and reusable).

pub fn[D] Pool::in_use_count(Self[D]) -> Int32

How many connections are checked out right now. A shutdown that wants to wait for borrowed connections to come home polls this after close_all: the close empties the idle set, so what is left is exactly what is still in use.

pub fn[D] Pool::is_closed(Self[D]) -> Bool

Whether close_all has been called.

pub fn[D] Pool::max_lifetime_ms(Self[D]) -> Int64

The configured maximum connection lifetime in milliseconds (0 = unlimited).

pub fn[D] Pool::max_size(Self[D]) -> Int32

The pool's ceiling on simultaneously open connections.

pub fn[D] Pool::new(() -> D raise @moondb.DbError, max_size? : Int32, overflow? : Int32, max_lifetime_ms? : Int64, pre_ping? : Bool, acquire_timeout_ms? : Int64, clock? : () -> Int64, ward? : @moonpool.Ward, reset? : (D) -> Unit raise @moondb.DbError, rand? : () -> Double, cpu? : () -> Int32) -> Self[D] raise @moondb.DbError

Build a pool whose connections come from make.

* max_size caps how many connections may be open at once (in use plus idle); it defaults to 10, and must be positive. * max_lifetime_ms retires a connection older than this many milliseconds on acquire (0, the default, means no age limit). Enforcing it requires a real clock; with the default clock every connection reads as age 0. * pre_ping turns on the Driver::ping health probe before an idle connection is reused (off by default). * acquire_timeout_ms records the wait budget for an exhausted acquire; 0 (the default) means "fail immediately". In this synchronous pool the wait is always degenerate (see the type doc), so the value serves the error message and an async layer above. * clock returns a monotonically non-decreasing millisecond reading and exists so max_lifetime is testable and portable; it defaults to a constant 0, which disables age-based retirement. * ward is moonpool's, none by default. A ward that times anything needs a real clock. * rand draws from [0, 1) for the ward's breaker and backoff jitter; without it the pool draws from a system-seeded generator. * cpu reads the processor load in thousandths for the ward's shedder; without it the load reads as zero and the shedder never sheds.

pub fn[D] Pool::open_count(Self[D]) -> Int32

How many connections the pool has open in total: those checked out plus those sitting idle. Never exceeds max_size.

pub fn[D] Pool::overflow(Self[D]) -> Int32

How many connections may be opened above max_size when the pool is busy.

pub fn[D] Pool::pre_ping(Self[D]) -> Bool

Whether the pre-ping health probe is enabled.

pub fn[D : @moondb.Driver] Pool::release(Self[D], D) -> Unit

Return a connection to the idle set so a later acquire can reuse it, preserving the birth time it was opened with so max_lifetime keeps counting from creation rather than resetting on every checkout. A connection released back into a closed pool is closed immediately rather than pooled, so no handle outlives close_all.

pub fn[D : @moondb.Driver] Pool::try_acquire(Self[D]) -> D? raise @moondb.DbError

Take a connection only if one is immediately available — an idle one or fresh headroom under max_size — returning None instead of raising when the pool is exhausted or its ward says no. This is the non-blocking counterpart of acquire, so a caller can choose to shed load or retry. Health and lifetime eviction apply exactly as in acquire; a genuine failure while opening a fresh connection (the make factory raising) still propagates.

pub fn[D : @moondb.Driver, R] Pool::with_conn(Self[D], (D) -> R raise @moondb.DbError) -> R raise @moondb.DbError

Run f with a pooled connection, returning it afterwards whether f returns or raises. This is the leak-proof way to use the pool: the release happens on every path, so a raising query never strands a connection checked out.

moonbitstack/moondb/apool

apool

Types 1

pub struct AsyncPool[D] {
  // private fields
}

A connection pool for an @moondb.AsyncDriver — the counterpart of @moondb/pool.Pool for a backend reached over the network.

It exists because a synchronous pool cannot do the one thing an exhausted pool is supposed to do: wait. In @moondb/pool.Pool nothing can return a connection while a caller is inside acquire, so a full pool fails immediately and acquire_timeout_ms only reaches the error message. Here the caller is a task in an event loop: other tasks keep running while it waits, so a full pool parks the caller on a permit and wakes it when a connection comes home. And it is what makes overflow and a real timeout meaningful.

The division of labour mirrors the sync pool: @moonpool keeps the accounting — live, idle, lent, each connection's birth time — and this type adds what only a driver can do (opening, probing, resetting, closing) plus the permit that makes an exhausted acquire wait.

let pool : @apool.AsyncPool[@postgres.PgConn] = @apool.AsyncPool::new(
() => @postgres.PgConn::connect("db", 5432, "app", "secret", "app"),
max_size=8,
overflow=2,
pre_ping=true,
acquire_timeout_ms=3000L,
reset=conn => conn.rollback() catch { _ => () },
)

let rows = pool.with_conn(conn => conn.query("SELECT 1", []))

Functions & methods 16

pub async fn[D : @moondb.AsyncDriver] AsyncPool::acquire(Self[D]) -> D raise @moondb.DbError

Take a connection, waiting for one if the pool is at capacity.

The wait is real: the calling task parks on the permit and other tasks in the loop keep running, so a connection released by another task wakes this one. With acquire_timeout_ms set, a wait longer than the budget raises QueryError.

An idle connection past max_lifetime_ms, or one that fails the AsyncDriver::ping probe under pre_ping, is closed and skipped rather than handed back — in both cases the slot is refilled, so a stale connection does not shrink the pool. Raises Closed if the pool has been closed, ConnectError if the ward holds back or refuses a new connection, and whatever make raised if opening a fresh connection fails.

Return the connection with release — or use with_conn, which does so on every path.

pub fn[D] AsyncPool::acquire_timeout_ms(Self[D]) -> Int64

How long an acquire may wait for a permit in milliseconds (0 = no limit).

pub fn[D] AsyncPool::capacity(Self[D]) -> Int32

The hard ceiling on concurrent checkouts: max_size + overflow.

pub async fn[D : @moondb.AsyncDriver] AsyncPool::close_all(Self[D]) -> Unit noraise

Close every idle connection and mark the pool closed. Connections still lent out are not cut off — each closes as it is released back — so this is the graceful half of a shutdown. After this, acquire raises Closed. Idempotent.

pub fn[D] AsyncPool::idle_count(Self[D]) -> Int32

How many connections are idle and reusable right now.

pub fn[D] AsyncPool::in_use_count(Self[D]) -> Int32

How many connections are lent out right now.

pub fn[D] AsyncPool::is_closed(Self[D]) -> Bool

Whether close_all has been called.

pub fn[D] AsyncPool::max_lifetime_ms(Self[D]) -> Int64

The configured connection lifetime in milliseconds (0 = unlimited).

pub fn[D] AsyncPool::max_size(Self[D]) -> Int32

The steady-state ceiling on open connections.

pub fn[D] AsyncPool::new(async () -> D raise @moondb.DbError, max_size? : Int32, overflow? : Int32, max_lifetime_ms? : Int64, pre_ping? : Bool, acquire_timeout_ms? : Int64, clock? : () -> Int64, ward? : @moonpool.Ward, reset? : async (D) -> Unit raise @moondb.DbError, rand? : () -> Double, cpu? : () -> Int32) -> Self[D] raise @moondb.DbError

Build a pool whose connections come from make, which runs inside the event loop and may await the handshake.

* max_size caps the steady-state number of open connections (default 10) and must be positive. * overflow is how many connections may be opened above max_size while every steady-state connection is lent; a connection that came from that allowance is closed on return rather than idled, so the pool settles back to max_size. * max_lifetime_ms retires a connection older than this on acquire (0, the default, means no age limit). Enforcing it requires a real clock. * pre_ping probes an idle connection with AsyncDriver::ping before handing it out, closing it and trying the next if the backend has dropped it. * acquire_timeout_ms is how long a caller may wait for a permit before failing (0, the default, means wait as long as it takes — the pool never gives up on its own). * reset runs on return before the connection becomes reusable: roll back a leftover transaction, clear session state. A reset that raises closes the connection instead of pooling it. * clock returns monotonic milliseconds and defaults to a constant 0, which disables age-based retirement; rand/cpu feed the ward's jitter and shedder.

pub fn[D] AsyncPool::open_count(Self[D]) -> Int32

How many connections the pool has open: lent plus idle.

pub fn[D] AsyncPool::overflow(Self[D]) -> Int32

How many connections may be opened above max_size while the pool is busy.

pub fn[D] AsyncPool::pre_ping(Self[D]) -> Bool

Whether the pre-ping probe is enabled.

pub async fn[D : @moondb.AsyncDriver] AsyncPool::release(Self[D], D) -> Unit noraise

Return a connection so a later acquire can reuse it, and wake one waiter.

The reset hook runs first; under overflow, a connection that pushed the open set above max_size is closed instead of idled so the pool settles back to its steady-state size. A connection returned to a closed pool is closed immediately, so no handle outlives close_all. Every path releases the permit exactly once.

pub async fn[D : @moondb.AsyncDriver] AsyncPool::try_acquire(Self[D]) -> D? raise @moondb.DbError

Take a connection only if the pool is not at capacity, returning None instead of waiting. This is the load-shedding counterpart of acquire: a caller that would rather refuse work than queue can pick its own answer. Health and lifetime eviction apply exactly as in acquire, and a genuine failure while opening a fresh connection still propagates.

pub async fn[D : @moondb.AsyncDriver, R] AsyncPool::with_conn(Self[D], async (D) -> R raise @moondb.DbError) -> R raise @moondb.DbError

Run f with a pooled connection and return that connection afterwards, whether f returns or raises. This is the leak-proof way to use the pool.

moonbitstack/moondb/blocking

blocking

Types 3

pub struct Blocking[T] {
  driver : T
}

A @moondb.Driver seen as an @moondb.AsyncDriver, so a backend whose work happens inside a C call can be handed to code written against the asynchronous seam — the checkpoint store of an agent framework, a retrieval index, a server that runs one event loop for every dependency.

The two traits are method-for-method identical, so the bridge is a delegation and nothing more: each async method calls its blocking twin and returns its result. There is no thread, no queue and no wake-up, because there is nothing to wait for — SQLite steps a sqlite3_stmt and returns.

What the bridge does not do is as important. The call runs on the task's own thread, so it occupies the event loop for as long as the driver takes: that is the right trade for a backend answering in microseconds out of the page cache, and the wrong one for a backend answering in milliseconds over a socket — a networked backend implements @moondb.AsyncDriver itself, where the wait is real and other tasks keep running through it. Nor does the bridge serialise: a driver that is not safe to use from two tasks at once is still not safe here, so give each task its own connection, or park them behind @moondb/apool.AsyncPool, which hands one connection to one caller at a time.

What it does keep is everything beyond the base contract: prepare hands back the driver's own server-side handle rather than a buffered re-run, query_stream keeps the driver's live cursor rather than materialising the result, and dialect and capabilities come from the wrapped driver, so a query layer sees the real backend underneath.

test "a blocking driver behind the async seam" {
let db = @blocking.Blocking::new(@moondb.MockDriver::new())
assert_eq(db.dialect(), @moondb.Dialect::Sqlite)
assert_true(db.capabilities().savepoints)
}
pub struct BlockingCursor {
  cursor : &@moondb.Cursor
}

A blocking driver's @moondb.Cursor behind the asynchronous seam.

The default async query_stream materialises the whole result and hands back an array cursor, which is exactly the memory bound streaming exists to avoid. This adapter pulls from the wrapped cursor instead, so a backend that steps a prepared statement one row at a time keeps doing so — and the drain discipline is the wrapped cursor's: it must be read to None or closed.

pub struct BlockingStmt {
  stmt : &@moondb.Stmt
}

A blocking driver's @moondb.Stmt behind the asynchronous seam.

The default async prepare would keep the SQL text and re-run the driver's execute/query on every call, which throws away the thing a prepared handle exists for. This adapter keeps the wrapped handle instead, so the SQLite sqlite3_stmt — compiled, planned and bound — is what each run reaches.

Functions & methods 25

pub async fn[T : @moondb.Driver] Blocking::begin(Self[T]) -> Unit raise @moondb.DbError

@moondb.AsyncDriver's begin, callable on the value directly.

pub fn[T : @moondb.Driver] Blocking::capabilities(Self[T]) -> @moondb.Capabilities

@moondb.AsyncDriver's capabilities, callable on the value directly.

pub async fn[T : @moondb.Driver] Blocking::close(Self[T]) -> Unit

@moondb.AsyncDriver's close, callable on the value directly.

pub async fn[T : @moondb.Driver] Blocking::commit(Self[T]) -> Unit raise @moondb.DbError

@moondb.AsyncDriver's commit, callable on the value directly.

pub fn[T : @moondb.Driver] Blocking::dialect(Self[T]) -> @moondb.Dialect

@moondb.AsyncDriver's dialect, callable on the value directly.

pub async fn[T : @moondb.Driver] Blocking::execute(Self[T], String, Array[@moondb.Value]) -> @moondb.ExecResult raise @moondb.DbError

@moondb.AsyncDriver's execute, callable on the value directly.

pub fn[T] Blocking::inner(Self[T]) -> T

The driver this adapter wraps, for the methods the contract does not carry — a backend's own connection controls, an index rebuild, a file handle.

pub fn[T] Blocking::new(T) -> Self[T]

driver, presented as an @moondb.AsyncDriver.

pub async fn[T : @moondb.Driver] Blocking::ping(Self[T]) -> Bool

@moondb.AsyncDriver's ping, callable on the value directly.

pub async fn[T : @moondb.Driver] Blocking::prepare(Self[T], String) -> &@moondb.AsyncStmt raise @moondb.DbError

@moondb.AsyncDriver's prepare, callable on the value directly.

pub async fn[T : @moondb.Driver] Blocking::query(Self[T], String, Array[@moondb.Value]) -> Array[@moondb.Row] raise @moondb.DbError

@moondb.AsyncDriver's query, callable on the value directly.

pub async fn[T : @moondb.Driver] Blocking::query_stream(Self[T], String, Array[@moondb.Value]) -> &@moondb.AsyncCursor raise @moondb.DbError

@moondb.AsyncDriver's query_stream, callable on the value directly.

pub async fn[T : @moondb.Driver] Blocking::rollback(Self[T]) -> Unit raise @moondb.DbError

@moondb.AsyncDriver's rollback, callable on the value directly.

pub async fn BlockingCursor::close(Self) -> Unit

@moondb.AsyncCursor's close, callable on the value directly.

pub fn BlockingCursor::new(&@moondb.Cursor) -> Self

cursor, presented as an @moondb.AsyncCursor.

pub async fn BlockingCursor::next(Self) -> @moondb.Row? raise @moondb.DbError

@moondb.AsyncCursor's next, callable on the value directly.

pub async fn BlockingStmt::bind(Self, Array[@moondb.Value]) -> Unit raise @moondb.DbError

@moondb.AsyncStmt's bind, callable on the value directly.

pub async fn BlockingStmt::bind_named(Self, Map[String, @moondb.Value]) -> Unit raise @moondb.DbError

@moondb.AsyncStmt's bind_named, callable on the value directly.

pub async fn BlockingStmt::close(Self) -> Unit

@moondb.AsyncStmt's close, callable on the value directly.

pub async fn BlockingStmt::execute(Self) -> @moondb.ExecResult raise @moondb.DbError

@moondb.AsyncStmt's execute, callable on the value directly.

pub fn BlockingStmt::new(&@moondb.Stmt) -> Self

stmt, presented as an @moondb.AsyncStmt.

pub fn BlockingStmt::params(Self) -> Array[String]

@moondb.AsyncStmt's params, callable on the value directly.

pub async fn BlockingStmt::query(Self) -> Array[@moondb.Row] raise @moondb.DbError

@moondb.AsyncStmt's query, callable on the value directly.

pub async fn BlockingStmt::query_stream(Self) -> &@moondb.AsyncCursor raise @moondb.DbError

@moondb.AsyncStmt's query_stream, callable on the value directly.

pub fn BlockingStmt::sql(Self) -> String

@moondb.AsyncStmt's sql, callable on the value directly.

moonbitstack/moondb/conformance

conformance

Types 3

pub(all) struct Case {
  name : String
  outcome : Outcome
}
pub(all) enum Outcome {
  Passed
  Failed(String)
  Skipped(String)
}
pub(all) struct Report {
  cases : Array[Case]
}

Functions & methods 10

pub async fn check(&@moondb.AsyncDriver, table? : String) -> Report

Run the conformance matrix against one live connection and report, case by case, which parts of the @moondb.AsyncDriver contract the backend honours.

## What it checks

The matrix is the contract's own text turned into cases: connect, bind and read back, prepare once and re-run, bracket a transaction, nest one through a savepoint, survive every @moondb.Value case the dialect has a column type for, and classify two failures the way the error model says they must be classified. Each case is one entry in the report, and a failure says what came back instead.

## What it does not check

It is a conformance suite, not a SQL-engine test: it says nothing about whether the backend's SQL is right, its planner fast, or its own extensions present. Nor does it check wire details — whether a driver offers a text or a binary result format is its own business, and both are expected to produce the same @moondb.Values.

## Using it

async test "the backend honours the contract" {
let conn = @moonpostgres.PgConn::connect(
"127.0.0.1", 5432, "postgres", "postgres", "test",
)
let report = @conformance.check(conn)
assert_eq(report.failures(), [])
}

A blocking driver goes through the bridge, and the suite then exercises the bridge as well:

async test "the embedded backend honours the contract" {
let db = @moonsqlite.SqliteDriver::open(":memory:")
let report = @conformance.check(@blocking.Blocking::new(db))
assert_eq(report.failures(), [])
}

The table it uses is created and dropped by the run, so the database needs no preparation beyond a connection allowed to create one. table names it when two runs share a database and would otherwise collide.

A case whose feature the driver reports as absent (capabilities().savepoints, for one) is recorded as skipped, with the reason, rather than quietly counted as a pass.

pub async fn[T : @moondb.Driver] check_blocking(T, table? : String) -> Report

The same matrix over a blocking @moondb.Driver, bridged to the asynchronous seam so the cases are written once. The bridge is part of what the run exercises: an embedded backend's query_stream and prepare reach the suite through it.

pub fn Report::failed(Self) -> Int32

How many cases it failed.

pub fn Report::failures(Self) -> Array[String]

The cases that failed, one line each: what was checked and what came back. A test asserting a clean run wants this to be empty, and a person reading a red build wants exactly this.

pub fn Report::is_clean(Self) -> Bool

Whether the backend honoured every applicable case. A skipped case does not make a report unclean — the backend said it could not, and the skip names why — but a failed one does.

pub fn Report::passed(Self) -> Int32

How many cases the backend passed.

pub fn Report::skipped(Self) -> Int32

How many cases did not apply.

pub fn Report::skips(Self) -> Array[String]

The skips, one line each, with the reason the backend gave.

pub fn Report::summary(Self) -> String

One line a build log can carry: 14 passed, 1 skipped, 0 failed.

pub fn Report::total(Self) -> Int32

How many cases ran, skipped ones included.

moonbitstack/moonsqlite

moonsqlite

Types 2

pub struct SqliteDriver {
  handle : Int64
  mut closed : Bool
}

A live connection to a SQLite database. Open it with SqliteDriver::open; it implements @moondb.Driver, so a moondb query layer can drive it directly. The opaque sqlite3* is carried as its pointer bits in handle.

pub struct SqliteStmt {
  driver : SqliteDriver
  st : Int64
  text : String
  slots : Array[String]
  cols : Array[String]
  mut closed : Bool
}

A statement SQLite prepares once and runs many times.

prepare compiles the SQL into a sqlite3_stmt and keeps it alive; bind resets the compiled program and binds a fresh set of values, so a loop over a thousand rows pays for parsing and planning once. close finalises the statement. A :name statement is rewritten to ? at prepare time and remembers the slot names, so bind_named works without the caller numbering anything.

Functions & methods 19

pub fn SqliteCursor::close(Self) -> Unit

@moondb.Cursor's close, callable on the value directly.

pub fn SqliteCursor::next(Self) -> @moondb.Row? raise @moondb.DbError

@moondb.Cursor's next, callable on the value directly.

pub fn SqliteDriver::begin(Self) -> Unit raise @moondb.DbError

@moondb.Driver's begin, callable on the value directly.

pub fn SqliteDriver::capabilities(Self) -> @moondb.Capabilities

@moondb.Driver's capabilities, callable on the value directly.

pub fn SqliteDriver::close(Self) -> Unit

@moondb.Driver's close, callable on the value directly.

pub fn SqliteDriver::commit(Self) -> Unit raise @moondb.DbError

@moondb.Driver's commit, callable on the value directly.

pub fn SqliteDriver::dialect(Self) -> @moondb.Dialect

@moondb.Driver's dialect, callable on the value directly.

pub fn SqliteDriver::exec_script(Self, String) -> Unit raise @moondb.DbError

Run a semicolon-separated script (e.g. a schema DDL block) in one call. Unlike execute, this uses SQLite's multi-statement exec path and binds no parameters. Raises a @moondb.Server error carrying SQLite's message and its result code on failure; DbError::kind gives the class.

pub fn SqliteDriver::execute(Self, String, Array[@moondb.Value]) -> @moondb.ExecResult raise @moondb.DbError

@moondb.Driver's execute, callable on the value directly.

pub fn SqliteDriver::is_closed(Self) -> Bool

Whether close has been called. Once closed, every statement raises @moondb.Closed rather than touching the freed sqlite3*.

pub fn SqliteDriver::open(String) -> Self raise @moondb.DbError

On js: pub async fn SqliteDriver::open(String) -> Self raise @moondb.DbError

Open (or create) the database at path. Use ":memory:" for a private in-memory database. Raises @moondb.ConnectError if the file cannot be opened.

pub fn SqliteDriver::ping(Self) -> Bool

@moondb.Driver's ping, callable on the value directly.

pub fn SqliteDriver::prepare(Self, String) -> &@moondb.Stmt raise @moondb.DbError

@moondb.Driver's prepare, callable on the value directly.

pub fn SqliteDriver::prepare_stmt(Self, String) -> SqliteStmt raise @moondb.DbError

Compile sql into a reusable statement. A statement using :name parameters is rewritten to SQLite's ? spelling first.

pub fn SqliteDriver::query(Self, String, Array[@moondb.Value]) -> Array[@moondb.Row] raise @moondb.DbError

@moondb.Driver's query, callable on the value directly.

pub fn SqliteDriver::query_stream(Self, String, Array[@moondb.Value]) -> &@moondb.Cursor raise @moondb.DbError

@moondb.Driver's query_stream, callable on the value directly.

pub fn SqliteDriver::rollback(Self) -> Unit raise @moondb.DbError

@moondb.Driver's rollback, callable on the value directly.

pub fn SqliteStmtCursor::close(Self) -> Unit

@moondb.Cursor's close, callable on the value directly.

pub fn SqliteStmtCursor::next(Self) -> @moondb.Row? raise @moondb.DbError

@moondb.Cursor's next, callable on the value directly.

moonbitstack/moonpostgres

moonpostgres

Types 8

pub struct PgConn {
  stream : PgWire
  host : String
  port : Int32
  mut closed : Bool
  mut streaming : Bool
  params : Map[String, String]
  mut pid : Int32
  mut secret : Int32
  statements : Map[String, String]
  described : Map[String, (Array[String], Array[Int32])]
  mut next_stmt : Int32
}

A live connection to a PostgreSQL backend, already past the startup handshake and sitting at ReadyForQuery. Runs the v3 frontend/backend protocol over a single @socket.Tcp. Not safe for concurrent use by multiple tasks: one statement must complete (drain to ReadyForQuery) before the next begins, as the wire protocol is a strict request/response pipeline per connection.

pub struct PgCopyIn {
  conn : PgConn
  mut done : Bool
  mut failed : Bool
}

A COPY … FROM STDIN in progress: write_chunk(#write_chunk) pushes pieces as they are produced and finish(#finish) closes the stream and reports what the server did with it.

The stream owns the connection until finish (or fail(#fail)); writing after either is a QueryError. Chunks are passed through as COPY data messages, so a caller can stream a file of any size without materialising it.

pub struct PgCopyOut {
  conn : PgConn
  mut done : Bool
  mut rows : Int64
}

A COPY … TO STDOUT in progress: the server sends the data in pieces and read_chunk(#read_chunk) hands them over one at a time, so a table or a query result larger than memory can be written straight to a file or a socket.

The stream owns the connection's single-response pipeline until it ends: read it to None (or cancel(#cancel) it) before the next statement. The chunks are the server's own framing — COPY text rows are newline-terminated, CSV rows likewise, binary rows are length-prefixed — so a caller writes them out verbatim.

pub struct PgCursor {
  conn : PgConn
  name : String
  batch : Int32
  mut exhausted : Bool
  mut closed : Bool
}

A server-side cursor: DECLARE … CURSOR opens it inside the current transaction and next_batch(#next_batch) pulls at most batch rows per round trip.

This is the cursor for a result too large to hold, and it is different from PgRowStream, which streams one statement's response: the server owns the position, so the connection is free between fetches, and the rows already read are never resent. It requires an open transaction — a cursor declared outside one is closed by the implicit commit at the end of the statement.

pub struct PgDriver {
  host : String
  port : Int32
  user : String
  password : String
  database : String
}

The parameters needed to reach a PostgreSQL backend — a connection descriptor, not a connection. connect(PgDriver::connect) opens the live PgConn, which is the thing that implements the driver seam.

It exists so a caller can carry connection settings around (a config value, a pool factory) and open a connection from them on demand, the way a DSN string works elsewhere.

pub struct PgRowStream {
  conn : PgConn
  mut col_names : Array[String]
  mut col_oids : Array[Int32]
  formats : Array[Int32]
  mut done : Bool
}

A forward-only cursor that reads a query's rows off the wire on demand instead of buffering them, the streaming counterpart to query, for results too large to materialise (asyncpg's cursor. It is bound to its PgConn, which is a single-statement pipeline: the stream must be drained (next returns None) or [closed](PgRowStream::close) before another statement runs on that connection.

pub struct PgStmt {
  conn : PgConn
  name : String
  key : String
  translated : String
  text : String
  slots : Array[String]
  mut params : Array[@moondb.Value]
  mut closed : Bool
  col_names : Array[String]
  col_oids : Array[Int32]
  formats : Array[Int32]
  binary : Bool
}

A statement PostgreSQL prepares once, on the server, and runs many times.

prepare sends Parse for the statement's text under a generated name, so the parse and the plan survive between calls; each execute/query sends only Bind/Describe/Execute with new values. The name comes from the connection's statement cache keyed by the SQL text, so preparing the same text twice reuses one server-side statement rather than allocating a second.

A :name statement is preprocessed here — rewritten to $1, $2, … and the slot names remembered — which is what makes bind_named work without the caller numbering the parameters. A statement written with ? is translated the same way execute translates it, and a bare ? that is the JSONB operator is left alone.

pub struct PgWire {
  // private fields
}

The byte channel a connection speaks the PostgreSQL v3 protocol over.

A real connection uses a socket — plain, or a TLS session layered on one. The protocol code above it, though, only ever needs three things: read exactly n bytes, write these bytes, close. Pulling that out of PgConn is what lets the state machine (statement sequencing, the streaming guard, error draining, COPY, notifications) be tested against a scripted peer with no machine and no server behind it: pipe(PgWire::pipe) hands it one end of an in-memory pipe pair and the test plays the server on the other.

It is an enum rather than a trait object because the two transports have different concrete types already (@tcp.Conn, @io.PipeRead + @io.PipeWrite) and a closed set keeps the dispatch a match with no allocation.

Functions & methods 101

pub fn build_bind(Array[@moondb.Value]) -> Bytes

Bind ('B'): bind params (all text format) to the unnamed statement, producing the unnamed portal, and request all result columns in text format.

pub fn build_bind_named(String, String, Array[@moondb.Value], formats? : Array[Int32]) -> Bytes

Bind ('B') params to a named (or unnamed) prepared statement, producing a named (or unnamed) portal. Parameters always go as text; formats says, one entry per result column, whether that column comes back in binary (1) or text (0). An empty formats is the protocol's own spelling of "all text".

pub fn build_cancel_request(Int32, Int32) -> Bytes

CancelRequest: Int32 length 16, Int32 request code 80877102, then the backend process id and secret key from BackendKeyData. Sent on a new connection to ask the server to cancel the query running on the old one.

pub fn build_close_statement(String) -> Bytes

Close ('C') for a named prepared statement: the server frees it and answers CloseComplete. A Sync follows so the message reaches a ready state on its own.

pub fn build_copy_data(Bytes) -> Bytes

CopyData ('d'): one chunk of COPY data.

pub fn build_copy_done() -> Bytes

CopyDone ('c'): the client has sent every COPY chunk.

pub fn build_copy_fail(String) -> Bytes

CopyFail ('f'): abort a COPY FROM STDIN with a message.

pub fn build_describe_portal() -> Bytes

Describe ('D') the unnamed portal, so the server sends a RowDescription before the DataRows (giving column names + type OIDs for decoding).

pub fn build_describe_statement(String) -> Bytes

Describe ('D') a prepared statement, which answers with its parameter types and a RowDescription. That description is what a binary result needs before the Bind: the format code of each result column is chosen from its type.

pub fn build_execute() -> Bytes

Execute ('E') the unnamed portal with no row limit (0 = all rows).

pub fn build_parse(String) -> Bytes

Parse ('P'): prepare the unnamed statement from sql (with $n placeholders). Zero declared parameter types — the server infers them.

pub fn build_parse_named(String, String) -> Bytes

Parse ('P') into a named prepared statement, so a later Bind can reuse it without re-parsing. An empty name is the unnamed statement.

pub fn build_password(String) -> Bytes

PasswordMessage ('p'): the auth response token (cleartext, or the md5… digest), as a C string.

pub fn build_query(String) -> Bytes

Simple Query ('Q'): one SQL string, no bound parameters.

pub fn build_sasl_initial(String, String) -> Bytes

SASLInitialResponse ('p'): the chosen mechanism name, then the length-prefixed client-first SCRAM message (PG protocol §55.2.1).

pub fn build_sasl_response(String) -> Bytes

SASLResponse ('p'): the raw client-final SCRAM message, no length prefix.

pub fn build_ssl_request() -> Bytes

SSLRequest: Int32 length 8, Int32 request code 80877103. Sent before the startup message; the server answers a single byte — S to begin a TLS handshake, N to continue in cleartext.

pub fn build_startup(String, String) -> Bytes

StartupMessage: protocol version 3.0 (196608) plus the user / database parameters and a client_encoding=UTF8 request, terminated by an empty key.

pub fn build_sync() -> Bytes

Sync ('S'): close the extended-query batch; the server replies ReadyForQuery.

pub fn build_terminate() -> Bytes

Terminate ('X'): ask the backend to close the connection.

pub fn concat_bytes(Bytes, Bytes) -> Bytes

Concatenate two byte strings. A small helper the MD5 auth path and message framing lean on; MoonBit's Bytes is immutable so this allocates once.

pub fn count_placeholders(String) -> Int32

The number of ? placeholders translate_placeholders would consume — the count of parameters a statement expects. Shares the same scanner discipline so ?? escapes and quoted/comment spans do not count.

pub fn decode_bytea(String) -> Bytes

Decode PostgreSQL bytea hex text (\xDEADBEEF) back to raw bytes. A value not in hex form (legacy escape format) rides back as its UTF-8 bytes.

pub fn decode_value(Int32, Bool, String) -> @moondb.Value

Decode a text-format result cell (raw, already UTF-8) tagged with its column oid into a Value. is_null marks a wire NULL (length -1), which decodes to Null regardless of type. Boolean, integer, floating-point, numeric, uuid, json/jsonb and the date/time OIDs decode to their typed cases; an OID the table does not know — an enum or a domain — rides back as Text, exactly the dialect-neutral contract moondb documents.

pub fn encode_param(@moondb.Value) -> Bytes?

Encode a bound parameter to its text-format bytes, or None for SQL NULL (which the Bind message sends as a length of -1). Binding is out-of-band — the value never touches the SQL string — so this is the injection-safe path.

* integers / doubles / bools render to their canonical PostgreSQL text literals (t/f for booleans); * Text passes through UTF-8; * Blob uses the bytea hex format (\x + lowercase hex), which the server accepts for a text-format bytea parameter.

pub const OID_BOOL : Int32 = 16

The pg_type OID of bool, as a column description reports it.

pub const OID_BPCHAR : Int32 = 1042

The pg_type OID of char(n) (blank-padded).

pub const OID_BYTEA : Int32 = 17

The pg_type OID of bytea, as a column description reports it.

pub const OID_DATE : Int32 = 1082

The pg_type OID of date.

pub const OID_FLOAT4 : Int32 = 700

The pg_type OID of float4, as a column description reports it.

pub const OID_FLOAT8 : Int32 = 701

The pg_type OID of float8, as a column description reports it.

pub const OID_INT2 : Int32 = 21

The pg_type OID of int2, as a column description reports it.

pub const OID_INT4 : Int32 = 23

The pg_type OID of int4, as a column description reports it.

pub const OID_INT8 : Int32 = 20

The pg_type OID of int8, as a column description reports it.

pub const OID_JSON : Int32 = 114

The pg_type OID of json.

pub const OID_JSONB : Int32 = 3802

The pg_type OID of jsonb.

pub const OID_NAME : Int32 = 19

The pg_type OID of name (an identifier column).

pub const OID_NUMERIC : Int32 = 1700

The pg_type OID of numeric.

pub const OID_TIME : Int32 = 1083

The pg_type OID of time.

pub const OID_TIMESTAMP : Int32 = 1114

The pg_type OID of timestamp.

pub const OID_TIMESTAMPTZ : Int32 = 1184

The pg_type OID of timestamptz.

pub const OID_TIMETZ : Int32 = 1266

The pg_type OID of timetz.

pub const OID_UUID : Int32 = 2950

The pg_type OID of uuid.

pub const OID_VARCHAR : Int32 = 1043

The pg_type OID of varchar.

pub fn parse_double(String) -> Double?

Parse a floating-point literal from PostgreSQL text (123.45, -1e10, Infinity, NaN), or None if malformed.

The three special tokens PostgreSQL emits are handled here; everything else goes to the language runtime's decimal-to-binary conversion, which rounds once and correctly. A hand-rolled mantissa × 10^exponent loop would not: multiplying by ten three hundred times drifts by several ULP, and 1e300 would come back as the double next to it — a value that then differs from the same column read in binary.

pub fn parse_int(String) -> Int32?

Parse a base-10 Int, or None if s is not a well-formed integer. Used to decode int2/int4 result text; a parse failure falls back to Text so a surprising server rendering never silently becomes a wrong number.

pub fn parse_int64(String) -> Int64?

Parse a base-10 Int64, or None on any non-digit (after an optional sign).

pub fn pg_md5_password(String, String, Bytes) -> String

"md5" + hex(md5(hex(md5(password + user)) + salt)).

It is what the server asks for and nothing else uses it, which is why the shape is stated rather than assembled from parts a caller might reorder.

pub fn PgConn::backend_pid(Self) -> Int32

The backend process id, from BackendKeyData.

pub async fn PgConn::begin(Self) -> Unit raise @moondb.DbError

Begin an explicit transaction (BEGIN).

pub async fn PgConn::cancel(Self) -> Unit raise @moondb.DbError

Cancel the query currently running on this connection. PostgreSQL requires the request on a separate connection: this opens one, sends CancelRequest with the backend key, and closes it.

pub fn PgConn::capabilities(Self) -> @moondb.Capabilities

@moondb.AsyncDriver's capabilities, callable on the value directly.

pub async fn PgConn::close(Self) -> Unit

Close the connection: best-effort Terminate, then close the socket. Idempotent — a second call is a no-op.

pub async fn PgConn::close_cursor(Self, String) -> Unit raise @moondb.DbError

Close a declared cursor.

pub async fn PgConn::commit(Self) -> Unit raise @moondb.DbError

Commit the current transaction (COMMIT).

pub async fn PgConn::connect(String, Int32, String, String, String, tls? : @tls.Client) -> Self raise @moondb.DbError

Open a connection and complete the startup handshake: send StartupMessage, satisfy the authentication request (trust/AuthenticationOk, cleartext, MD5, or SASL/SCRAM-SHA-256), and drain server parameters up to the first ReadyForQuery. host may be a hostname or a literal IP; port is typically 5432. With tls, the connection first sends PostgreSQL's SSLRequest and negotiates TLS (a server that answers N is an error, not a silent downgrade), then runs SCRAM with channel binding when the server offers -PLUS. Raises ConnectError on any transport, TLS, or handshake failure.

pub async fn PgConn::copy_in(Self, String, Bytes) -> @moondb.ExecResult raise @moondb.DbError

Run a COPY … FROM STDIN, streaming data to the server in bounded chunks, and report the command tag's affected-row count. The convenience wrapper over copy_in_stream(#copy_in_stream) for a payload that is already in memory.

pub async fn PgConn::copy_in_stream(Self, String) -> PgCopyIn raise @moondb.DbError

Start a COPY … FROM STDIN. Returns once the server has signalled it is ready for data.

pub async fn PgConn::copy_out(Self, String) -> Bytes raise @moondb.DbError

Run a COPY … TO STDOUT and return everything it produced. The convenience wrapper over copy_out_stream(#copy_out_stream) for a result that does fit in memory.

pub async fn PgConn::copy_out_stream(Self, String) -> PgCopyOut raise @moondb.DbError

Start a COPY … TO STDOUT and hand back the stream. Nothing is buffered: the first chunk is read by the first read_chunk.

pub async fn PgConn::cursor(Self, String, batch? : Int32) -> PgCursor raise @moondb.DbError

Declare a cursor over sql inside the current transaction. batch is how many rows one FETCH asks for, and is the knob that trades round trips for memory.

pub async fn PgConn::declare_cursor(Self, String, String) -> Unit raise @moondb.DbError

Declare a server-side cursor over sql inside the current transaction. The caller must be inside an explicit transaction (begin); the cursor is closed by close_cursor or at commit/rollback.

pub fn PgConn::dialect(Self) -> @moondb.Dialect

@moondb.AsyncDriver's dialect, callable on the value directly.

pub async fn PgConn::execute(Self, String, Array[@moondb.Value]) -> @moondb.ExecResult raise @moondb.DbError

Run a non-row statement (INSERT/UPDATE/DELETE/DDL) and report rows changed. last_insert_id is 0: the simple/extended protocols do not surface a generated key without a RETURNING clause (roadmap).

pub async fn PgConn::fetch_cursor(Self, String, Int32) -> Array[@moondb.Row] raise @moondb.DbError

Fetch up to count rows forward from a declared cursor.

pub fn PgConn::is_closed(Self) -> Bool

Whether close(PgConn::close) has been called. A closed connection refuses further statements with @moondb.Closed.

pub fn PgConn::is_streaming(Self) -> Bool

Whether a PgRowStream is still outstanding. While one is, this connection refuses new statements — the wire is a single-statement pipeline — until the stream is drained or [closed](PgRowStream::close).

pub async fn PgConn::listen(Self, String) -> Unit raise @moondb.DbError

Start listening on channel; next_notification then yields the payloads other sessions send with NOTIFY.

pub async fn PgConn::next_notification(Self) -> (String, String) raise @moondb.DbError

Block until a notification arrives on a channel this connection listens on, returning (channel, payload). Skips ParameterStatus and ReadyForQuery while waiting; an ErrorResponse raises.

pub async fn PgConn::notify(Self, String, payload? : String) -> Unit raise @moondb.DbError

Send a notification on channel, optionally carrying payload.

pub fn PgConn::parameter(Self, String) -> String?

A session parameter the server reported (server_version, client_encoding, TimeZone, …), or None if it has not been reported.

pub async fn PgConn::ping(Self) -> Bool

@moondb.AsyncDriver's ping, callable on the value directly.

pub async fn PgConn::prepare(Self, String) -> &@moondb.AsyncStmt raise @moondb.DbError

@moondb.AsyncDriver's prepare, callable on the value directly.

pub async fn PgConn::prepare_stmt(Self, String, binary? : Bool) -> PgStmt raise @moondb.DbError

Parse sql on the server (or reuse the cached statement with the same text) and return the handle.

binary fixes the result format for the handle's whole life: each column is described once at prepare time and then bound in whichever format reads back the same value — binary where that is exact, text otherwise. A handle therefore pays for the description once instead of on every call, unlike PgConn::query.

pub async fn PgConn::query(Self, String, Array[@moondb.Value], binary? : Bool) -> Array[@moondb.Row] raise @moondb.DbError

Run a row-returning statement and materialise every row. Uses the simple Query protocol when params is empty and the result is text, and the extended Parse/Bind/Execute protocol (with ?→$n translation and out-of-band text-format binding) otherwise.

With binary, each result column whose type has an exact binary decoder is fetched in the binary format and the rest stay text — see decode_binary. Text is the default, and a binary result costs one extra round trip per call: the column types have to be described before the Bind can name a format for each.

pub async fn PgConn::query_stream(Self, String, Array[@moondb.Value], binary? : Bool) -> PgRowStream raise @moondb.DbError

Send sql (with bound params) and return a streaming cursor over its rows. binary asks for the same per-type binary result query does.

pub async fn PgConn::rollback(Self) -> Unit raise @moondb.DbError

Roll back the current transaction (ROLLBACK).

pub async fn PgConn::unlisten(Self, String) -> Unit raise @moondb.DbError

Stop listening on channel (or every channel when channel is "*").

pub async fn PgCopyIn::fail(Self, String) -> Unit raise @moondb.DbError

Abort the load (CopyFail with reason) and read the server's error. The connection stays usable; the rows already sent are rolled back with the statement.

pub async fn PgCopyIn::finish(Self) -> @moondb.ExecResult raise @moondb.DbError

Close the stream (CopyDone) and read the server's verdict, reporting the command tag's affected-row count.

pub async fn PgCopyIn::write_chunk(Self, Bytes) -> Unit raise @moondb.DbError

Send one piece of COPY data. The chunk is framed here, so the caller only supplies the payload — rows in COPY's own format.

pub async fn PgCopyOut::cancel(Self) -> Unit raise @moondb.DbError

Abandon the stream: finish reading the server's response so the connection can run the next statement. Everything already read stays read.

pub async fn PgCopyOut::read_chunk(Self) -> Bytes? raise @moondb.DbError

The next chunk of copied data, or None once the server has finished. The command tag's row count is reported by rows(#rows) after the stream ends.

pub fn PgCopyOut::rows(Self) -> Int64

How many rows the finished COPY reported, or 0 while it is still running.

pub async fn PgCursor::close(Self) -> Unit raise @moondb.DbError

Close the cursor. Idempotent; a cursor also disappears with its transaction.

pub async fn PgCursor::next_batch(Self) -> Array[@moondb.Row] raise @moondb.DbError

The next batch of rows, in order. An empty array means the cursor is exhausted.

pub async fn PgDriver::connect(Self) -> PgConn raise @moondb.DbError

Open the connection this descriptor points at. Call it inside an event loop (async test / async fn main); the returned PgConn is an @moondb.AsyncDriver.

pub fn PgDriver::new(host~ : String, port? : Int32, user~ : String, password? : String, database~ : String) -> Self

Build a connection descriptor. Does not connect: PostgreSQL I/O is async, so the connection is opened by connect(PgDriver::connect) inside an event loop.

pub async fn PgRowStream::close(Self) -> Unit raise @moondb.DbError

Abandon the stream early, draining any unread response to ReadyForQuery so the connection can run the next statement. Idempotent once the stream is done.

pub async fn PgRowStream::next(Self) -> @moondb.Row? raise @moondb.DbError

The next row, or None once the result is exhausted. Reads messages until a DataRow (updating the column metadata from any RowDescription first), and on ReadyForQuery marks the stream done. An ErrorResponse is drained to ReadyForQuery before raising, so the connection stays usable.

pub fn PgWire::close(Self) -> Unit

Close the channel. Idempotent at the transport level, so a double close after a failed handshake is harmless.

pub fn PgWire::pipe(@io.PipeRead, @io.PipeWrite) -> Self

Wrap the client end of an in-memory pipe pair: from_server is where the peer's writes arrive, to_server is where this connection's writes go. Used by the offline protocol tests; a live connection never takes this path.

pub async fn PgWire::read_exactly(Self, Int32) -> Bytes

Read exactly n bytes, waiting for them if the peer has not sent them yet.

pub fn PgWire::tcp(@tcp.Conn) -> Self

Wrap a socket — plain, or the TLS session @tcp.Conn::client returned.

pub async fn PgWire::write(Self, Bytes) -> Unit

Write every byte of bytes, then return. The peer sees them in one piece.

pub fn saslprep(String) -> String

SASLprep (RFC 4013) on a password before SCRAM: map the non-ASCII space characters to U+0020, drop the "commonly mapped to nothing" code points, then NFKC-normalize. An ASCII password is returned unchanged, which is the common case; a non-ASCII one would otherwise derive a different key from the server's stored verifier.

pub fn scram_client_final(Bytes, String, String, gs2_header? : String, cbind? : Bytes) -> (String, Bytes) raise @moondb.DbError

Build the SCRAM client-final message and the expected server signature from the server's first message (RFC 5802). client_first_bare is n=…,r=clientnonce; server_first is r=nonce,s=salt,i=iters. Returns (client_final_message, server_signature) — the client sends the first and verifies the server's v= against the base64 of the second.

pub fn scram_client_proof(Bytes, Bytes) -> Bytes

The SCRAM ClientProof for the AuthMessage: ClientKey XOR HMAC(StoredKey, AuthMessage), where ClientKey = HMAC(SaltedPassword, "Client Key") and StoredKey = SHA256(ClientKey) (RFC 5802 §3).

pub fn scram_server_signature(Bytes, Bytes) -> Bytes

The SCRAM ServerSignature: HMAC(ServerKey, AuthMessage), ServerKey = HMAC(SaltedPassword, "Server Key"). The client verifies the server's v= against this to authenticate the server (RFC 5802 §3).

pub fn translate_placeholders(String) -> String

Rewrite moondb's dialect-neutral ? positional placeholders into PostgreSQL's numbered $1, $2, … form. moondb fixes the calling convention (an ordered params array) but leaves the placeholder spelling to the driver; PostgreSQL's extended-query protocol requires $n, so a query layer that emits ? for portability is translated here before Parse.

A ? is only a placeholder in SQL text — never inside a single-quoted string literal, a dollar-quoted string, a "-quoted identifier, or a -- / /* */ comment. Those spans are scanned through verbatim so a literal ? in data or a comment is left untouched and does not shift the parameter numbering. A literal ? an application genuinely needs in output can be written ??, which collapses to a single ? (mirroring JDBC-style escaping).

pub fn utf8_decode(Bytes) -> String

Decode UTF-8 bytes to a String. PostgreSQL text values, column names, and error fields all arrive UTF-8 (the startup message negotiates client_encoding). Decodes the ASCII fast path directly and multi-byte sequences by code point; malformed input yields U+FFFD rather than raising, matching a lenient text codec.

moonbitstack/moonmysql

moonmysql

Types 8

pub(all) struct ColumnDef {
  name : String
  column_type : Int32
  charset : Int32
  flags : Int32
}
pub struct Handshake {
  server_version : String
  server_kind : ServerKind
  connection_id : Int32
  salt : Bytes
  capability : Int32
  mariadb_capability : Int32
  charset : Int32
  status : Int32
  auth_plugin : String
}

The server's initial Handshake packet (protocol version 10), decoded down to the fields the client needs: the 20-byte auth salt, the negotiated capabilities, and the auth-plugin name that selects the scramble.

pub(all) suberror MysqlError {
  ProtocolError(String)
  ServerError(Int32, String, String)
  UnsupportedError(String)
}
pub(all) struct OkPacket {
  affected_rows : Int64
  last_insert_id : Int64
  status : Int32
  warnings : Int32
}
pub struct PacketReader {
  data : Bytes
  mut pos : Int32
}

A cursor over one decoded MySQL packet payload. Every field type the protocol uses — fixed-width little-endian integers, length-encoded integers and strings, NUL-terminated strings, and raw byte runs — is read through this, advancing a position and raising MysqlError::ProtocolError on any short read. It holds no socket: the transport reads a full payload into Bytes first, then parses it here, which is what lets the whole codec be tested off any backend.

pub(all) enum QuoteMode {
  Backslash
  DoubleQuote
}
pub(all) enum ServerKind {
  MySQL
  MariaDB
}
pub(all) struct StmtOk {
  id : Int32
  num_columns : Int32
  num_params : Int32
}

The server's reply to COM_STMT_PREPARE: the statement handle and how many parameters and result columns the statement has.

Functions & methods 67

pub fn bind_params(String, Array[@moondb.Value], mode? : QuoteMode) -> Bytes raise MysqlError

Substitute the ordered params for the ? placeholders in sql, producing the UTF-8 query bytes a COM_QUERY carries.

The text protocol has no out-of-band parameter binding — that arrives with prepared statements (COM_STMT_PREPARE, binary protocol) in a later round — so values are rendered as escaped literals here. The scanner tracks single-, double-, and backtick-quoted spans (honouring backslash escapes) so a literal ? inside a string or identifier is never mistaken for a placeholder, and it raises when the placeholder and parameter counts disagree.

pub fn build_handshake_response(Handshake, String, Bytes, String, ssl? : Bool, charset? : Int32) -> Bytes raise MysqlError

Build the client's HandshakeResponse41 payload for user/password/database.

mysql_native_password and caching_sha2_password (MySQL 8's default, fast-path scramble here and full auth driven in MysqlConn::connect) are both handled directly. For any other plugin the client advertises mysql_native_password and sends a native token, so a native-capable account authenticates and otherwise the server drives an AuthSwitchRequest the connection layer answers (native or caching_sha2) or rejects. Only a server that requires a non-native plugin and does not offer pluggable auth is rejected here. client_ed25519 is answered by the connection layer when MariaDB switches to it.

pub fn build_ssl_request(String, charset? : Int32) -> Bytes

SSLRequest: Int32 length 8, Int32 request code 80877103. Sent before the startup message; the server answers a single byte — S to begin a TLS handshake, N to continue in cleartext.

pub fn build_stmt_close(Int32) -> Bytes

COM_STMT_CLOSE (0x19): the statement id; the server answers nothing.

pub fn build_stmt_execute(Int32, Array[@moondb.Value], streamed? : Array[Int32]) -> Bytes

COM_STMT_EXECUTE (0x17) for statement id, binding params out-of-band. Every non-NULL parameter is declared as VAR_STRING (or BLOB for bytes) and sent length-encoded; the server coerces it to the column type. The NULL bitmap uses bit i for parameter i, LSB first.

streamed names the parameters whose value was already sent with COM_STMT_SEND_LONG_DATA: their type is still declared here — the server needs to know how to read the buffered bytes — but their value is not repeated.

pub fn build_stmt_prepare(String) -> Bytes

COM_STMT_PREPARE (0x16): the SQL text follows the command byte.

pub fn build_stmt_send_long_data(Int32, Int32, Bytes) -> Bytes

COM_STMT_SEND_LONG_DATA (0x18): statement id, parameter index, then one slice of that parameter's value. The server appends slices in the order they arrive, answers nothing at all, and forgets the accumulated value once the statement is executed or reset — so the pieces must be sent immediately before the execute that binds them.

pub fn build_text_rows(Array[ColumnDef], Array[Bytes]) -> Array[@moondb.Row] raise MysqlError

Build the materialised rows of a text-protocol result set from its column definitions and the raw row packets. This is the pure heart of query: the socket transport collects columns and row_payloads, and every cell decode happens here, off any backend.

pub fn bytes_to_string(Bytes) -> String

Bytes → String, lossily (server version, error text, text-protocol cells).

pub fn caching_sha2_full_auth_token(Bytes, Bytes, String, Bytes) -> Bytes raise MysqlError

The caching_sha2_password full-auth token: the NUL-terminated password XORed with the nonce (cycled), then RSA-OAEP encrypted under the server's public key.

seed is the 20 random octets OAEP masks with. MySQL's own client draws them from OpenSSL; here they are a parameter, because this package has no entropy source and a caller that reuses a seed should have to write that down.

pub fn caching_sha2_scramble(Bytes, Bytes) -> Bytes

The caching_sha2_password fast-auth scramble: SHA256(pw) XOR SHA256( SHA256(SHA256(pw)) ‖ nonce ), 32 bytes. An empty password sends an empty token.

pub let client_connect_with_db : Int32

The CLIENT_CONNECT_WITH_DB capability flag of the connection handshake.

pub let client_long_flag : Int32

The CLIENT_LONG_FLAG capability flag of the connection handshake.

pub let client_long_password : Int32

The CLIENT_LONG_PASSWORD capability flag of the connection handshake.

pub let client_multi_results : Int32

CLIENT_MULTI_RESULTS: one statement may answer with more than one result set — what a stored procedure's CALL needs. Without it the server rejects a procedure that returns rows (ER_SP_BADSELECT).

pub let client_multi_statements : Int32

CLIENT_MULTI_STATEMENTS: one COM_QUERY may carry several ;-separated statements.

pub let client_plugin_auth : Int32

The CLIENT_PLUGIN_AUTH capability flag of the connection handshake.

pub let client_protocol_41 : Int32

The CLIENT_PROTOCOL_41 capability flag of the connection handshake.

pub let client_secure_connection : Int32

The CLIENT_SECURE_CONNECTION capability flag of the connection handshake.

pub let client_ssl : Int32

The CLIENT_SSL capability flag of the connection handshake: the client will begin a TLS handshake after an SSLRequest packet.

pub let client_transactions : Int32

The CLIENT_TRANSACTIONS capability flag of the connection handshake.

pub fn concat_bytes(Bytes, Bytes) -> Bytes

Concatenate two byte strings. A small helper the MD5 auth path and message framing lean on; MoonBit's Bytes is immutable so this allocates once.

pub fn decode_binary_row(Bytes, Array[ColumnDef]) -> @moondb.Row raise MysqlError

Decode one binary-protocol result row against columns into a @moondb.Row. The row header is 0x00, then a NULL bitmap offset by two bits, then each non-NULL value in the column's binary type.

pub fn decode_text_value(Bytes?, ColumnDef) -> @moondb.Value raise MysqlError

Map one text-protocol cell (None = the 0xFB NULL sentinel) onto a @moondb.Value using the column's declared type. Integers narrow to Int except BIGINT, which keeps 64 bits; FLOAT/DOUBLE become Double; binary-collation strings become Blob; everything else (VARCHAR, TEXT, DECIMAL, temporal types as ISO text, JSON) becomes Text.

pub fn eof_status(Bytes) -> Int32

The status-flags word of an EOF packet (0xFE, warnings LE16, status LE16), or 0 for a packet that is not a protocol-41 EOF.

pub fn has_more_results(Bytes) -> Bool

Whether an EOF packet's status word says another result set follows.

pub fn is_auth_switch_request(Bytes) -> Bool

Whether payload is an AuthSwitchRequest (0xFE lead byte with a body — the length is what tells it apart from a 5-byte EOF, exactly as is_eof_packet keys off < 9).

pub fn is_eof_packet(Bytes) -> Bool

Whether payload is an EOF packet (first byte 0xFE, fewer than 9 bytes — which is what tells it apart from a row whose first cell is an 8-byte length-encoded value).

pub fn is_err_packet(Bytes) -> Bool

Whether payload is an ERR packet (first byte 0xFF).

pub fn is_ok_packet(Bytes) -> Bool

Whether payload is an OK packet (first byte 0x00, at least 7 bytes).

pub let long_data_chunk : Int32

How much of one parameter a COM_STMT_SEND_LONG_DATA packet carries, and the length at which a parameter stops travelling inside the execute packet and is streamed this way instead.

A packet is capped by the server's max_allowed_packet (64 MiB on MySQL 8 by default, 16 MiB on MariaDB), and the execute packet has to hold every parameter at once, so a value near that cap used to be un-sendable however it was encoded. One mebibyte is a sixteenth of the smaller default, which leaves room for the rest of the packet whatever the server is set to.

pub fn long_data_params(Array[@moondb.Value]) -> Array[Int32]

The parameter indexes, ascending, whose value is streamed rather than inlined. build_stmt_execute takes this list so it can leave their value bytes out.

pub fn long_data_slices(Array[@moondb.Value], Int32) -> Array[Bytes]

The slices of parameter index to send with COM_STMT_SEND_LONG_DATA, in order. Empty when the value fits in the execute packet, which is the case for everything shorter than long_data_chunk and for NULL.

pub let mariadb_client_cache_metadata : Int32

MariaDB's MARIADB_CLIENT_CACHE_METADATA capability flag of the connection handshake.

pub let mariadb_client_com_multi : Int32

MariaDB's MARIADB_CLIENT_COM_MULTI capability flag of the connection handshake.

pub let mariadb_client_extended_metadata : Int32

MariaDB's MARIADB_CLIENT_EXTENDED_METADATA capability flag of the connection handshake.

pub let mariadb_client_progress : Int32

MariaDB's MARIADB_CLIENT_PROGRESS capability flag of the connection handshake.

pub let mariadb_client_stmt_bulk_operations : Int32

MariaDB's MARIADB_CLIENT_STMT_BULK_OPERATIONS capability flag of the connection handshake.

pub fn mariadb_ed25519_response(Bytes, Bytes) -> Bytes

The MariaDB client_ed25519 response: the 64-byte Ed25519 signature of the server's 32-byte challenge, keyed by the password.

MariaDB keys the signature with the password itself, where RFC 8032 would expand a 32-byte seed — the derivation PrivateKey::of_secret exists for. The plugin signs unconditionally, so an empty password expands to SHA-512("") and still yields a valid signature.

pub fn MysqlError::to_string(Self) -> String

A one-line rendering, e.g. ServerError(1146, 42S02): Table 'test.t' doesn't exist.

pub fn native_password_scramble(Bytes, Bytes) -> Bytes

The mysql_native_password challenge response: SHA1(password) XOR SHA1(salt ++ SHA1(SHA1(password))), 20 bytes. An empty password produces an empty response (the server accepts a zero-length token).

pub fn PacketReader::at_end(Self) -> Bool

Whether the cursor has consumed the whole payload.

pub fn PacketReader::bytes(Self, Int32) -> Bytes raise MysqlError

Read n raw bytes.

pub fn PacketReader::lenenc_bytes(Self) -> Bytes? raise MysqlError

Read a length-encoded string (the string<lenenc> type), returning None for the 0xFB NULL sentinel that appears in text-protocol rows.

pub fn PacketReader::lenenc_uint(Self) -> Int64 raise MysqlError

Read a length-encoded unsigned integer (the int<lenenc> type). The 0xFB NULL sentinel and 0xFF are rejected here — NULL only has meaning inside a row, which lenenc_bytes handles.

pub fn PacketReader::new(Bytes) -> Self

Wrap a decoded packet payload for reading from the start.

pub fn PacketReader::peek(Self) -> Int32

Peek the next byte without advancing; -1 at end of payload.

pub fn PacketReader::remaining(Self) -> Int32

Bytes not yet consumed.

pub fn PacketReader::rest(Self) -> Bytes

Read everything left in the payload (an EOF-terminated string field).

pub fn PacketReader::skip(Self, Int32) -> Unit raise MysqlError

Skip n bytes.

pub fn PacketReader::string_nul(Self) -> Bytes raise MysqlError

Read a NUL-terminated string, consuming the terminator.

pub fn PacketReader::u8(Self) -> Int32 raise MysqlError

Read one byte as an Int in 0..=255.

pub fn PacketReader::uint_le(Self, Int32) -> Int64 raise MysqlError

Read an n-byte little-endian unsigned integer.

pub fn parse_auth_switch_request(Bytes) -> (String, Bytes) raise MysqlError

Decode an AuthSwitchRequest into the plugin name the server wants and the fresh 20-byte auth salt for it. The trailing NUL that follows the scramble is dropped so the salt feeds native_password_scramble directly.

pub fn parse_column_def(Bytes) -> ColumnDef raise MysqlError

Decode a column-definition packet (protocol 41). Only the fields that steer text decoding are kept; catalog/schema/table names and the length/decimals fields are read past.

pub fn parse_ed25519_challenge(Bytes) -> Bytes raise MysqlError

The 32-byte nonce from a client_ed25519 AuthSwitchRequest. MariaDB sends exactly NONCE_BYTES (= 32) with no NUL terminator and the client signs all of them, so — unlike the native scramble — the full 32 bytes are read raw rather than through the NUL-stripping parse_auth_switch_request.

pub fn parse_err(Bytes) -> MysqlError raise MysqlError

Decode a full ERR packet (including its 0xFF marker) into the MysqlError::ServerError it represents.

pub fn parse_handshake(Bytes) -> Handshake raise MysqlError

Parse a protocol-10 initial Handshake payload. An ERR packet in its place (0xFF, e.g. "Host is blocked" / "Too many connections") is surfaced as a MysqlError::ServerError.

pub fn parse_ok(Bytes) -> OkPacket raise MysqlError

Decode an OK packet (including its 0x00 marker).

pub fn parse_rsa_public_key(String) -> @rsa.PublicKey raise MysqlError

The RSA public key inside the PEM SubjectPublicKeyInfo the server sends.

pub fn parse_server_version(String) -> (ServerKind, String)

Classify a raw handshake version string and recover the real version.

MariaDB 10+ prefixes its version with the fake 5.5.5- sentinel (MySQL 5.5.5 was never released), so pre-10 clients that gate on the leading 5. still connect. Strip it to get the true version — e.g. 5.5.5-11.4.2-MariaDB-ubu2404 → 11.4.2-MariaDB-ubu2404 as ServerKind::MariaDB; a plain 8.0.35 is returned unchanged as ServerKind::MySQL.

pub fn parse_stmt_prepare_ok(Bytes) -> StmtOk raise MysqlError

Decode the COM_STMT_PREPARE_OK packet: 0x00, statement id (LE32), column count (LE16), parameter count (LE16), one reserved byte, warning count (LE16).

pub fn put_lenenc_bytes(@buffer.Buffer, Bytes) -> Unit

Append a length-encoded string (its lenenc length prefix, then the bytes).

pub fn put_lenenc_uint(@buffer.Buffer, Int64) -> Unit

Append a length-encoded unsigned integer.

pub fn put_string_nul(@buffer.Buffer, Bytes) -> Unit

Append raw bytes followed by a NUL terminator.

pub fn put_uint_le(@buffer.Buffer, Int64, Int32) -> Unit

Append an n-byte little-endian unsigned integer.

pub let server_more_results_exists : Int32

SERVER_MORE_RESULTS_EXISTS: the status flag that says another result set follows the one just terminated.

moonbitstack/moonmysql/client

client

Types 4

pub struct MysqlConn {
  stream : @tcp.Conn
  mut seq : Int32
  mut closed : Bool
  server_kind : @moonmysql.ServerKind
  mut streaming : Bool
  mut quote_mode : @moonmysql.QuoteMode
}

A live MySQL connection over a raw TCP socket, speaking the wire protocol in pure MoonBit. This is the asynchronous, full-fidelity driver: it owns the socket for its whole lifetime, so begin/commit/rollback bracket a real server-side transaction. The synchronous @moondb.Driver adapter (MysqlDriver) is layered on top for the moondb contract.

Framing is the classic 3-byte little-endian length + 1-byte sequence id header; the transport transparently reassembles the 0xFFFFFF continuation frames a large payload is split into.

pub struct MysqlDriver {
  host : String
  port : Int32
  user : String
  password : String
  database : String
  tls : @tls.Client?
}

A synchronous @moondb.Driver over MySQL — the adapter that lets a moondb query layer (moonorm) talk to MySQL through the same contract as the SQLite and Postgres drivers.

## The async wall, and what it means here

moonbitlang/async sockets are asynchronous, but @moondb.Driver's methods are synchronous, and MoonBit forbids calling an async function from a non-async one. The bridge is @async.run_async_main, which spins an event loop, runs an async body to completion, and returns — the same hook the toolchain uses to enter async fn main.

A socket does **not** survive across two such event loops (each tears its file descriptors down; verified empirically), so every execute/query opens a fresh connection, authenticates, runs its one statement, and closes. That is correct and efficient enough for autocommit work — which is exactly what the moondb roundtrip is — but it means a transaction cannot be held open across separate Driver calls. begin/commit/rollback therefore raise a clear error directing callers to the asynchronous MysqlConn API, which owns its socket for its whole lifetime and brackets real transactions. This is a language constraint, not a shortcut, and it is documented in the README.

pub struct MysqlRowStream {
  conn : MysqlConn
  columns : Array[@moonmysql.ColumnDef]
  mut pending : Bytes?
  mut done : Bool
}

A forward-only cursor that reads a query's rows off the wire on demand instead of buffering them, the streaming counterpart to query, for results too large to materialise at once (mysql_use_result. Bound to its MysqlConn, a single-command pipeline: the stream must be drained (next returns None) or [closed](MysqlRowStream::close) before another statement runs on that connection.

pub struct MysqlStmt {
  conn : MysqlConn
  id : Int32
  text : String
  slots : Array[String]
  mut params : Array[@moondb.Value]
  mut closed : Bool
}

A statement MySQL prepares once and runs many times.

prepare sends COM_STMT_PREPARE and keeps the server-side handle; every execute/query sends only the statement id and the values, which is both cheaper than re-parsing and the path that binds binary payloads without hex expansion. close sends COM_STMT_CLOSE, after which the handle raises Closed.

A :name statement is rewritten to MySQL's ? spelling at prepare time and the slot names are kept, so bind_named works without the caller counting anything. A handle belongs to the connection that prepared it: one wire, one statement at a time.

Functions & methods 29

pub async fn MysqlConn::begin(Self) -> Unit

Begin a server-side transaction.

pub fn MysqlConn::capabilities(Self) -> @moondb.Capabilities

@moondb.AsyncDriver's capabilities, callable on the value directly.

pub fn MysqlConn::close(Self) -> Unit

Close the socket (best-effort, idempotent).

pub async fn MysqlConn::commit(Self) -> Unit

Commit the current transaction.

pub async fn MysqlConn::connect(String, Int32, String, String, String, tls? : @tls.Client, charset? : Int32) -> Self

Open a connection to host:port and complete the auth handshake against user/password, selecting database (empty for none). Works against both MySQL and MariaDB (the reported dialect is on server_kind).

mysql_native_password and caching_sha2_password (MySQL 8's default, including the RSA full-auth path on a cache miss) are handled directly, whether the server drives them from its default or via an AuthSwitchRequest. With tls, the connection sends an SSLRequest after the greeting and runs the whole handshake over TLS; the caching_sha2 full-auth step then sends the password in cleartext under TLS instead of RSA-encrypting it. A switch to any other plugin (e.g. client_ed25519, on the README roadmap) raises @moonmysql.MysqlError::UnsupportedError.

pub fn MysqlConn::dialect(Self) -> @moondb.Dialect

@moondb.AsyncDriver's dialect, callable on the value directly.

pub async fn MysqlConn::execute(Self, String, Array[@moondb.Value]) -> @moondb.ExecResult

Run a non-row statement and report the rows affected and last insert id. With bound params it uses a prepared statement (binary protocol, out-of-band binding); without, the text protocol.

pub async fn MysqlConn::ping(Self) -> Bool

@moondb.AsyncDriver's ping, callable on the value directly.

pub async fn MysqlConn::prepare(Self, String) -> &@moondb.AsyncStmt raise @moondb.DbError

@moondb.AsyncDriver's prepare, callable on the value directly.

pub async fn MysqlConn::prepare_stmt(Self, String) -> MysqlStmt

Ask the server to prepare sql and return the handle.

pub async fn MysqlConn::query(Self, String, Array[@moondb.Value]) -> Array[@moondb.Row]

Run a row-returning statement and materialise every @moondb.Row. With bound params it uses a prepared statement (binary protocol); without, the text protocol.

pub async fn MysqlConn::query_multi(Self, String, Array[@moondb.Value]) -> Array[Array[@moondb.Row]]

Run a statement that answers with more than one result set and return every set, in the order the server sent them — a stored procedure's CALL, or a multi-statement packet. Values bind only on the prepared path, which has no multi-result protocol of its own, so params must be empty here.

pub async fn MysqlConn::query_stream(Self, String, Array[@moondb.Value]) -> MysqlRowStream

Send a row-returning statement and return a streaming cursor over its rows. The column definitions and their terminating EOF are consumed up front; the rows themselves are read one at a time by next(MysqlRowStream::next).

pub async fn MysqlConn::rollback(Self) -> Unit

Roll back the current transaction.

pub fn MysqlConn::server_kind(Self) -> @moonmysql.ServerKind

The dialect (MySQL or MariaDB) reported by the server's handshake.

pub fn MysqlDriver::begin(Self) -> Unit raise @moondb.DbError

@moondb.Driver's begin, callable on the value directly.

pub fn MysqlDriver::capabilities(Self) -> @moondb.Capabilities

@moondb.Driver's capabilities, callable on the value directly.

pub fn MysqlDriver::close(Self) -> Unit

@moondb.Driver's close, callable on the value directly.

pub fn MysqlDriver::commit(Self) -> Unit raise @moondb.DbError

@moondb.Driver's commit, callable on the value directly.

pub fn MysqlDriver::dialect(Self) -> @moondb.Dialect

@moondb.Driver's dialect, callable on the value directly.

pub fn MysqlDriver::execute(Self, String, Array[@moondb.Value]) -> @moondb.ExecResult raise @moondb.DbError

@moondb.Driver's execute, callable on the value directly.

pub fn MysqlDriver::new(user~ : String, password? : String, database? : String, host? : String, port? : Int32, tls? : @tls.Client) -> Self

Configure a driver. host defaults to loopback, port to 3306; password and database may be empty. tls turns on TLS for the connection.

pub fn MysqlDriver::ping(Self) -> Bool

@moondb.Driver's ping, callable on the value directly.

pub fn MysqlDriver::prepare(Self, String) -> &@moondb.Stmt raise @moondb.DbError

@moondb.Driver's prepare, callable on the value directly.

pub fn MysqlDriver::query(Self, String, Array[@moondb.Value]) -> Array[@moondb.Row] raise @moondb.DbError

@moondb.Driver's query, callable on the value directly.

pub fn MysqlDriver::query_stream(Self, String, Array[@moondb.Value]) -> &@moondb.Cursor raise @moondb.DbError

@moondb.Driver's query_stream, callable on the value directly.

pub fn MysqlDriver::rollback(Self) -> Unit raise @moondb.DbError

@moondb.Driver's rollback, callable on the value directly.

pub async fn MysqlRowStream::close(Self) -> Unit

Abandon the stream early, draining any unread rows to the terminating EOF so the connection can run the next statement. Idempotent once the stream is done.

pub async fn MysqlRowStream::next(Self) -> @moondb.Row?

The next row, or None at the EOF that ends the result set. An ErrorResponse mid-stream is raised.

moonbitstack/moonchdb

moonchdb

Types 1

pub struct ClickHouseConn {
  host : String
  port : Int32
  user : String
  password : String
  database : String
  mut closed : Bool
  mut in_transaction : Bool
}

A ClickHouse server, reached over its HTTP interface.

Every statement is one HTTP request on its own connection, which is what the interface itself is: stateless, one query in the body, its parameters in the URL. Nothing is held open between calls, so a connection can be shared by any number of tasks without a lock, and a close is bookkeeping rather than a round trip.

The driver asks for RowBinaryWithNamesAndTypes, so a column value crosses the wire in its own bytes rather than as the text the server would print, and reads the count of written rows back from the X-ClickHouse-Summary header.

let db = @clickhouse.ClickHouseConn::connect("127.0.0.1", user="default", database="default")
db.execute("CREATE TABLE hero (id Int32, name String) ENGINE = Memory", []) |> ignore
db.execute("INSERT INTO hero VALUES (?, ?)", [Int(1), Text("Nova")]) |> ignore
let rows = db.query("SELECT name FROM hero WHERE id = ?", [Int(1)])

Functions & methods 14

pub fn ClickHouseConn::begin(Self) -> Unit raise @moondb.DbError

ClickHouse has no transactions: begin, commit and rollback are recorded so a caller can see the state, but no statement is grouped with another.

pub fn ClickHouseConn::capabilities(Self) -> @moondb.Capabilities

@moondb.AsyncDriver's capabilities, callable on the value directly.

pub fn ClickHouseConn::close(Self) -> Unit

Close the connection. The interface is stateless, so this only stops later calls with Closed.

pub fn ClickHouseConn::commit(Self) -> Unit raise @moondb.DbError
pub async fn ClickHouseConn::connect(String, port? : Int32, user? : String, password? : String, database? : String) -> Self raise @moondb.DbError

Open a connection to host:port and prove the credentials with one SELECT 1. The HTTP interface is request-scoped, so "opening" is that round trip and nothing is retained.

pub fn ClickHouseConn::database(Self) -> String

The database this connection addresses.

pub fn ClickHouseConn::dialect(Self) -> @moondb.Dialect

@moondb.AsyncDriver's dialect, callable on the value directly.

pub async fn ClickHouseConn::execute(Self, String, Array[@moondb.Value]) -> @moondb.ExecResult raise @moondb.DbError

Run a statement and discard its rows, reporting how many it wrote. ClickHouse's summary header carries the counts, so rows_affected is the server's number and not a guess.

pub fn ClickHouseConn::in_transaction(Self) -> Bool

Whether a begin is outstanding. Nothing is rolled back by it on this engine.

pub async fn ClickHouseConn::ping(Self) -> Bool

Whether the server answers SELECT 1, reported as a verdict rather than raised.

pub async fn ClickHouseConn::prepare(Self, String) -> &@moondb.AsyncStmt raise @moondb.DbError

@moondb.AsyncDriver's prepare, callable on the value directly.

pub async fn ClickHouseConn::query(Self, String, Array[@moondb.Value]) -> Array[@moondb.Row] raise @moondb.DbError

Run a row-returning statement and materialise the result.

pub async fn ClickHouseConn::query_stream(Self, String, Array[@moondb.Value]) -> &@moondb.AsyncCursor raise @moondb.DbError

@moondb.AsyncDriver's query_stream, callable on the value directly.

pub fn ClickHouseConn::rollback(Self) -> Unit raise @moondb.DbError

moonbitstack/moonckdb

moonckdb

Types 1

pub(all) enum Server {
  Postgres
  Cockroach
}

Functions & methods 6

pub fn capabilities_for(Server) -> @moondb.Capabilities

What this server can do, as opposed to what the driver implements. @moondb.Capabilities::for_dialect describes the PostgreSQL implementation; CockroachDB is not PostgreSQL, and the difference that matters to a caller is notifications: LISTEN and NOTIFY do not exist there, so a query layer that plans to wait for a notification has to poll instead.

The rest of the PostgreSQL surface does hold on CockroachDB — cursors, savepoints, COPY in both directions, RETURNING — which is why this clears one flag and not a row of them.

pub async fn connect(String, Int32, String, String, String, tls? : @tls.Client) -> @moonpostgres.PgConn raise @moondb.DbError

Connect the way @moonpostgres.PgConn::connect does — the protocol is the same one, and there is nothing to translate.

pub fn Server::from_version(String) -> Self

The server a SELECT version() answer names. CockroachDB reports itself as CockroachDB CCL v24.3.0 (x86_64-…, built …), PostgreSQL as PostgreSQL 16.4 …, and a compatibility layer that borrows the string is still that layer.

pub fn Server::name(Self) -> String
pub async fn server_of(&@moondb.AsyncDriver) -> Server raise @moondb.DbError

Which server is behind this connection, asked rather than assumed.

pub async fn version(&@moondb.AsyncDriver) -> String raise @moondb.DbError

The server's own version sentence, as it answered.

moonbitstack/moontidb

moontidb

Types 1

pub(all) enum Server {
  Mysql
  Mariadb
  Tidb
}

Functions & methods 6

pub fn capabilities_for(Server) -> @moondb.Capabilities

What this server can do, as opposed to what the driver implements. @moondb.Capabilities::for_dialect describes the PostgreSQL implementation; CockroachDB is not PostgreSQL, and the difference that matters to a caller is notifications: LISTEN and NOTIFY do not exist there, so a query layer that plans to wait for a notification has to poll instead.

The rest of the PostgreSQL surface does hold on CockroachDB — cursors, savepoints, COPY in both directions, RETURNING — which is why this clears one flag and not a row of them.

pub async fn connect(String, Int32, String, String, String, tls? : @tls.Client) -> @client.MysqlConn

Connect the way @moonpostgres.PgConn::connect does — the protocol is the same one, and there is nothing to translate.

pub fn Server::from_version(String) -> Self

The server a SELECT version() answer names. CockroachDB reports itself as CockroachDB CCL v24.3.0 (x86_64-…, built …), PostgreSQL as PostgreSQL 16.4 …, and a compatibility layer that borrows the string is still that layer.

pub fn Server::name(Self) -> String
pub async fn server_of(@client.MysqlConn) -> Server raise @moondb.DbError

Which server is behind this connection, asked rather than assumed.

pub async fn version(@client.MysqlConn) -> String raise @moondb.DbError

The server's own version sentence, as it answered.

moonbitstack/moonduckdb

moonduckdb

Types 2

pub struct DuckdbDriver {
  handle : Int64
  mut closed : Bool
}

A live connection to a DuckDB database, embedded in the process and reached through its C API — or, on the js target, through DuckDB's own WebAssembly build, which offers the same calls once it is instantiated.

On native the engine is not linked in: the stub loads the released shared library on the first call, which is what keeps this package a single small C file instead of a twenty-megabyte C++ build. MOONDUCKDB_LIBRARY names the library when the loader's default search does not find it. On js the engine is a build fetched before the run: see scripts/fetch_engine.sh.

The type, its methods and the trait are the same on both. Only open differs, because a WebAssembly module has to be instantiated before it can be called.

pub struct DuckdbStmt {
  handle : Int64
  sql : String
  mut params : Array[@moondb.Value]
  mut closed : Bool
}

A statement DuckDB prepared once. DuckDB's PREPARE survives across runs, so a repeated INSERT binds new values to the same plan instead of being parsed again — which for an analytical engine is where most of the per-statement cost sits.

Functions & methods 23

pub fn DuckdbDriver::begin(Self) -> Unit raise @moondb.DbError

@moondb.Driver's begin, callable on the value directly.

pub fn DuckdbDriver::capabilities(Self) -> @moondb.Capabilities

@moondb.Driver's capabilities, callable on the value directly.

pub fn DuckdbDriver::close(Self) -> Unit

@moondb.Driver's close, callable on the value directly.

pub fn DuckdbDriver::commit(Self) -> Unit raise @moondb.DbError

@moondb.Driver's commit, callable on the value directly.

pub fn DuckdbDriver::dialect(Self) -> @moondb.Dialect

@moondb.Driver's dialect, callable on the value directly.

pub fn DuckdbDriver::exec_script(Self, String) -> Unit raise @moondb.DbError

A semicolon-separated script, run as one call — for a schema block, where the statement-by-statement path would be a statement per line.

pub fn DuckdbDriver::execute(Self, String, Array[@moondb.Value]) -> @moondb.ExecResult raise @moondb.DbError

@moondb.Driver's execute, callable on the value directly.

pub fn DuckdbDriver::is_closed(Self) -> Bool

Whether close has been called.

pub fn DuckdbDriver::open(String) -> Self raise @moondb.DbError

On js: pub async fn DuckdbDriver::open(String) -> Self raise @moondb.DbError

Open (or create) the database at path. Use ":memory:" for a private in-memory database. Raises @moondb.ConnectError when the library cannot be loaded — the message says which name was looked for — or when the file cannot be opened.

pub fn DuckdbDriver::ping(Self) -> Bool

@moondb.Driver's ping, callable on the value directly.

pub fn DuckdbDriver::prepare(Self, String) -> &@moondb.Stmt raise @moondb.DbError

@moondb.Driver's prepare, callable on the value directly.

pub fn DuckdbDriver::query(Self, String, Array[@moondb.Value]) -> Array[@moondb.Row] raise @moondb.DbError

@moondb.Driver's query, callable on the value directly.

pub fn DuckdbDriver::query_stream(Self, String, Array[@moondb.Value]) -> &@moondb.Cursor raise @moondb.DbError

@moondb.Driver's query_stream, callable on the value directly.

pub fn DuckdbDriver::rollback(Self) -> Unit raise @moondb.DbError

@moondb.Driver's rollback, callable on the value directly.

pub fn DuckdbStmt::bind(Self, Array[@moondb.Value]) -> Unit raise @moondb.DbError

@moondb.Stmt's bind, callable on the value directly.

pub fn DuckdbStmt::bind_named(Self, Map[String, @moondb.Value]) -> Unit raise @moondb.DbError

@moondb.Stmt's bind_named, callable on the value directly.

pub fn DuckdbStmt::close(Self) -> Unit

@moondb.Stmt's close, callable on the value directly.

pub fn DuckdbStmt::execute(Self) -> @moondb.ExecResult raise @moondb.DbError

@moondb.Stmt's execute, callable on the value directly.

pub fn DuckdbStmt::new(Int64, String) -> Self raise @moondb.DbError

Prepare sql on connection.

pub fn DuckdbStmt::params(Self) -> Array[String]

@moondb.Stmt's params, callable on the value directly.

pub fn DuckdbStmt::query(Self) -> Array[@moondb.Row] raise @moondb.DbError

@moondb.Stmt's query, callable on the value directly.

pub fn DuckdbStmt::query_stream(Self) -> &@moondb.Cursor raise @moondb.DbError

@moondb.Stmt's query_stream, callable on the value directly.

pub fn DuckdbStmt::sql(Self) -> String

@moondb.Stmt's sql, callable on the value directly.

moonbitstack/moonsqls

moonsqls

Types 3

pub struct SqlServerConn {
  wire : TdsWire
  mut closed : Bool
  database : String
  mut txn : Bytes
}

A live connection to a SQL Server instance, speaking TDS 7.4 directly. Open it with SqlServerConn::connect; it implements @moondb.AsyncDriver, so a query layer written for PostgreSQL or MySQL drives it unchanged.

Every statement is either a SQL batch or an RPC to sp_executesql, and the answer is read out of the token stream — COLMETADATA, ROW, NBCROW, DONE, ERROR — rather than through a client library.

pub struct SqlServerStmt {
  conn : SqlServerConn
  sql : String
  mut params : Array[@moondb.Value]
  mut handle : Int32
  mut declaration : String
  mut closed : Bool
}

One SQL text compiled by the server, run with new values on every call.

The handle comes from sp_prepare and is released by sp_unprepare; between the two, a run is a sp_execute RPC that sends the handle and the values and nothing else — the statement text does not travel again.

A statement's declaration is part of what the server compiles: sp_prepare is told the type of every parameter, and those types come from the values. So a handle is compiled at the first bind that has values (or immediately, for a statement with no placeholders), and a later bind whose values declare different types drops the old handle and compiles a new one rather than sending values the plan did not expect.

pub struct TdsWire {
  sock : @socket.Tcp
  mut tls : @tls.Conn?
  mut pending : Bytes
  mut packet_id : Int32
}

The transport under the protocol.

Functions & methods 25

pub fn permissive_tls() -> @tls.Client

A TLS configuration that encrypts without authenticating: SQL Server ships a self-signed certificate, and a driver that refused it would refuse every default install. Pass your own @tls.Client to connect to check the chain instead.

pub async fn SqlServerConn::begin(Self) -> Unit raise @moondb.DbError

@moondb.AsyncDriver's begin, callable on the value directly.

pub async fn SqlServerConn::bulk_copy(Self, String, Array[String], Array[Array[@moondb.Value]]) -> Int64 raise @moondb.DbError

Copy rows into table, one cell per name in columns, and report how many rows the server took.

The rows travel in the protocol's bulk-load format: INSERT BULK declares the shape, then one message carries the column metadata and every row. Values are bound into that stream, never spliced into the statement, so a string containing a quote is a string.

let copied = conn.bulk_copy("hero", ["id", "name"], [
[@moondb.Int(1), @moondb.Text("Nova")],
[@moondb.Int(2), @moondb.Text("Ivan")],
])
pub fn SqlServerConn::capabilities(Self) -> @moondb.Capabilities

@moondb.AsyncDriver's capabilities, callable on the value directly.

pub async fn SqlServerConn::close(Self) -> Unit

@moondb.AsyncDriver's close, callable on the value directly.

pub async fn SqlServerConn::commit(Self) -> Unit raise @moondb.DbError

@moondb.AsyncDriver's commit, callable on the value directly.

pub async fn SqlServerConn::connect(String, Int32, String, String, String, encrypt? : Bool, strict? : Bool, tls? : @tls.Client) -> Self raise @moondb.DbError

Open a connection. encrypt defaults to true: the login packet carries a password, and prelogin is where the client says so. A server that answers "encryption not supported" gets a clear session, because the alternative is refusing to connect to it.

strict selects TDS 8.0, where TLS is not negotiated at all: it has already started before the first TDS byte, so prelogin and the login inside it are encrypted whether the server would have offered encryption or not. A server older than SQL Server 2022 fails the handshake — which is the point of asking for it.

pub fn SqlServerConn::database(Self) -> String

The database the session is in.

pub fn SqlServerConn::dialect(Self) -> @moondb.Dialect

@moondb.AsyncDriver's dialect, callable on the value directly.

pub async fn SqlServerConn::execute(Self, String, Array[@moondb.Value]) -> @moondb.ExecResult raise @moondb.DbError

@moondb.AsyncDriver's execute, callable on the value directly.

pub async fn SqlServerConn::ping(Self) -> Bool

@moondb.AsyncDriver's ping, callable on the value directly.

pub async fn SqlServerConn::prepare(Self, String) -> &@moondb.AsyncStmt raise @moondb.DbError

@moondb.AsyncDriver's prepare, callable on the value directly.

pub async fn SqlServerConn::query(Self, String, Array[@moondb.Value]) -> Array[@moondb.Row] raise @moondb.DbError

@moondb.AsyncDriver's query, callable on the value directly.

pub async fn SqlServerConn::query_stream(Self, String, Array[@moondb.Value]) -> &@moondb.AsyncCursor raise @moondb.DbError

@moondb.AsyncDriver's query_stream, callable on the value directly.

pub async fn SqlServerConn::rollback(Self) -> Unit raise @moondb.DbError

@moondb.AsyncDriver's rollback, callable on the value directly.

pub async fn SqlServerStmt::bind(Self, Array[@moondb.Value]) -> Unit raise @moondb.DbError

@moondb.AsyncStmt's bind, callable on the value directly.

pub async fn SqlServerStmt::bind_named(Self, Map[String, @moondb.Value]) -> Unit raise @moondb.DbError

@moondb.AsyncStmt's bind_named, callable on the value directly.

pub async fn SqlServerStmt::close(Self) -> Unit

@moondb.AsyncStmt's close, callable on the value directly.

pub async fn SqlServerStmt::execute(Self) -> @moondb.ExecResult raise @moondb.DbError

@moondb.AsyncStmt's execute, callable on the value directly.

pub fn SqlServerStmt::new(SqlServerConn, String) -> Self
pub fn SqlServerStmt::params(Self) -> Array[String]

@moondb.AsyncStmt's params, callable on the value directly.

pub async fn SqlServerStmt::query(Self) -> Array[@moondb.Row] raise @moondb.DbError

@moondb.AsyncStmt's query, callable on the value directly.

pub async fn SqlServerStmt::query_stream(Self) -> &@moondb.AsyncCursor raise @moondb.DbError

@moondb.AsyncStmt's query_stream, callable on the value directly.

pub fn SqlServerStmt::sql(Self) -> String

@moondb.AsyncStmt's sql, callable on the value directly.

pub fn strict_tls() -> @tls.Client

The configuration a TDS 8.0 session needs: the same permissive trust as permissive_tls, plus the ALPN protocol tds/8.0. The server uses ALPN to tell which protocol version a TLS connection that started before any TDS byte is carrying; without it the handshake is refused, so a caller who supplies their own configuration for a strict connection has to name the protocol themselves.