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
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.
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
}
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 93
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 fn AsyncArrayCursor::new(Array[Row]) -> Self
A cursor positioned before the first of rows.
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
pub fn[T] AsyncExecutor::new(T) -> Self[T]
The narrow view over inner.
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 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 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/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]
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]
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
}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 61
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]) -> Bytes
Bind ('B') params to a named (or unnamed) prepared statement, producing a named (or unnamed) portal. All parameters and result columns use text format.
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_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_BYTEA : Int32 = 17
The pg_type OID of bytea, as a column description reports it.
pub const OID_DATE : Int32 = 1082
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
pub const OID_JSONB : Int32 = 3802
The pg_type OID of jsonb.
pub const OID_NUMERIC : Int32 = 1700
The pg_type OID of numeric.
pub const OID_TIME : Int32 = 1083
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
pub fn parse_double(String) -> Double?
Parse a floating-point literal from PostgreSQL text (123.45, -1e10, Infinity, NaN), or None if malformed. Handles sign, fraction, and a base-10 exponent; the special IEEE tokens PostgreSQL emits are recognised.
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 fn PgConn::capabilities(Self) -> @moondb.Capabilities
@moondb.AsyncDriver's capabilities, callable on the value directly.
pub fn PgConn::dialect(Self) -> @moondb.Dialect
@moondb.AsyncDriver's dialect, callable on the value directly.
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 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 fn PgCopyOut::rows(Self) -> Int64
How many rows the finished COPY reported, or 0 while it is still running.
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 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 fn PgWire::tcp(@tcp.Conn) -> Self
Wrap a socket — plain, or the TLS session @tcp.Conn::client returned.
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 63
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]) -> 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.
pub fn build_stmt_prepare(String) -> Bytes
COM_STMT_PREPARE (0x16): the SQL text follows the command byte.
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 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
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
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
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.