Sign in

    #Postgres Client

    An asynchronous PostgreSQL client for MoonBit.

    Client owns one PostgreSQL session. Calls made through clones of the same client are executed in FIFO order, one complete logical operation at a time. Use pgpool when independent tasks need database concurrency.

    #Quick Start

    ///|
    async fn _quick_start() -> Unit {
    @async.with_task_group(group => {
    let config = @client.Config::Config(
    "localhost",
    user="postgres",
    database="app",
    password="secret",
    port=5432,
    ssl_mode=Disable,
    application_name="my-service",
    )
    let (client, executor) = @client.Client::create(config)
    let task = group.spawn(no_wait=true, () => executor.run())
    client.ready()
    let current_user : String = client
    .query_one("select current_user::text as current_user")
    .get_name("current_user")
    ignore(current_user)
    client.close()
    task.wait()
    })
    }

    Client::create(config) only allocates state. Spawn the single-use executor and await ready() to observe connection and authentication errors. Any number of tasks may await the same readiness result. Async database operations also wait for readiness. Before startup finishes, parameter() and cancel_token() raise ClientError::NotReady. A repeated run() raises ClientError::ExecutorAlreadyStarted. Client::close() is asynchronous: it stops new work, waits for already queued work, sends PostgreSQL Terminate, and waits for the driver to finish.

    Client::abort() is the hard-stop counterpart. It is synchronous and idempotent: it marks the runtime unusable immediately and requests driver cancellation without sending Terminate. Active, queued, and later operations fail with ClientError::Closed("connection aborted"). The driver closes the discarded physical transport as cancellation unwinds.

    Client::is_closed() reports whether the runtime can still be used, not whether every teardown callback has already completed. It therefore returns true immediately after abort(); close() remains the API to await graceful PostgreSQL shutdown and completed transport cleanup.

    Client::is_transaction_idle() returns true only after successful startup, while the client remains open, and when the most recent ReadyForQuery status was I. It is a snapshot of the last protocol response, not a live server check. It returns false for active (T) or failed (E) transactions.

    #Execution Model

    A physical PostgreSQL session has one protocol timeline. The client therefore holds an operation permit from the beginning to the end of a logical operation:

    • prepare, type lookup, execute, and temporary-statement cleanup form one operation
    • a row, simple-query, or COPY OUT stream owns the permit until EOF, finish(), or a detached background drain completes
    • COPY IN owns it until finish() or abort()
    • a transaction owns it until commit() or rollback()

    Another call through the same Client waits in FIFO order. It is not written to the server early and responses are never routed between several in-flight requests. Waiting is cancellation-safe: cancelling a waiter does not consume the permit or disturb the active operation.

    An unfinished stream intentionally blocks later work on that client. Prefer the callback scopes below: they finish the stream even on early return, error, or cancellation. Inside a callback, finish the stream before starting another operation on the same client. Use stream handles only within their scope.

    For manually managed streams, call finish() when stopping early. detach() transfers draining to the background; calling finish() afterwards waits for that drain to complete.

    If the connection closes or is aborted during a detached drain, the drain saves ClientError::Closed for an explicit finish() call. Database errors also remain observable through finish(); unexpected protocol failures fail the executor. If the executor has stopped, finish() drains the remaining responses itself or reports their closure.

    #Query APIs

    Choose the smallest result shape that matches the query:

    NeedAPI
    Exactly one rowClient::query_one
    Zero or one rowClient::query_opt
    Collect every rowClient::with_stream(sql, stream => stream.collect())
    Incremental rowsClient::with_stream
    Explicit parameter typesClient::with_typed_stream
    Execute a prepared statementClient::with_statement_stream
    Fetch a portal windowPortal::with_stream
    Affected row countClient::execute
    SQL batch without parametersClient::batch_execute
    Text/simple protocol framesClient::with_simple_query
    Bulk import/exportClient::with_copy_in, Client::with_copy_out

    Client::simple_query and Client::query_statement are asynchronous because they must wait for an operation permit before returning a stream. MoonBit async calls do not use an await keyword.

    QuerySummary.row_count is a UInt64 count of observed DataRow messages, including rows discarded during cleanup. All execute and execute_raw methods, including transaction, scoped-statement, and portal methods, return the affected row count as UInt64 from the command tag. Commands without a count return zero. Counted tags (INSERT oid rows and DELETE/UPDATE/MERGE/SELECT/MOVE/FETCH/COPY rows) require decimal digits within the UInt64 range; missing, invalid, or overflowing counts raise ClientError::Protocol after the execution stream has been drained. Portal max_rows remains an Int protocol limit.

    Rows decode by index or PostgreSQL column name:

    ///|
    async fn _query_example(client : @client.Client) -> Int32 {
    let input = 41
    let row = client.query_one("select $1::int4 + 1 as value", params=[
    input as &ToSql,
    ])
    row.get_name("value")
    }

    StringView query parameters use the same codec as String, so a string slice can be passed without first materializing an owned string. Ordinary string parameters use text format. The ltree extension types ltree, lquery, and ltxtquery use binary format with a version byte followed by UTF-8. String decoding supports both text and binary results for these types; binary decoding validates and removes the version byte.

    String parameters must not contain NUL (U+0000). Built-in string codecs raise ClientError::Encode before UTF-8 encoding or writing the value, including StringView, Some values, string array elements, and the string extension types above. Only the viewed slice is checked for StringView. The driver also rejects zero bytes (0x00) in non-NULL text-format parameter payloads produced by custom ToSql codecs. NULL payloads are ignored, and binary payloads such as bytea may contain zero bytes. Literal \u0000 text and JSON escapes are allowed; this check does not validate JSON/JSONB logical values. Encoding errors may occur after Parse/Describe prepares the statement, but before any Bind/Execute is submitted. Temporary statements are closed before the error is returned, so a transaction callback that catches the error can continue with a valid query.

    #Client Encoding

    Dangerous operation: changing client_encoding away from UTF-8. The client requests client_encoding=UTF8 at startup and always encodes and decodes strings as UTF-8. Keep this setting at UTF-8 for the entire connection lifetime, including when using transactions or pooled sessions. Do not change it through SET client_encoding, SET NAMES, set_config, or startup options.

    The current implementation records server ParameterStatus updates but does not reject an unsupported encoding or adapt its codecs. An encoding mismatch can cause invalid UTF-8 errors or silently write incorrect text. For example, with a UTF-8 database and client_encoding=LATIN1, a parameter containing é is sent as UTF-8 bytes but interpreted as the two characters à and ©. Reading it back on the same connection can produce é again, hiding the incorrect stored value. Switching parameters or results to binary text format does not bypass PostgreSQL's client encoding conversion.

    If the encoding has been changed, discard the affected connection and verify any affected writes through a separate UTF-8 connection. Restoring UTF-8 does not repair text already stored incorrectly. Enforcement is tracked in TODO.md. See PostgreSQL's client encoding documentation.

    Inspect result data and metadata through Row::columns(), Row::values(), RowStream::columns(), SimpleQueryRow::columns(), and SimpleQueryRow::values(). These return read-only ArrayView values; use to_owned() when an editable copy is needed. RowStream::columns() reflects the latest row description when called, so obtain a new view after advancing the stream. SimpleQueryMessage::RowDescription also carries a read-only column-label view. CopyOutStream::formats() exposes the COPY wire formats in the same way.

    Statement and ScopedStatement expose params() and columns() metadata views; Portal exposes columns(). Type descriptors expose their classification through type_.kind; enum labels and composite fields are read-only views. The raw Bytes returned by Row::get_raw() is unchanged.

    query_typed supplies PostgreSQL parameter types explicitly. The former query_typed_raw compatibility alias has been removed.

    Each scope returns the callback's result. Row, simple-query, and COPY OUT scopes call finish() under cancellation protection after the callback exits. A successful callback is followed by any cleanup error; when the callback raises or is cancelled, best-effort cleanup preserves that original cause.

    ///|
    async fn _first_value(client : @client.Client) -> Int32? {
    client.with_stream("select generate_series(1, 100) as value", stream => {
    match stream.next() {
    Some(row) => Some(row.get_name("value"))
    None => None
    }
    })
    }

    Returning after one row still drains the remaining results and closes the temporary statement before the scope returns. If parameter count, type, or encoding validation fails after preparation, the temporary statement is closed under the same operation permit before the original error is returned; no Execute request is submitted.

    #Type Descriptors And Custom Arrays

    Type carries oid, name, schema : String?, and kind. Built-ins have Some("pg_catalog"); catalog-resolved types retain their actual schema. Type::unknown(oid, name=...) keeps its existing constructor and has None for its unresolved schema. Codecs can distinguish identically named types in different schemas:

    ///|
    fn _accept_status(type_ : @client.Type) -> Bool {
    type_.schema == Some("app") && type_.name == "status" && type_.kind is Enum(_)
    }

    Kind::Array(element), Domain(base), and Range(subtype) now carry complete Type descriptors. Composite fields expose field.type_, including nested metadata. To migrate code that used their OID payloads, read element.oid, base.oid, or subtype.oid; replace field.type_oid with field.type_.oid. Descriptor fields remain read-only. Eq compares the entire structure, including schema and child descriptors.

    The generic Array[T] codec uses Kind::Array metadata, so implementing only ToSql and FromSql for a custom enum also enables Array[Enum] and Array[Enum?]. Empty arrays and NULL elements are supported. Obtain the resolved array descriptor from statement or row metadata when using query_typed. Custom element encoders must write PostgreSQL binary element payloads: an array uses binary format for every element, regardless of the scalar codec's format method. Decoders receive the full element descriptor and Binary; the payload's element OID must match that descriptor. Built-in string arrays include ltree[], lquery[], and ltxtquery[]: their scalar codecs add and remove the version byte for each non-NULL element. Ordinary strings and JSON/JSONB retain their existing array handling.

    Catalog metadata is resolved recursively and cached per connection. clear_type_cache() forces custom types to be resolved again. A cycle raises ClientError::UnsupportedRecursiveType(oid) for the repeated OID; completed shared subtypes are reusable, and descriptors under construction are never cached. This is a local transaction error, so a successfully cleaned-up operation leaves the connection and transaction usable. If metadata resolution fails after preparation, the driver closes the undelivered statement before returning the error; a failed Close aborts the connection and preserves the original metadata error. Missing types and recoverable catalog database errors retain the existing Unknown fallback.

    Domains are not automatically unwrapped by codecs. Generic composite/range codecs, a public type registry, and multidimensional arrays are not provided.

    #Prepared Statements And Portals

    Use with_prepared to keep a named server-side statement for a callback and close it on return, error, or cancellation:

    ///|
    async fn _prepared_example(client : @client.Client) -> Int32 {
    client.with_prepared("select $1::int4 as value", statement => {
    let value = 7
    statement.with_stream(params=[value as &ToSql], stream => {
    let result : Int32 = stream.next().unwrap().get_name("value")
    result
    })
    })
    }

    with_prepared_typed also accepts explicit parameter types. The scoped handle offers with_stream and execute, and expires when the callback ends. It waits for operations already started through that handle, then closes the Statement. Client::with_statement_stream manages only an execution stream and leaves a manually prepared Statement open. For a manual statement inside a transaction, use Transaction::close_statement: direct Statement::close immediately raises ClientError::Closed while a root transaction holds or waits for the client permit. A named Statement may outlive the transaction, so rollback does not close it.

    Every Statement belongs to its creating connection. Passing it to another Client or Transaction raises ClientError::StatementConnectionMismatch before waiting or submitting requests, even if the Statement is already closed. Client copies share this identity; Statements remain reusable across transactions on that same connection.

    Portal is the only public portal handle. Create one inside a transaction with Transaction::with_portal(statement, params?, callback) or a transaction-owned ScopedStatement::with_portal(params?, callback). Both entry points close the portal on return, error, or cancellation. A client-owned ScopedStatement cannot create portals.

    With with_prepared, nested callbacks clean up in stream, portal, statement order:

    ///|
    async fn _portal_example(client : @client.Client) -> Int32 {
    client.with_transaction(tx => {
    tx.with_prepared("select 7::int4 as value", statement => {
    statement.with_portal((portal : @client.Portal) => {
    portal.with_stream(1, stream => {
    let result : Int32 = stream.next().unwrap().get_name("value")
    result
    })
    })
    })
    })
    }

    Portal owns both its server resource and callback scope. The former scoped handle has been renamed to Portal without a compatibility alias. Portal creation and cleanup use with_portal; Transaction::bind, query_portal, with_portal_stream, and close_portal remain package-internal helpers. Replace a manual bind/query/close sequence with one with_portal callback and successive portal.with_stream calls. Each fetch finishes only its current stream, so the same portal can resume at the next window. Unread rows in that window are discarded before the next call. Use max_rows=0 to fetch all remaining rows, or inspect QuerySummary.suspended to decide whether to fetch another window:

    ///|
    async fn _portal_pagination(client : @client.Client) -> Array[Int32] {
    let statement = client.prepare("select generate_series(1, $1::int4) as value")
    defer @async.protect_from_cancel(() => statement.close())
    client.with_transaction(tx => {
    tx.with_portal(statement, params=[3 as &ToSql], portal => {
    let values : Array[Int32] = []
    for ;; {
    let summary = portal.with_stream(2, stream => {
    for row in stream.collect() {
    values.push(row.get_name("value"))
    }
    stream.finish()
    })
    if !summary.suspended {
    break values
    }
    }
    })
    })
    }

    Portal::execute() consumes the portal to completion and returns the affected row count. Transaction::with_portal leaves its ordinary Statement open; the caller can reuse it after the portal scope or transaction ends and closes it separately. The pool's prepared-statement cache is unchanged.

    Both scoped handles reject new database operations with ClientError::Closed after their callback ends, wait for calls already started, then close their resource. Explicit commit() or rollback() raises ClientError::Closed while either resource scope is active, including while cleanup is waiting for a fetch.

    If cancellation arrives while prepare is waiting for PostgreSQL, the driver closes an already created statement before propagating cancellation. A failed Close aborts that physical connection.

    #Transactions

    with_transaction is the preferred scoped API. When the callback returns normally, it commits only if the transaction is still open; an explicit commit() or rollback() prevents a second completion. When the callback raises or is cancelled, it rolls back best-effort if the transaction is still open. Unfinished query streams are detached and drained through protocol cleanup before commit or rollback. A database error found during that drain causes rollback and is raised to the caller. An unfinished nested transaction is recursively rolled back, then the parent rolls back and raises ClientError::UnfinishedChildTransaction. A callback error or cancellation keeps its original cause while protected cleanup completes:

    ///|
    async fn _transaction_example(client : @client.Client) -> Unit {
    client.with_transaction(tx => {
    ignore(tx.execute("update accounts set active = true"))
    ()
    })
    }

    Set an isolation level directly with @client.IsolationLevel::ReadUncommitted, ReadCommitted, RepeatableRead, or Serializable. TransactionOptions accepts an enum value instead of an SQL string. The former read_uncommitted(), read_committed(), repeatable_read(), and serializable() factory methods have been removed; use the corresponding enum constructors:

    ///|
    async fn _serializable_transaction(client : @client.Client) -> Unit {
    client.with_transaction(
    _tx => (),
    options=@client.TransactionOptions(
    isolation_level=@client.IsolationLevel::Serializable,
    ),
    )
    }

    Use transaction() only when manual commit() / rollback() control is required. A transaction reserves the session for its complete lifetime, so calls queued through the outer Client run only after it ends. Nested transactions use PostgreSQL savepoints and stay inside the same operation. Manual commit() and rollback() also finish any open child streams; rollback recursively rolls back open nested transactions. A manual commit with an open nested transaction instead rolls it back and raises UnfinishedChildTransaction after rolling back the parent.

    Inside a transaction, with_stream and with_statement_stream scope one execution stream. They discard unread rows and wait for cleanup when the callback returns, raises, or is cancelled. with_statement_stream leaves the Statement open. Portal::with_stream likewise finishes one fetch, with the portal retained until its enclosing with_portal callback ends. Use with_transaction or with_savepoint on a transaction to scope nested work:

    ///|
    async fn _nested_transaction_example(client : @client.Client) -> Unit {
    client.with_transaction(tx => {
    tx.with_savepoint("optional_work", child => {
    child.with_stream("select 1", stream => ignore(stream.next()))
    })
    })
    }

    Named savepoints are quoted PostgreSQL identifiers. Quoting preserves case and supports spaces, double quotes, semicolons, and Unicode characters. Empty names and names containing NUL raise ClientError::InvalidSavepointName before SQL is sent; the parent transaction remains usable.

    Do not return a stream or nested transaction handle from its callback for later use: the scope finishes it before returning. Draining can wait for the current SQL command to finish; it does not impose an implicit timeout.

    #COPY And Streams

    Prefer with_copy_in and with_copy_out for bulk transfer. COPY IN requires an explicit sink.finish() inside the callback to commit the COPY. Returning without finishing, throwing, or being cancelled aborts unfinished COPY input and waits for cleanup. A completed COPY is not undone by a later callback error. finish() returns the affected row count as UInt64. If its command tag has an invalid or overflowing count, it raises ClientError::Protocol after consuming ReadyForQuery and releasing the operation permit; the connection is reusable.

    ///|
    async fn _copy_rows(client : @client.Client) -> UInt64 {
    client.with_copy_in("copy events(value) from stdin", sink => {
    sink.send(b"first\nsecond\n")
    sink.finish()
    })
    }

    The raw client APIs (query, query_typed, query_statement, simple_query, copy_in, and copy_out) remain available when the caller needs to transfer ownership or control draining explicitly. Portal streams are available only inside Portal::with_stream:

    • RowStream, SimpleQueryStream, and CopyOutStream expose next, collect, finish, and detach as appropriate.
    • CopyInSink::send feeds a bounded eight-chunk input queue; finish completes the copy and abort terminates it with an error.
    • Dropping a handle is not a lifecycle operation. Explicitly finish, abort, or detach it.

    The response and COPY input queues are bounded. During COPY IN, the driver stays the sole protocol reader while one request-local writer emits complete CopyData frames. This lets PostgreSQL report malformed input before finish(). Once an early database error reaches a blocked or later send, the sink drains through ReadyForQuery, releases its permit, and raises ClientError::Database; the same connection is then reusable. Bounded queues keep memory usage predictable and apply backpressure without allowing later operations to pass the active COPY.

    #Async Messages

    PostgreSQL notices, notifications, and parameter-status changes are separated from ordinary query responses:

    ///|
    async fn _read_async_message(client : @client.Client) -> @client.AsyncMessage? {
    client.next_message()
    }

    Use one dedicated consumer for next_message(). Notifications arrive while the connection is idle, without sending another query. It returns None after the driver closes, after buffered messages are read. By default the client retains at most 256 messages. Client::create_with_async_message_capacity(config,capacity) accepts a positive capacity. When full, the driver discards the oldest message and increments Client::dropped_async_messages() without waiting for the consumer. Current server parameters remain available through Client::parameter(name).

    #Cancellation

    Client::cancel_token() creates a best-effort PostgreSQL cancellation token. Cancellation targets the current backend operation; it does not replace normal task cancellation or stream cleanup. Call CancelToken::cancel() from another task or a timeout handler to send the request. process_id() exposes the backend process ID for diagnostics; the cancellation key stays inside the token.

    Task cancellation still waits for protected protocol work and cleanup to reach a safe boundary. execute drains its results before closing its temporary statement; execute_raw drains results while leaving the caller's statement open. This can wait for the current SQL command to finish.

    The client and transaction stream/COPY scopes keep business callbacks cancellable, including waits on user queues or other I/O. Waiting for the client's permit is also cancellable. Protocol startup is protected until a handle has an owner; cleanup is protected until ReadyForQuery and any temporary-statement Close complete. Pending cancellation is checked before entering the callback, after it returns, and after protected cleanup. Scopes also wait for drains already started by stream or sink methods interrupted inside the callback.

    Scopes do not automatically send PostgreSQL CancelRequest. Cancellation can therefore wait for the current SQL request to finish and drain. Use a raw stream's detach() when the caller needs to return before draining completes, or Client::abort() when the physical connection may be discarded.

    Pending cancellation is checked before starting a new operation and after its outermost cancellation protection ends. Transaction callbacks check before automatic commit, so cancellation received during SQL triggers protected rollback. If cancellation prevents delivery of a new transaction or savepoint handle, completed BEGIN or SAVEPOINT work is rolled back before releasing ownership. Once COMMIT has been sent, its server result is processed before cancellation propagates.

    If cancellation interrupts raw stream creation, the client releases its permit or hands the created stream to a background drain. An explicit caller protection defers cancellation to the caller's boundary. Task cancellation does not automatically send PostgreSQL cancel requests or retry operations.

    Use Client::abort() when a caller must enforce a hard local deadline and the physical connection may be discarded. Unlike a PostgreSQL cancel token, abort does not try to preserve the session, send Terminate, or perform a TLS close_notify exchange.

    #Configuration And TLS

    Config::Config accepts the host, optional concrete hostaddr, user, database, password, port, TLS settings, channel-binding policy, application name, startup options, connect timeout, and TCP keepalive settings. The Debug output for a config shows <hidden> in place of a configured password; a missing password remains None.

    TLS modes are:

    • SslMode::Disable: plaintext
    • SslMode::VerifyCa: TLS with certificate-chain validation
    • SslMode::VerifyFull: TLS with certificate-chain and endpoint identity validation; this is the default

    ssl_root_cert="system" uses the platform trust store and requires VerifyFull. A custom PEM CA file may be supplied instead. VerifyFull requires an explicit host or hostaddr.

    MD5 password authentication is not supported. Configure PostgreSQL for scram-sha-256 or cleartext password authentication over an appropriately protected connection.

    The package supports native and classic wasm; WasmGC is intentionally not supported because its socket/TLS transport is unavailable.

    #Errors

    Client-defined failures use ClientError. Important variants include database errors, authentication/TLS failures, wrong-type decoding, row-count mismatches, connection closure, and protocol failures. Low-level transport and I/O failures are not wrapped in ClientError; they propagate as their original error values.

    A fatal driver error becomes the terminal result for the active request and requests already accepted by the submission queue. Calls still waiting at the operation gate have not submitted a request; after the driver stops, they observe ClientError::Closed instead.

    ClientError::Database(err) and AsyncMessage::Notice(err) expose the same DatabaseError fields. All fields except message are independently optional; absent values are None, and an absent message defaults to "database error". Object fields need not appear together or refer to objects that currently exist.

    FieldsMeaning
    severity, severity_nonlocalizedLocalizable severity from S, nonlocalized severity from V
    code, message, detail, hintSQLSTATE, primary message, extra detail, suggested action
    schema, table, column, datatype, constraintAssociated object names; constraint can also name an index
    position, internal_positionOne-based character indices into the submitted query and internal_query
    internal_query, where_Internally generated SQL and error context (possibly multiline)
    file, line, routineServer source location

    position and internal_position are positive UInt values, preserved as supplied by the server. They count characters, including in Unicode queries; they are not byte offsets or MoonBit UTF-16 indices. line is an optional UInt. Malformed decimal values, overflow, zero positions, and invalid UTF-8 raise ProtocolError::InvalidInput in the shared parser.

    Previously severity could be overwritten by V depending on field order. It now preserves only S; use severity_nonlocalized for stable severity matching. Debug and Eq include every field. Query, transaction, COPY, and pool operations preserve the same structured fields.

    Branch on SQLSTATE and constraint name instead of parsing the message:

    ///|
    fn _is_email_conflict(error : Error) -> Bool {
    match error {
    @client.ClientError::Database(err) =>
    err.code == Some("23505") && err.constraint == Some("accounts_email_key")
    _ => false
    }
    }

    The field meanings follow the PostgreSQL error and notice protocol.

    FromSql

    pub(open) trait FromSql {
    fn from_sql(Type, WireFormat, BytesView) -> Self raise
    fn accepts(Type) -> Bool
    fn moonbit_type_name() -> String
    fn from_sql_null(Type, WireFormat) -> Self raise = _
    }

    Trait implemented by values that can be decoded from a result column.

    Row::get performs the accepts check before invoking from_sql, so a decoder can assume it only receives compatible PostgreSQL types unless the row was constructed manually in tests. For nullable columns, either decode into T? or implement from_sql_null directly.
    impl FromSql for Bool
    impl FromSql for Int
    impl FromSql for Int64
    impl FromSql for UInt
    impl FromSql for Float
    impl FromSql for Double
    impl FromSql for String
    impl FromSql for Option[T]
    impl FromSql for Bytes
    impl FromSql for Array[T]
    impl FromSql for Json

    ToSql

    pub(open) trait ToSql {
    fn format(Self, Type) -> WireFormat = _
    fn accepts(Self, Type) -> Bool
    fn moonbit_type_name(Self) -> String
    fn to_sql(Self, Type,
    Buffer
    ) ->
    IsNull
    raise
    }

    Trait implemented by values that can be encoded as query parameters.

    The driver asks three questions before sending a parameter:
    1. accepts: does the codec support the PostgreSQL target type?
    2. to_sql: write the actual payload or mark the value as NULL.
    3. format: should PostgreSQL receive WireFormat::Text or WireFormat::Binary data?

    The compatibility check happens before serialization, which keeps failures deterministic and improves error messages for callers. A minimal custom text codec usually overrides format to return WireFormat::Text, checks the target OID in accepts, writes UTF-8 bytes in to_sql, and returns IsNull::No. Built-in strings reject NUL (U+0000) with ClientError::Encode before UTF-8 encoding, including views, optional values, and array elements. After serialization, the driver rejects zero bytes in non-NULL text-format payloads, including custom codecs, with ClientError::Encode. NULL payloads are ignored; binary payloads are not scanned for zero bytes. These errors can occur after preparation but before Bind/Execute is sent.
    impl ToSql for Bool
    impl ToSql for Int
    impl ToSql for Int64
    impl ToSql for UInt
    impl ToSql for Float
    impl ToSql for Double
    impl ToSql for String
    impl ToSql for Option[T]
    impl ToSql for Bytes
    impl ToSql for Array[T]
    impl ToSql for Json
    impl ToSql for StringView

    ClientError

    pub suberror ClientError {
    Database(DatabaseError)
    Authentication(String)
    NotReady
    ExecutorAlreadyStarted
    Closed(String)
    StatementConnectionMismatch
    Protocol(String)
    InvalidConfig(String)
    InvalidSavepointName(String)
    Ssl(String)
    Encode(String)
    Decode(String)
    WrongType(WrongTypeError)
    UnsupportedRecursiveType(UInt)
    ColumnNotFound(String)
    RowCount(String)
    UnfinishedChildTransaction
    UnexpectedMessage(String)
    } derive(Eq,
    Debug
    )

    Top-level error union raised by client operations.

    The variants are intentionally grouped by failure source: database-originated failures, startup/authentication problems, connection lifecycle issues, protocol violations, codec errors, and higher-level query shape checks such as row-count assertions.

    AsyncMessage

    pub enum AsyncMessage {
    Notice(DatabaseError)
    Notification(Notification)
    ParameterStatus(String, String)
    } derive(Eq,
    Debug
    )

    Message delivered out-of-band from the main request/response flow.

    These messages are emitted by the private driver and can be consumed through Client::next_message.

    CancelToken

    type CancelToken

    Capability object used to send a PostgreSQL cancel request.

    PostgreSQL cancellation happens over a separate short-lived TCP connection, so the token stores the backend process ID and secret key learned during startup rather than a direct handle to the existing socket.

    CancelToken::cancel

    async fn CancelToken::cancel(self : CancelToken) -> Unit

    Send a PostgreSQL cancel request over a short-lived control connection.

    PostgreSQL requires cancellation to happen on a separate TCP connection that carries only the backend process ID and secret key from startup.

    CancelToken::process_id

    fn CancelToken::process_id(self : CancelToken) -> Int

    Return the backend process ID embedded in the cancellation token.

    ChannelBinding

    pub(all) enum ChannelBinding {
    Disable
    Prefer
    Require
    } derive(Eq,
    Debug
    )

    Controls whether SCRAM channel binding is disabled, preferred, or required.

    This setting controls whether the connection must complete SCRAM channel binding. It does not enable TLS by itself. It is also unrelated to TLS sslmode: this package still does not support the removed libpq-style TLS aliases sslmode=prefer or sslmode=require.

    Disable always uses plain SCRAM-SHA-256. Prefer uses SCRAM-SHA-256-PLUS when the TLS transport exposes a supported binding and the server advertises it, otherwise it falls back to plain SCRAM-SHA-256. Require insists on SCRAM-SHA-256-PLUS and fails when TLS or server support is missing. Because non-SCRAM authentication methods cannot employ PostgreSQL channel binding, Require rejects them before sending authentication credentials.

    Client

    type Client

    User-facing handle used to enqueue work onto the private driver.

    Client is intentionally lightweight. It does not own the socket and can be cloned freely by value because all mutable state lives inside Shared.

    Client::abort

    fn Client::abort(self : Client) -> Unit

    Immediately abandon this physical connection.

    Unlike close, abort does not wait behind active work or send PostgreSQL's graceful Terminate message. It synchronously rejects new work and requests driver cancellation; the driver closes the transport as cancellation unwinds. Active and queued operations fail with ClientError::Closed("connection aborted"). Repeated calls are safe.

    Client::batch_execute

    async fn Client::batch_execute(self : Client, sql : String) -> Unit

    Execute one or more SQL commands and discard any returned rows.

    This is the simplest helper for commands such as schema setup or session configuration where only protocol completion matters.

    Client::cancel_token

    fn Client::cancel_token(self : Client) -> CancelToken raise

    Create a token capable of cancelling the current backend operation.

    The token stays valid until the underlying connection closes. It can be stored and used from another task or timeout handler when a long-running query should be interrupted.

    Client::check_connection

    async fn Client::check_connection(self : Client) -> Unit

    Perform a cheap round trip that validates the connection is still usable.

    The method sends a bare Sync, waits for the matching ReadyForQuery, and therefore confirms that both the outbound request path and inbound response path are still alive.

    Client::clear_type_cache

    fn Client::clear_type_cache(self : Client) -> Unit

    Reset the shared type cache to the built-in PostgreSQL descriptors only.

    Use this when the application expects server-side type definitions to change and wants later queries to re-fetch catalog metadata lazily.

    Client::close

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

    Gracefully shut down the client runtime.

    Close is an operation barrier: it waits behind work already accepted by the FIFO gate, rejects later work, sends Terminate, and returns only after the private driver has closed the transport. Cancellation of the caller still propagates while executor failure after shutdown is ignored.

    Client::copy_in

    async fn Client::copy_in(self : Client, sql : String) -> CopyInSink

    Start a COPY ... FROM STDIN operation and return a sink for input chunks.

    The returned CopyInSink can be fed from another task while the connection loop relays backend progress and completion messages. Prefer with_copy_in to abort unfinished input on every callback exit.

    Client::copy_out

    async fn Client::copy_out(self : Client, sql : String) -> CopyOutStream

    Start a COPY ... TO STDOUT operation and return a raw chunk stream.

    The SQL string is executed with the simple query protocol because PostgreSQL starts COPY mode directly from the statement text. Prefer with_copy_out for automatic cleanup on every callback exit.

    Warning: if you stop consuming the returned stream early and want to discard it, call detach() before dropping the handle. Otherwise later requests on the same connection can remain blocked until the unfinished response is drained.

    Client::create

    fn Client::create(config : Config) -> (Client, ClientExecutor)

    Construct a client handle and its single-use executor without network I/O. Spawn executor.run() and await client.ready() before reading startup data. The asynchronous message buffer holds 256 entries by default.

    Client::create_with_async_message_capacity

    fn Client::create_with_async_message_capacity(config : Config, capacity : Int) -> (Client, ClientExecutor) raise

    Create a client with a bounded asynchronous message buffer. The oldest message is discarded when the buffer is full.

    Client::dropped_async_messages

    fn Client::dropped_async_messages(self : Client) -> Int64

    Number of asynchronous messages discarded from this client's own buffer.

    Client::execute

    async fn Client::execute(self : Client, sql : String, params? : Array[&ToSql]) -> UInt64

    Execute SQL and return the affected row count as UInt64.

    This convenience helper prepares a temporary statement, executes it once, waits for completion, and then closes the prepared statement. Missing, invalid, or overflowing counts in counted command tags raise ClientError::Protocol after the execution stream has been drained. If execution is cancelled, cleanup drains the results before closing the temporary statement, then propagates cancellation. This can wait for the current SQL command to finish so the connection remains reusable.

    Client::execute_raw

    async fn Client::execute_raw(self : Client, statement : Statement, params? : Array[&ToSql]) -> UInt64

    Execute a prepared statement and return the affected row count as UInt64.

    The result stream is drained internally so the command tag can be parsed and converted into a numeric row count. On cancellation, remaining results are drained before cancellation propagates, which may wait for the SQL command to finish. The caller retains ownership of the prepared statement. Missing, invalid, or overflowing counts in counted command tags raise ClientError::Protocol after the execution stream has been drained.

    Client::is_closed

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

    Return whether the shared runtime can no longer accept or perform work.

    Graceful close sets this after the driver exits. Abort sets it immediately; the driver then closes the physical transport while cancellation unwinds.

    Client::is_transaction_idle

    fn Client::is_transaction_idle(self : Client) -> Bool

    Return whether the latest ReadyForQuery reported an idle transaction.

    This is a snapshot of the most recent protocol response, not a live server check. It returns false before successful startup and after the client closes, even though the initial stored status is I.

    Client::next_message

    async fn Client::next_message(self : Client) -> AsyncMessage?

    Receive the next asynchronous message buffered by the private driver.

    Returns None after the connection closes.

    Client::parameter

    fn Client::parameter(self : Client, name : String) -> String? raise

    Look up a server parameter learned during startup or later async updates.

    Client::prepare

    async fn Client::prepare(self : Client, sql : String) -> Statement

    Prepare a statement and let PostgreSQL infer its parameter types.

    Client::prepare_typed

    async fn Client::prepare_typed(self : Client, sql : String, types : Array[Type]) -> Statement

    Prepare a statement with explicit parameter types.

    Explicit types are useful when PostgreSQL cannot infer parameter OIDs from context or when the caller wants deterministic server-side coercion.

    Client::query

    async fn Client::query(self : Client, sql : String, params? : Array[&ToSql]) -> RowStream

    Prepare and execute a query, returning rows through an incremental stream.

    This helper uses a temporary named prepared statement under the hood so it can fetch resolved parameter and column metadata before execution. The temporary statement is closed automatically once the returned stream is fully drained or finish is called. Prefer with_stream to also cover early return, callback errors, and cancellation.

    Warning: if you stop consuming the returned stream early and want to discard it, call detach() before dropping the handle. Otherwise later requests on the same connection can remain blocked until the unfinished response is drained.

    Client::query_one

    async fn Client::query_one(self : Client, sql : String, params? : Array[&ToSql]) -> Row

    Execute a query and require exactly one row.

    This helper fully drains the underlying stream so temporary resources are always cleaned up, even when the row-count assertion fails.

    Client::query_opt

    async fn Client::query_opt(self : Client, sql : String, params? : Array[&ToSql]) -> Row?

    Execute a query and require zero or one row.

    Client::query_statement

    async fn Client::query_statement(self : Client, statement : Statement, params? : Array[&ToSql]) -> RowStream

    Execute a previously prepared statement and return a row stream.

    Unlike query, this reuses a named prepared statement and therefore does not attach deferred cleanup to the returned stream.

    Client::query_typed

    async fn Client::query_typed(self : Client, sql : String, param_types : Array[Type], params? : Array[&ToSql]) -> RowStream

    Execute a query with explicit parameter types.

    This skips PostgreSQL's parameter-type inference path and is the most direct way to run an extended query when the caller already knows the desired OIDs.

    Client::ready

    async fn Client::ready(self : Client) -> Unit

    Wait for startup. All waiters observe the same result.

    Client::simple_query

    async fn Client::simple_query(self : Client, sql : String) -> SimpleQueryStream

    Execute a SQL string with PostgreSQL's simple query protocol.

    Use this when you need PostgreSQL's statement batching semantics or when you want to inspect raw text-format result frames through SimpleQueryStream. Prefer with_simple_query for automatic cleanup on every callback exit.

    Warning: if you stop consuming the returned stream early and want to discard it, call detach() before dropping the handle. Otherwise later requests on the same connection can remain blocked until the unfinished response is drained.

    Client::transaction

    async fn Client::transaction(self : Client, options? : TransactionOptions) -> Transaction

    Begin a transaction with PostgreSQL's default BEGIN settings or the given BEGIN options.

    Client::with_copy_in

    async fn[T] Client::with_copy_in(self : Client, sql : String, f : async (CopyInSink) -> T) -> T

    Produce COPY input in a cancellable callback and return its result.

    The callback must explicitly call sink.finish() to commit the COPY. An unfinished sink is aborted on return, error, or cancellation. Protected cleanup waits for protocol completion and preserves the callback's error.

    Client::with_copy_out

    async fn[T] Client::with_copy_out(self : Client, sql : String, f : async (CopyOutStream) -> T) -> T

    Consume COPY output in a cancellable callback and return its result. Finish the stream on every exit, with the same error and cancellation semantics as with_stream.

    Client::with_prepared

    async fn[T] Client::with_prepared(self : Client, sql : String, f : async (ScopedStatement) -> T) -> T

    Prepare a statement, run one callback, and close it on every exit.

    Client::with_prepared_typed

    async fn[T] Client::with_prepared_typed(self : Client, sql : String, types : Array[Type], f : async (ScopedStatement) -> T) -> T

    Like with_prepared, with explicit parameter types.

    Client::with_simple_query

    async fn[T] Client::with_simple_query(self : Client, sql : String, f : async (SimpleQueryStream) -> T) -> T

    Consume simple-query frames in a cancellable callback and return its result. Finish the stream on every exit, with the same error and cancellation semantics as with_stream.

    Client::with_statement_stream

    async fn[T] Client::with_statement_stream(self : Client, statement : Statement, params? : Array[&ToSql], f : async (RowStream) -> T) -> T

    Execute a prepared statement with the cleanup semantics of with_stream. Only this execution stream is finished; the caller retains the statement.

    Client::with_stream

    async fn[T] Client::with_stream(self : Client, sql : String, params? : Array[&ToSql], f : async (RowStream) -> T) -> T

    Execute a query in a scope and return the callback's result.

    The callback remains cancellable. On return, error, or cancellation, finish the stream and close its temporary statement under cancellation protection. Cleanup errors propagate on success, but never replace a callback error or cancellation. Cleanup waits for ReadyForQuery and does not send CancelRequest.

    Client::with_transaction

    async fn[T] Client::with_transaction(self : Client, f : async (Transaction) -> T, options? : TransactionOptions) -> T

    Run one callback inside a transaction and auto-complete it.

    On normal return, the transaction is committed if the callback did not already finish it explicitly. Unfinished row streams are discarded and drained before completion. An unfinished nested transaction is rolled back and raises UnfinishedChildTransaction. If the callback raises or is cancelled, the transaction is rolled back best-effort instead.

    Client::with_typed_stream

    async fn[T] Client::with_typed_stream(self : Client, sql : String, param_types : Array[Type], params? : Array[&ToSql], f : async (RowStream) -> T) -> T

    Like with_stream, with explicit PostgreSQL parameter types.

    ClientExecutor

    type ClientExecutor

    Owns the socket and all background work for a single client. Spawn run once, then await Client::ready before using startup metadata.

    ClientExecutor::run

    async fn ClientExecutor::run(self : ClientExecutor) -> Unit

    Connect, authenticate, and drive the socket until shutdown. A second invocation fails even after the first has returned.

    Column

    pub struct Column {
    name : String
    table_oid : UInt
    column_id : Int
    type_ : Type
    type_size : Int
    type_modifier : Int
    format : WireFormat
    } derive(Eq,
    Debug
    )

    Metadata describing one result column.

    A Column combines protocol-level row-description fields with the driver's resolved Type descriptor so higher-level code can inspect both raw wire details and semantic type information.

    Config

    pub struct Config {
    host : String
    hostaddr : String?
    port : Int
    user : String
    database : String
    password : String?
    ssl_mode : SslMode
    ssl_root_cert : String?
    channel_binding : ChannelBinding
    application_name : String
    options : String?
    connect_timeout_ms : Int?
    keepalives : Bool?
    keepalives_idle_s : Int?
    } derive(Eq)

    Immutable configuration supplied to Client::create.

    The returned ClientExecutor::run uses this value to establish a PostgreSQL session. The client runtime retains it so that later features, such as cancellation, can reopen a control connection with the exact same host and port. Its Debug representation replaces a configured password with <hidden>.
    impl Debug for Config

    Config::Config

    fn Config::Config(host : String, hostaddr? : String, port? : Int, user~ : String, database? : String, password? : String, ssl_mode? : SslMode, ssl_root_cert? : String, channel_binding? : ChannelBinding, application_name~ : String, options? : String, connect_timeout_ms? : Int, keepalives? : Bool, keepalives_idle_s? : Int) -> Config

    Build a configuration with PostgreSQL-friendly defaults.

    Defaults are chosen to keep secure TLS on the main path: port 5432, ssl_mode = SslMode::VerifyFull, and database name equal to the user name. Callers pass application_name explicitly.

    Config::new_internal

    #internal(internal, "for internal use only")
    fn Config::new_internal(host : String, hostaddr : String?, port : Int, user : String, database : String, password : String?, ssl_mode : SslMode, ssl_root_cert : String?, channel_binding : ChannelBinding, application_name : String, options : String?, connect_timeout_ms : Int?, keepalives : Bool?, keepalives_idle_s : Int?) -> Config

    Internal constructor for a single PostgreSQL connection target.

    Used by pgpool to build one client config per endpoint. Callers supply every field explicitly; no defaults or validation are applied here.

    CopyInSink

    type CopyInSink

    Client-side sink for an active COPY ... FROM STDIN operation.

    The sink feeds CopyInAction values to the connection loop while the paired response queue waits for the backend's completion status.

    CopyInSink::abort

    async fn CopyInSink::abort(self : CopyInSink, message? : String) -> Unit

    Abort the COPY IN operation with an error message.

    PostgreSQL still sends a completion sequence after an abort. The driver drains it internally and suppresses the returned row count because the COPY did not complete successfully. Unexpected transport or protocol errors still propagate to the caller.

    CopyInSink::finish

    async fn CopyInSink::finish(self : CopyInSink) -> UInt64

    Finish the COPY IN operation and return the affected row count as UInt64.

    After calling finish, the sink cannot be reused. The method waits for the backend's command-complete sequence and converts its command tag to an UInt64 row count. Invalid or overflowing counts raise ClientError::Protocol after completion has been drained and the operation permit released.

    CopyInSink::send

    async fn CopyInSink::send(self : CopyInSink, data : BytesView) -> Unit

    Send one COPY data chunk to the server.

    CopyOutStream

    type CopyOutStream

    Stream of raw chunks produced by COPY ... TO STDOUT.

    The client does not attempt to decode the copy format. It simply surfaces each CopyData payload exactly as received so higher-level code can parse CSV, text, or binary COPY output as needed.

    CopyOutStream::collect

    async fn CopyOutStream::collect(self : CopyOutStream) -> Array[Bytes]

    Collect all remaining COPY OUT chunks into memory.

    CopyOutStream::detach

    fn CopyOutStream::detach(self : CopyOutStream) -> Unit

    Explicitly abandon the remaining COPY OUT payloads.

    This starts a background drain that discards CopyData frames so later requests can continue without waiting for synchronous completion.

    CopyOutStream::finish

    async fn CopyOutStream::finish(self : CopyOutStream) -> Unit

    Drain the COPY OUT stream to its terminal ReadyForQuery. After detach, wait for the drain and report its connection closure or database error. If no background task could start, drain the remaining responses here.

    CopyOutStream::formats

    Return a read-only view of the COPY OUT column wire formats.

    CopyOutStream::next

    async fn CopyOutStream::next(self : CopyOutStream) -> Bytes?

    Read the next COPY OUT payload chunk.

    DatabaseError

    pub struct DatabaseError {
    severity : String?
    severity_nonlocalized : String?
    code : String?
    message : String
    detail : String?
    hint : String?
    schema : String?
    table : String?
    column : String?
    datatype : String?
    constraint : String?
    position : UInt?
    internal_position : UInt?
    internal_query : String?
    where_ : String?
    file : String?
    line : UInt?
    routine : String?
    } derive(Eq,
    Debug
    )

    PostgreSQL ErrorResponse and NoticeResponse fields.

    Optional fields are independent: one object name does not imply another. Positions preserve PostgreSQL's one-based character indices, not byte or UTF-16 offsets. Unknown protocol tags are ignored.

    Field

    pub struct Field {
    name : String
    type_ : Type
    } derive(Eq,
    Debug
    )

    Field descriptor returned through Kind::Composite.

    IsolationLevel

    pub(all) enum IsolationLevel {
    ReadUncommitted
    ReadCommitted
    RepeatableRead
    Serializable
    } derive(Eq,
    Debug
    )

    PostgreSQL transaction isolation level accepted by TransactionOptions.

    Kind

    pub enum Kind {
    Simple
    Array(Type)
    Enum(ArrayView[String])
    Composite(ArrayView[Field])
    Domain(Type)
    Range(Type)
    Pseudo
    Unknown
    } derive(Eq,
    Debug
    )

    Classifies the structural shape of a PostgreSQL type.

    Most user-facing codecs only care whether a type is a simple scalar or an array, but the richer variants let the client cache enough metadata for advanced introspection and future extension points. Inspect this read-only classification through type_.kind.

    Notification

    pub struct Notification {
    process_id : Int
    channel : String
    payload : String
    } derive(Eq,
    Debug
    )

    Payload of PostgreSQL NotificationResponse.

    Portal

    type Portal

    A bound portal usable only during its with_portal callback. Fetch multiple windows with with_stream or execute to completion with execute. The scope waits for started calls before closing the portal. Database operations through a saved handle report Closed after its callback ends.

    Portal::columns

    fn Portal::columns(self : Portal) -> ArrayView[Column]

    Return a read-only view of the portal's result columns.

    Portal::execute

    async fn Portal::execute(self : Portal) -> UInt64

    Execute this portal to completion and return its affected row count as UInt64.

    Portal::with_stream

    async fn[T] Portal::with_stream(self : Portal, max_rows : Int, f : async (RowStream) -> T) -> T

    Fetch rows from this portal, finishing the stream on every exit. The portal remains available for later fetches within its callback. A zero row limit fetches all remaining rows; QuerySummary.suspended reports when PostgreSQL stops at a row limit.

    QuerySummary

    pub struct QuerySummary {
    command_tag : String
    row_count : UInt64
    suspended : Bool
    } derive(Eq,
    Debug
    )

    Final summary reported after an extended query stream is fully drained.

    Row

    One fully materialized row from an extended query.

    The row stores its column metadata alongside the raw field payloads so each get call can validate the target type, decode on demand, and still support low-level access through get_raw.

    Row::columns

    fn Row::columns(self : Row) -> ArrayView[Column]

    Return a read-only view of the row's column metadata.

    Row::get

    fn[T : FromSql] Row::get(self : Row, index : Int) -> T raise

    Decode the field at index as T.

    The compatibility check happens before any decoder code runs, so a custom FromSql implementation can rely on receiving a PostgreSQL type it claimed to support.

    Row::get_name

    fn[T : FromSql] Row::get_name(self : Row, name : String) -> T raise

    Decode the named column as T.

    Row::get_raw

    fn Row::get_raw(self : Row, index : Int) -> Bytes?

    Return the raw field bytes at index.

    This is the escape hatch for callers that want to perform custom decoding or inspect values that do not yet have a FromSql implementation.

    Row::get_raw_name

    fn Row::get_raw_name(self : Row, name : String) -> Bytes? raise

    Return the raw field bytes for the named column.

    Row::get_text

    fn Row::get_text(self : Row, index : Int) -> String? raise

    Decode the field at index as nullable text.

    This is shorthand for self.get[String?](index) and therefore preserves SQL NULL as None.

    Row::get_text_name

    fn Row::get_text_name(self : Row, name : String) -> String? raise

    Decode the named field as nullable text.

    Row::index_of

    fn Row::index_of(self : Row, name : String) -> Int?

    Return the index of the named column, if present.

    Column matching is exact and scans from left to right, so the first matching name wins when a query produces duplicate column labels.

    Row::len

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

    Return the number of values in this row.

    Row::try_get

    fn[T : FromSql] Row::try_get(self : Row, index : Int) -> T? raise

    Decode the field at index as T, returning None when out of range.

    Row::try_get_name

    fn[T : FromSql] Row::try_get_name(self : Row, name : String) -> T? raise

    Decode the named column as T, returning None when the column is absent.

    Row::values

    fn Row::values(self : Row) -> ArrayView[Bytes?]

    Return a read-only view of the row's raw field payloads.

    RowStream

    type RowStream

    Incremental stream of rows produced by the extended query protocol.

    The stream owns the queue of backend messages for one request. Temporary statements created by helper APIs are cleaned up only after the stream reaches its terminal ReadyForQuery, so callers should either consume the stream to exhaustion, call finish, or call detach. A transaction also drains any registered stream before its own commit or rollback.

    RowStream::collect

    async fn RowStream::collect(self : RowStream) -> Array[Row]

    Collect all remaining rows into an array.

    This is a convenience wrapper over repeated next calls. It still drains the underlying protocol stream and therefore runs the same cleanup logic as manual iteration.

    RowStream::columns

    fn RowStream::columns(self : RowStream) -> ArrayView[Column]

    Return the latest resolved column metadata as a read-only view. Obtain a new view after advancing the stream to see a later description.

    RowStream::detach

    fn RowStream::detach(self : RowStream) -> Unit

    Explicitly abandon the remaining rows and drain in the background.

    Unlike finish, this returns immediately and does not decode discarded row payloads. Use it when later requests should be allowed to make progress without waiting for synchronous completion of the current stream.

    RowStream::finish

    async fn RowStream::finish(self : RowStream) -> QuerySummary

    Drain the stream and return the final query summary.

    Use this when you only need a prefix of the rows but still want the driver to observe the terminal ReadyForQuery, release backpressure, and execute deferred cleanup such as closing temporary statements. After detach, this waits for the drain and reports any saved connection closure or database error. If no background task could start, it drains here.

    RowStream::next

    async fn RowStream::next(self : RowStream) -> Row?

    Read the next row from the stream.

    On the terminal ReadyForQuery, the stream records its summary, runs any deferred cleanup, and then either raises the captured database error or returns None.

    RowStream::one

    async fn RowStream::one(self : RowStream) -> Row

    Require exactly one row and fully drain the stream.

    RowStream::opt

    async fn RowStream::opt(self : RowStream) -> Row?

    Require zero or one row and fully drain the stream.

    ScopedStatement

    type ScopedStatement

    A prepared statement usable only during its with_prepared callback. Keep operations inside this handle so streams finish before the statement is closed. A saved handle reports Closed after its callback ends.

    ScopedStatement::columns

    Return a read-only view of the scoped statement's result columns.

    ScopedStatement::execute

    async fn ScopedStatement::execute(self : ScopedStatement, params? : Array[&ToSql]) -> UInt64

    Execute this prepared statement and return its affected row count as UInt64.

    ScopedStatement::params

    Return a read-only view of the scoped statement's parameter types.

    ScopedStatement::with_portal

    async fn[T] ScopedStatement::with_portal(self : ScopedStatement, params? : Array[&ToSql], f : async (Portal) -> T) -> T

    Bind a portal from a transaction-owned scoped statement and close it on every callback exit. The statement remains usable within its own scope.

    ScopedStatement::with_stream

    async fn[T] ScopedStatement::with_stream(self : ScopedStatement, params? : Array[&ToSql], f : async (RowStream) -> T) -> T

    Execute one statement stream and finish it before this call returns.

    SimpleQueryMessage

    pub enum SimpleQueryMessage {
    RowDescription(ArrayView[String])
    Row(SimpleQueryRow)
    CommandComplete(String)
    } derive(Eq,
    Debug
    )

    One frame emitted by the simple query protocol.

    The protocol can interleave row descriptions, rows, and command-complete messages when a SQL string contains multiple statements, so the stream surfaces all three explicitly.

    SimpleQueryRow

    type SimpleQueryRow derive(Eq,
    Debug
    )

    One row emitted by SimpleQueryStream.

    Simple-query rows always carry text-format values because PostgreSQL's simple query protocol does not support selecting per-column binary formats.

    SimpleQueryRow::columns

    fn SimpleQueryRow::columns(self : SimpleQueryRow) -> ArrayView[String]

    Return a read-only view of the column labels for this result row.

    SimpleQueryRow::get

    fn SimpleQueryRow::get(self : SimpleQueryRow, index : Int) -> String?

    Return the text value at index.

    SimpleQueryRow::get_name

    fn SimpleQueryRow::get_name(self : SimpleQueryRow, name : String) -> String? raise

    Return the text value for the named column.

    SimpleQueryRow::index_of

    fn SimpleQueryRow::index_of(self : SimpleQueryRow, name : String) -> Int?

    Return the index of the named column, if present.

    SimpleQueryRow::len

    fn SimpleQueryRow::len(self : SimpleQueryRow) -> Int

    Return the number of values in this row.

    SimpleQueryRow::values

    fn SimpleQueryRow::values(self : SimpleQueryRow) -> ArrayView[String?]

    Return a read-only view of this result row's text values.

    SimpleQueryStream

    type SimpleQueryStream

    Incremental stream of SimpleQueryMessage values.

    SimpleQueryStream::collect

    Collect all remaining simple-query messages.

    SimpleQueryStream::detach

    fn SimpleQueryStream::detach(self : SimpleQueryStream) -> Unit

    Explicitly abandon the remaining simple-query frames.

    The driver drains to the terminal ReadyForQuery in a background coroutine so later requests can continue without waiting for synchronous completion.

    SimpleQueryStream::finish

    async fn SimpleQueryStream::finish(self : SimpleQueryStream) -> Unit

    Drain the simple-query stream. After detach, wait for the drain and report its connection closure or database error. If no background task could start, drain the remaining responses here.

    SimpleQueryStream::next

    Read the next simple-query message.

    The stream records database errors when it sees ErrorResponse, but delays raising them until the terminal ReadyForQuery so the server and driver stay in sync with the protocol.

    SslMode

    pub(all) enum SslMode {
    Disable
    VerifyCa
    VerifyFull
    } derive(Eq,
    Debug
    )

    Controls how the client negotiates TLS when opening a TCP connection.

    The modes are intentionally strict: plaintext is opt-in via Disable, while TLS-using callers choose between certificate-chain verification with or without endpoint identity checks.

    Statement

    type Statement

    Prepared statement stored on the PostgreSQL server.

    A statement belongs to its creating connection and may be reused across transactions on that connection. Read-only metadata views expose the server-inferred parameter and result types so callers can inspect what PostgreSQL resolved during preparation.

    Statement::close

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

    Close the prepared statement on the server.

    Closing is idempotent on the client side; repeated calls are ignored after the first successful close.

    Statement::columns

    fn Statement::columns(self : Statement) -> ArrayView[Column]

    Return a read-only view of the statement's result columns.

    Statement::params

    fn Statement::params(self : Statement) -> ArrayView[Type]

    Return a read-only view of the server-inferred parameter types.

    Transaction

    type Transaction

    Transaction handle that enforces single-use commit or rollback semantics.

    Nested transactions are implemented with savepoints. The same handle shape is reused for the top-level transaction and for nested savepoint scopes.

    Transaction::batch_execute

    async fn Transaction::batch_execute(self : Transaction, sql : String) -> Unit

    Execute one or more SQL commands within this transaction.

    Transaction::close_statement

    async fn Transaction::close_statement(self : Transaction, statement : Statement) -> Unit

    Close a prepared statement without leaving this transaction's operation scope.

    Calling Statement::close directly while the transaction is open would wait for the transaction's outer client permit. This scoped variant uses the transaction capability that already owns that permit.

    Transaction::commit

    async fn Transaction::commit(self : Transaction) -> Unit

    Finish child streams, then commit this transaction or release its savepoint. An unfinished child transaction is rolled back and raises UnfinishedChildTransaction after this transaction is rolled back.

    Transaction::execute

    async fn Transaction::execute(self : Transaction, sql : String, params? : Array[&ToSql]) -> UInt64

    Execute SQL within this transaction and return the affected row count as UInt64.

    Transaction::execute_raw

    async fn Transaction::execute_raw(self : Transaction, statement : Statement, params? : Array[&ToSql]) -> UInt64

    Execute a previously prepared statement and return its affected row count as UInt64.

    Transaction::prepare

    async fn Transaction::prepare(self : Transaction, sql : String) -> Statement

    Prepare a statement while the transaction is open.

    Transaction::prepare_typed

    async fn Transaction::prepare_typed(self : Transaction, sql : String, types : Array[Type]) -> Statement

    Prepare a statement with explicit parameter types while the transaction is open.

    Transaction::query

    async fn Transaction::query(self : Transaction, sql : String, params? : Array[&ToSql]) -> RowStream

    Execute a query within this transaction scope. If left unfinished, its remaining rows and temporary statement are cleaned up at transaction end.

    Transaction::query_statement

    async fn Transaction::query_statement(self : Transaction, statement : Statement, params? : Array[&ToSql]) -> RowStream

    Execute a previously prepared statement within this transaction scope. An unfinished result is drained at transaction end; the statement remains owned by the caller.

    Transaction::rollback

    async fn Transaction::rollback(self : Transaction) -> Unit

    Finish child streams and recursively roll back child transactions, then roll back this transaction or to its savepoint.

    Transaction::savepoint

    async fn Transaction::savepoint(self : Transaction, name : String) -> Transaction

    Create a named savepoint while the transaction is open. The name is a case-sensitive quoted identifier. Empty names and NUL characters are rejected.

    Transaction::transaction

    async fn Transaction::transaction(self : Transaction) -> Transaction

    Start a nested transaction backed by a PostgreSQL savepoint.

    The returned handle behaves like a transaction, but commit releases the savepoint and rollback rolls back to it before releasing it.

    Transaction::with_portal

    async fn[T] Transaction::with_portal(self : Transaction, statement : Statement, params? : Array[&ToSql], f : async (Portal) -> T) -> T

    Bind a portal, run one callback, and close the portal on every exit. The caller retains the statement and may reuse it after this scope ends. Explicit commit or rollback reports an error until this scope has finished.

    Transaction::with_prepared

    async fn[T] Transaction::with_prepared(self : Transaction, sql : String, f : async (ScopedStatement) -> T) -> T

    Prepare a transaction-owned statement and close it within the transaction. Explicit commit or rollback reports an error until this scope has finished.

    Transaction::with_prepared_typed

    async fn[T] Transaction::with_prepared_typed(self : Transaction, sql : String, types : Array[Type], f : async (ScopedStatement) -> T) -> T

    Like with_prepared, with explicit parameter types.

    Transaction::with_savepoint

    async fn[T] Transaction::with_savepoint(self : Transaction, name : String, f : async (Transaction) -> T) -> T

    Like with_transaction, using a named PostgreSQL savepoint. The name is a case-sensitive quoted identifier; spaces, quotes, and punctuation are safe.

    Transaction::with_statement_stream

    async fn[T] Transaction::with_statement_stream(self : Transaction, statement : Statement, params? : Array[&ToSql], f : async (RowStream) -> T) -> T

    Scope one prepared statement execution stream. The statement remains owned by the caller and must be closed separately.

    Transaction::with_stream

    async fn[T] Transaction::with_stream(self : Transaction, sql : String, params? : Array[&ToSql], f : async (RowStream) -> T) -> T

    Execute a query in a cancellable callback. Discard remaining rows and wait for protocol cleanup before returning, including on error or cancellation.

    Transaction::with_transaction

    async fn[T] Transaction::with_transaction(self : Transaction, f : async (Transaction) -> T) -> T

    Run a callback inside a savepoint-backed child transaction. The callback stays cancellable. An open child is committed on success and rolled back on error or cancellation; its own unfinished children follow the same rules.

    TransactionOptions

    pub struct TransactionOptions {
    isolation_level : IsolationLevel?
    read_only : Bool?
    deferrable : Bool?
    } derive(Eq,
    Debug
    )

    Options for the BEGIN command used when opening a transaction.

    TransactionOptions::TransactionOptions

    fn TransactionOptions::TransactionOptions(isolation_level? : IsolationLevel, read_only? : Bool, deferrable? : Bool) -> TransactionOptions

    Build transaction options for a future BEGIN command.

    Type

    pub struct Type {
    oid : UInt
    name : String
    schema : String?
    kind : Kind
    } derive(Eq,
    Debug
    )

    Stable description of a PostgreSQL type known to the client.

    Built-in descriptors are created eagerly for common scalar and array types. Less common server-defined types are resolved lazily through catalog queries and then cached in the shared client state. Equality compares schema and complete child descriptors as well as OID and name. Cyclic catalog metadata raises ClientError::UnsupportedRecursiveType rather than forming a cycle.

    Type::bool

    fn Type::bool() -> Type

    Return the built-in PostgreSQL bool type descriptor.

    Type::bool_array

    fn Type::bool_array() -> Type

    Return the built-in PostgreSQL bool[] type descriptor.

    Type::bytea

    fn Type::bytea() -> Type

    Return the built-in PostgreSQL bytea type descriptor.

    Type::bytea_array

    fn Type::bytea_array() -> Type

    Return the built-in PostgreSQL bytea[] type descriptor.

    Type::char

    fn Type::char() -> Type

    Return the built-in PostgreSQL char type descriptor.

    Type::date

    fn Type::date() -> Type

    Return the built-in PostgreSQL date type descriptor.

    Type::date_array

    fn Type::date_array() -> Type

    Return the built-in PostgreSQL date[] type descriptor.

    Type::float4

    fn Type::float4() -> Type

    Return the built-in PostgreSQL float4 type descriptor.

    Type::float4_array

    fn Type::float4_array() -> Type

    Return the built-in PostgreSQL float4[] type descriptor.

    Type::float8

    fn Type::float8() -> Type

    Return the built-in PostgreSQL float8 type descriptor.

    Type::float8_array

    fn Type::float8_array() -> Type

    Return the built-in PostgreSQL float8[] type descriptor.

    Type::int2

    fn Type::int2() -> Type

    Return the built-in PostgreSQL int2 type descriptor.

    Type::int2_array

    fn Type::int2_array() -> Type

    Return the built-in PostgreSQL int2[] type descriptor.

    Type::int4

    fn Type::int4() -> Type

    Return the built-in PostgreSQL int4 type descriptor.

    Type::int4_array

    fn Type::int4_array() -> Type

    Return the built-in PostgreSQL int4[] type descriptor.

    Type::int8

    fn Type::int8() -> Type

    Return the built-in PostgreSQL int8 type descriptor.

    Type::int8_array

    fn Type::int8_array() -> Type

    Return the built-in PostgreSQL int8[] type descriptor.

    Type::json

    fn Type::json() -> Type

    Return the built-in PostgreSQL json type descriptor.

    Type::json_array

    fn Type::json_array() -> Type

    Return the built-in PostgreSQL json[] type descriptor.

    Type::jsonb

    fn Type::jsonb() -> Type

    Return the built-in PostgreSQL jsonb type descriptor.

    Type::jsonb_array

    fn Type::jsonb_array() -> Type

    Return the built-in PostgreSQL jsonb[] type descriptor.

    Type::name_type

    fn Type::name_type() -> Type

    Return the built-in PostgreSQL name type descriptor.

    Type::oid_type

    fn Type::oid_type() -> Type

    Return the built-in PostgreSQL oid type descriptor.

    Type::text

    fn Type::text() -> Type

    Return the built-in PostgreSQL text type descriptor.

    Type::text_array

    fn Type::text_array() -> Type

    Return the built-in PostgreSQL text[] type descriptor.

    Type::time

    fn Type::time() -> Type

    Return the built-in PostgreSQL time type descriptor.

    Type::timestamp

    fn Type::timestamp() -> Type

    Return the built-in PostgreSQL timestamp type descriptor.

    Type::timestamp_array

    fn Type::timestamp_array() -> Type

    Return the built-in PostgreSQL timestamp[] type descriptor.

    Type::timestamptz

    fn Type::timestamptz() -> Type

    Return the built-in PostgreSQL timestamptz type descriptor.

    Type::unknown

    fn Type::unknown(oid : UInt, name? : String) -> Type

    Construct an unresolved placeholder descriptor with no schema.

    Unknown descriptors let the client carry OID information through error messages and metadata paths even when it has not yet queried PostgreSQL's system catalogs for richer shape information.

    Type::uuid

    fn Type::uuid() -> Type

    Return the built-in PostgreSQL uuid type descriptor.

    Type::uuid_array

    fn Type::uuid_array() -> Type

    Return the built-in PostgreSQL uuid[] type descriptor.

    Type::varchar

    fn Type::varchar() -> Type

    Return the built-in PostgreSQL varchar type descriptor.

    Type::varchar_array

    fn Type::varchar_array() -> Type

    Return the built-in PostgreSQL varchar[] type descriptor.

    WireFormat

    pub(all) enum WireFormat {
    Text
    Binary
    } derive(Eq,
    Debug
    )

    PostgreSQL wire-format selection for parameters and result columns.

    WrongTypeError

    pub struct WrongTypeError {
    moonbit_type : String
    postgres_type : Type
    } derive(Eq,
    Debug
    )

    Describes an early type mismatch detected by the driver.

    The client validates encoder and decoder compatibility before invoking the user-provided ToSql or FromSql implementation. That keeps failures deterministic and prevents custom codec code from seeing obviously incompatible PostgreSQL types.