Sign in

    moondb

    The standard database-access interface for MoonBit — the driver↔query-layer contract, transliterated from Go's database/sql/driver and Python's DB-API 2.0. ORMs (moonorm) build on it; drivers (moonsqlite / moonpostgres / moonmysql) implement it.

    database
    sql
    driver
    db-api
    moonbit
    interface
    Download zip
    Version
    0.2.2
    License
    Apache-2.0
    Last updated
    2 hours ago
    Downloads
    1K

    #moondb

    The standard database-access interface for MoonBit.

    The small, pure contract that sits between database drivers and query layers — MoonBit's answer to Go's database/sql/driver and Python's DB-API 2.0 (PEP 249).

    Check and Test tests API docs mooncakes license

    Moved on mooncakes from Lfan-ke/moondb to moonbitstack/moondb.

    moondb defines one thing: the boundary every SQL database backend implements and every ORM / query builder is written against. The interface package has zero dependencies, is pure (compiles on wasm, wasm-gc, js, and native alike), and ships a dependency-free reference driver so the whole stack above it can be tested without a database. The connection pool lives in its own package, moonbitstack/moondb/pool, so a driver that only implements the interface carries none of the pool's dependencies.

    It deliberately does not connect to a database, speak a wire protocol, or build SQL. Those belong on either side of the seam:

    flowchart TD subgraph layers["query layers — build on moondb"] ORM["moonorm<br/><small>SQLAlchemy-style ORM / query builder</small>"] APP["your app / repository code"] end IFACE(["<b>@moondb</b><br/><small>Value · Row · ExecResult · DbError · Driver</small>"]) subgraph drivers["drivers — implement moondb"] SQLITE["moonsqlite<br/><small>C-FFI, native</small>"] PG["moonpostgres<br/><small>pure-MoonBit wire</small>"] MYSQL["moonmysql<br/><small>pure-MoonBit wire</small>"] MOCK["MockDriver<br/><small>in-memory, ships here</small>"] end APP --> ORM --> IFACE IFACE --> SQLITE IFACE --> PG IFACE --> MYSQL IFACE --> MOCK

    The value of a contract package is that the two sides are written by different people at different times and still fit: a driver author implements Driver once, and every query layer works against their backend; an ORM author targets Driver once, and every driver works under their ORM.

    #Install

    moon add moonbitstack/moondb

    #The interface at a glance

    TypeRoleTransliterated from
    Valueone dialect-neutral cell — the unit crossing the boundary both waysGo driver.Value, DB-API type objects
    Rowone result row: aligned column names + Values, with typed accessorssql.Rows / DB-API row tuple
    ToValue, FromValuehow a MoonBit value binds as a parameter and reads back from a cell; Row::read reads any FromValueGo driver.Valuer / sql.Scanner, Diesel ToSql / FromSql
    ExecResultoutcome of a non-query: rows affected + last insert idGo sql.Result, DB-API rowcount/lastrowid
    DbErrorthe one error every operation raisesDB-API exception hierarchy (flattened)
    Driverthe trait a backend implements / a query layer targets (with a default ping health probe)Go driver.Conn+Execer+Queryer+Pinger
    @pool.Poola fixed-ceiling connection pool over any Driver, in moonbitstack/moondb/poolGo sql.DB pool, SQLAlchemy QueuePool
    AsyncDriverthe same contract for a backend reached over a socket, every method awaitedasyncpg / asyncio DB-API
    AsyncCursorforward-only cursor whose next is awaited, for a wire driver's live rows—
    MockDriverdependency-free in-memory reference driver, for tests—

    #The binding contract

    execute and query both take (sql, params). The SQL string carries positional placeholders and params supplies one Value per placeholder, in order. Values are bound out-of-band by the driver and are never spliced into the SQL text — that is what makes a moondb-based stack injection-safe all the way to the wire.

    The placeholder token is the driver's dialect (? for SQLite/MySQL, $1, $2, … for PostgreSQL); moondb fixes the calling convention — an ordered Array[Value] — not the spelling.

    #Quickstart

    test "round-trip through the interface" {
    // A query layer is written against the `Driver` trait, not a concrete backend.
    let db = @moondb.MockDriver::new()

    // Bind values as parameters — never string-interpolated into the SQL.
    db.execute("INSERT INTO hero (id, name) VALUES (?, ?)", [Int(1), Text("Nova")])
    |> ignore

    db.begin()
    db.execute("INSERT INTO hero (id, name) VALUES (?, ?)", [Int(2), Text("Iris")])
    |> ignore
    db.rollback() // Iris is undone; Nova remains.

    let rows = db.query("SELECT id, name FROM hero", [])
    assert_eq(rows.length(), 1)
    assert_eq(rows[0].int_by("c0"), 1)
    assert_eq(rows[0].text_by("c1"), "Nova")
    }

    Swap MockDriver for moonsqlite / moonpostgres / moonmysql and the same code runs against a real database — that substitutability is the point of the package.

    #Your own types

    ToValue and FromValue convert between a MoonBit value and a Value. They are implemented for the type behind each case (Bool, Int, Int64, Double, String, Bytes), for Value itself, and for T?, where None is NULL. Row::read and Row::read_by read a cell as any FromValue type, so a nullable column reads straight into an option:

    test "read a nullable column as an option" {
    let row : @moondb.Row = { columns: ["id", "age"], values: [Int(1), Null] }
    let id : Int64 = row.read(0)
    let age : Int? = row.read_by("age")
    assert_eq((id, age), (1L, None))
    }

    A type of the program's own implements both traits to be stored as one of the cases. A refusal is a TypeError whose message reads after "column N is":

    struct Email(String)

    impl @moondb.ToValue for Email with fn to_value(self) {
    let Email(s) = self
    Text(s)
    }

    impl @moondb.FromValue for Email with fn from_value(v) {
    match v {
    Text(s) if s.contains("@") => Email(s)
    other => raise TypeError(other.kind() + ", not Email")
    }
    }

    #Implementing a driver

    A backend implements the six-method Driver trait. DbError is pub(all), so a driver in its own package can construct and raise every case:

    pub impl @moondb.Driver for MyConn with execute(self, sql, params) {
    // ... bind `params` positionally, run `sql`, then:
    { rows_affected: n, last_insert_id: id }
    }
    // query / begin / commit / rollback / close likewise.

    execute/query/begin/commit/rollback raise DbError on failure; close is best-effort and idempotent.

    #Design notes

    • Why raise, not Result. Typed accessors and driver calls raise DbError rather than returning Result[_, DbError], so a decode bug or a dropped connection surfaces at the call site instead of being silently swallowed. A caller opts into recovery with try/catch.
    • Typed accessors are strict. row.int(i) raises TypeError if the cell is not an integer — including when it is NULL. Read a nullable column as T? with read, or guard it with is_null first. Integer→integer and integer→double conversions are allowed and lossless; int narrows an Int64 and says so.
    • DbError is pub(all). A plain pub suberror can be caught from another package but not constructed — which would stop out-of-tree drivers from raising it. pub(all) opens the constructors.
    • There are two driver traits, on purpose. Driver is synchronous, which is right for a backend whose calls block in C (moonsqlite steps a prepared statement and returns). A backend reached over TCP cannot be written that way: MoonBit's only socket stack is async-only, and an async fn cannot be called from a synchronous one. A wire driver forced to conform to Driver can do nothing but raise from every method — and because ping is a defaulted method built on query, it swallows that raise into false, so a Pool with pre_ping judges every one of its connections permanently unhealthy. AsyncDriver is the seam for those backends: the same eight operations, each awaited. It is a peer of Driver, not a replacement — a synchronous backend keeps implementing Driver and never becomes async. Declaring async methods pulls in no async runtime, so moondb stays dependency-free and still compiles on every backend; only a driver that implements the trait, and a caller that runs it in an event loop, need one.

    • The pool is synchronous, and its counting is moonpool's. @pool.Pool[D] reuses idle connections under a size ceiling, evicts a connection that fails its pre_ping probe or outlives max_lifetime (age measured by an injected clock, the way database/sql swaps nowFunc in tests), and offers a non-blocking try_acquire. Because moondb's base contract is sync and the pure backends have no threads, an exhausted acquire fails immediately rather than blocking — the acquire_timeout is the budget an async driver layers real waiting on top of. ping is a default Driver method (SELECT 1), so every driver gets a health probe for free. The bookkeeping underneath — how many are live, which are idle, when each was born — is moonpool's Pool, and so is the optional ward in front of the database: a backoff after failed connects (acquire then raises ConnectError until the delay has passed), a token bucket spacing reconnections, a breaker that stops connecting to a failing backend, a shedder. This package adds only opening, closing and probing. It is a separate package because that dependency is the pool's alone: import { "moonbitstack/moondb/pool" } where a pool is wanted, and the interface stays dependency-free where it is not.

    #Roadmap

    v0.1 fixes the smallest contract that a relational backend and a query layer both need, so it can stabilise. Planned, additive extensions (none of which change the v0.1 surface):

    • Prepared statements — a Stmt handle for repeated execution with rebinding.
    • Streaming rows — a Rows cursor so large result sets need not fully materialise.
    • A temporal Value case — dates/times currently ride as ISO-8601 Text; a dedicated case (with a decided epoch/precision) will follow.
    • Named parameters and nested transactions (savepoints) as a layer over the flat begin/commit/rollback.

    #Tests

    The suite covers the value model, every typed accessor and its error path, the error type, the reference driver — including real transaction rollback/commit semantics — and the connection pool. Key behaviours are mutation-verified: breaking rollback turns the transaction tests red, and disabling the pool's pre_ping eviction or its max_lifetime retirement turns the corresponding pool tests red. They run on all four backends:

    moon test --target all

    #License

    Apache-2.0 © Leo Cheng

    AsyncCursor

    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.

    AsyncDriver

    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 = _
    }

    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.

    Cursor

    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, so a large result set is consumed in bounded memory (SQLAlchemy's yield_per / server-side cursors, Go's sql.Rows, 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.

    Driver

    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 = _
    }

    The contract every database backend implements and every query layer is written against — the seam moondb exists to define. It is the MoonBit transliteration of Go's database/sql/driver.Conn/Execer/Queryer and Python's DB-API 2.0 Connection/Cursor, 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, matching Go's io.Closer discipline for connections.

    This trait is pub(open) so out-of-tree drivers (moonsqlite, moonpostgres, moonmysql, …) can implement it. A future prepared-Stmt handle and a streaming Rows cursor are noted in the README roadmap; v0.1 fixes exactly the operations below so the contract everything pins to stays small and stable.

    FromValue

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

    A MoonBit value read back from a cell: Go's sql.Scanner, Diesel's FromSql. 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".
    impl FromValue for Bool
    impl FromValue for Int
    impl FromValue for Int64
    impl FromValue for Double
    impl FromValue for String
    impl FromValue for Option[T]
    impl FromValue for Bytes

    ToValue

    pub(open) trait ToValue {
    fn to_value(Self) -> Value
    }

    A MoonBit value that binds as a parameter: Go's driver.Valuer, Diesel's ToSql. 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.
    impl ToValue for Bool
    impl ToValue for Int
    impl ToValue for Int64
    impl ToValue for Double
    impl ToValue for String
    impl ToValue for Option[T]
    impl ToValue for Bytes

    DbError

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

    A database-layer failure. Every fallible operation on the interface raises this one error type, so a query layer catches a single kind. The cases mirror the coarse failure classes both Go's database/sql and Python's DB-API 2.0 draw a line around, 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: a prepare/step/bind error, a constraint violation, or a protocol error. Carries the backend's message.
    • 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.

    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.
    impl Show for DbError

    DbError::output

    fn DbError::output(self : DbError, logger : &Logger) -> Unit

    DbError::to_string

    fn DbError::to_string(self : DbError) -> String

    A one-line rendering of the error, e.g. QueryError: no such table: hero.

    ArrayCursor

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

    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.

    ArrayCursor::close

    fn ArrayCursor::close(self : ArrayCursor) -> Unit

    ArrayCursor::new

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

    A cursor positioned before the first of rows.

    ArrayCursor::next

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

    AsyncArrayCursor

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

    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.

    AsyncArrayCursor::close

    async fn AsyncArrayCursor::close(self : AsyncArrayCursor) -> Unit

    AsyncArrayCursor::new

    A cursor positioned before the first of rows.

    AsyncArrayCursor::next

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

    ExecResult

    pub(all) struct ExecResult {
    rows_affected : Int64
    last_insert_id : Int64
    } derive(Eq)

    The outcome of a non-query statement (INSERT/UPDATE/DELETE/DDL): how many rows it changed and the id of the last inserted row.

    rows_affected is 64-bit to hold bulk DML counts; last_insert_id is the backend's auto-increment / rowid value and is backend-defined (0 when the statement produced none). This mirrors Go's sql.Result (RowsAffected / LastInsertId) and DB-API's cursor.rowcount / lastrowid.

    ExecResult::equal

    fn ExecResult::equal(ExecResult, ExecResult) -> Bool

    ExecResult::not_equal

    fn ExecResult::not_equal(x : ExecResult, y : ExecResult) -> Bool

    MockDriver

    pub struct MockDriver {
    rows : Array[Row]
    next_id : Int64
    savepoints : Array[Int]
    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.

    MockDriver::begin

    fn MockDriver::begin(self : MockDriver) -> Unit raise DbError

    MockDriver::close

    fn MockDriver::close(self : MockDriver) -> Unit

    MockDriver::commit

    fn MockDriver::commit(self : MockDriver) -> Unit raise DbError

    MockDriver::execute

    fn MockDriver::execute(self : MockDriver, _sql : String, params : Array[Value]) -> ExecResult raise DbError

    MockDriver::in_transaction

    fn MockDriver::in_transaction(self : MockDriver) -> Bool

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

    MockDriver::is_closed

    fn MockDriver::is_closed(self : MockDriver) -> Bool

    Whether the connection has been closed.

    MockDriver::new

    fn MockDriver::new() -> MockDriver

    A fresh, empty mock connection.

    MockDriver::ping

    fn MockDriver::ping(self : MockDriver) -> Bool

    MockDriver::query

    fn MockDriver::query(self : MockDriver, _sql : String, _params : Array[Value]) -> Array[Row] raise DbError

    MockDriver::query_stream

    fn MockDriver::query_stream(self : MockDriver, sql : String, params : Array[Value]) -> &Cursor raise DbError

    MockDriver::rollback

    fn MockDriver::rollback(self : MockDriver) -> Unit raise DbError

    MockDriver::row_count

    fn MockDriver::row_count(self : MockDriver) -> Int

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

    Row

    pub(all) struct Row {
    columns : Array[String]
    values : Array[Value]
    } derive(Eq,
    Debug
    )

    One row of a result set: the column names in projection order alongside the decoded [Value] for each. columns and values are the same length and are index-aligned — columns[i] names values[i].

    This is the shape drivers hand back from [Driver::query] and the shape a query layer decodes. Read cells either positionally ([get]/[int]/…) or by column name ([by_name]/[int_by]/…). The typed accessors raise on a type or lookup mismatch rather than returning a sentinel, so a decode bug surfaces as a DbError at the call site instead of silently reading a zero value.

    Row::blob

    fn Row::blob(self : Row, idx : Int) -> Bytes raise DbError

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

    Row::blob_by

    fn Row::blob_by(self : Row, name : String) -> Bytes raise DbError

    Read the column named name as an opaque blob.

    Row::bool

    fn Row::bool(self : Row, idx : Int) -> Bool raise DbError

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

    Row::bool_by

    fn Row::bool_by(self : Row, name : String) -> Bool raise DbError

    Read the column named name as a Bool.

    Row::by_name

    fn Row::by_name(self : Row, name : 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).

    Row::double

    fn Row::double(self : Row, idx : Int) -> Double raise DbError

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

    Row::double_by

    fn Row::double_by(self : Row, name : String) -> Double raise DbError

    Read the column named name as a Double.

    Row::equal

    fn Row::equal(Row, Row) -> Bool

    Row::get

    fn Row::get(self : Row, idx : Int) -> Value raise DbError

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

    Row::index_of

    fn Row::index_of(self : Row, name : String) -> Int 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.

    Row::int

    fn Row::int(self : Row, idx : Int) -> Int 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.

    Row::int64

    fn Row::int64(self : Row, idx : Int) -> Int64 raise DbError

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

    Row::int64_by

    fn Row::int64_by(self : Row, name : String) -> Int64 raise DbError

    Read the column named name as an Int64.

    Row::int_by

    fn Row::int_by(self : Row, name : String) -> Int raise DbError

    Read the column named name as an Int.

    Row::is_null

    fn Row::is_null(self : Row, idx : Int) -> 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.

    Row::is_null_by

    fn Row::is_null_by(self : Row, name : String) -> Bool raise DbError

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

    Row::not_equal

    fn Row::not_equal(x : Row, y : Row) -> Bool

    Row::read

    fn[T : FromValue] Row::read(self : Row, idx : Int) -> 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.

    Row::read_by

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

    Read the column named name as any [FromValue] type.

    Row::text

    fn Row::text(self : Row, idx : Int) -> String raise DbError

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

    Row::text_by

    fn Row::text_by(self : Row, name : String) -> String raise DbError

    Read the column named name as text.

    Row::to_repr

    Row::width

    fn Row::width(self : Row) -> Int

    The number of columns in the row.

    Value

    pub(all) enum Value {
    Null
    Bool(Bool)
    Int(Int)
    Int64(Int64)
    Double(Double)
    Text(String)
    Blob(Bytes)
    } derive(Eq,
    Debug
    )

    A single dialect-neutral database value — the unit of everything that crosses the driver boundary in either direction. Bound parameters travel in as Values (never spliced into the SQL string, so binding is injection-safe by construction) and result columns come back as Values.

    This is the moondb analogue of Go's driver.Value and Python DB-API's input type objects: a small, closed sum every backend can map onto its own wire types. Drivers are expected to normalise their column types down to these seven cases; a query layer built on moondb reads them back through [Row]'s typed accessors.

    • Null — SQL NULL / a missing value.
    • Bool — a boolean; backends without a native boolean map 0/1.
    • Int — a 32-bit signed integer.
    • Int64 — a 64-bit signed integer (rowids, BIGINT, counters).
    • Double — an IEEE-754 double (REAL / FLOAT8).
    • Text — a UTF-8 string (TEXT / VARCHAR); dates and times ride here as ISO-8601 text until a dedicated temporal case lands (see the README roadmap).
    • Blob — an opaque byte string (BLOB / BYTEA).
    impl Show for Value
    impl FromValue for Value
    impl ToValue for Value

    Value::equal

    fn Value::equal(Value, Value) -> Bool

    Value::from_value

    fn Value::from_value(v : Value) -> Value raise DbError

    Value::is_null

    fn Value::is_null(self : Value) -> Bool

    Whether this value is SQL NULL.

    Value::kind

    fn Value::kind(self : Value) -> 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.

    Value::not_equal

    fn Value::not_equal(x : Value, y : Value) -> Bool

    Value::output

    fn Value::output(self : Value, logger : &Logger) -> Unit

    Value::to_repr

    Value::to_string

    fn Value::to_string(self : Value) -> String

    Value::to_value

    fn Value::to_value(self : Value) -> Value

    drain

    fn drain(cursor : &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).

    drain_async

    async fn drain_async(cursor : &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.