#Postgres Client

    An async PostgreSQL client for MoonBit.

    If you want to start using this package quickly, the core flow is:

    1. build a Config
    2. call connect
    3. start connection.run() in a background task
    4. use client.query, client.query_one, client.execute, or client.transaction
    5. call client.close() when finished

    #Quick Start

    This is the smallest complete usage pattern:

    ///|
    async fn _quick_start() -> Unit {
    @async.with_task_group(group => {
    let config = @client.Config::new(
    "localhost",
    user="postgres",
    database="app",
    password="secret",
    port=5432,
    ssl_mode=VerifyFull,
    application_name="my-service",
    )
    let (client, connection) = @client.connect(config)
    group.spawn_bg(() => connection.run())

    let current_user : String = client
    .query_one("select current_user::text as current_user")
    .get_name("current_user")
    ignore(current_user)

    client.close()
    })
    }

    The line group.spawn_bg(() => connection.run()) is mandatory in normal use. Client only enqueues work. Connection::run is the task that actually keeps the socket moving, reads PostgreSQL messages, and delivers responses back to query streams.

    #Choose The Right API

    These are the main entry points and the situations they are designed for:

    NeedAPIUse it when
    Exactly one rowClient::query_oneA lookup must succeed once and only once, such as fetching by primary key or reading one aggregate row
    Zero or one rowClient::query_optThe row may be absent, such as optional profile/settings records
    Many rows or incremental consumptionClient::queryYou want a RowStream and may stop early or process rows as they arrive
    Affected row countClient::executeinsert, update, delete, or other parameterized commands where row data is not needed
    Raw SQL batch with no parametersClient::batch_executeSchema setup, BEGIN / COMMIT, temp tables, session settings, or other statement batches
    Reuse SQL many timesClient::prepare + Client::query_statement / Client::execute_rawThe same SQL text is executed repeatedly and you want PostgreSQL to keep a named prepared statement
    Fetch rows in chunksClient::bind + Client::query_portalLarge result sets where you want explicit fetch windows instead of collecting everything
    Multiple statements with atomicityClient::transaction or Client::with_transactionBusiness logic must commit or roll back as one unit
    PostgreSQL simple protocolClient::simple_queryMultiple statements in one SQL string, or direct access to text-format frames
    Bulk import / exportClient::copy_in / Client::copy_outHigh-volume streaming I/O with PostgreSQL COPY

    #Connection Config And Lifecycle

    Create a config with Config::new:

    ///|
    fn _config_example() -> @client.Config {
    @client.Config::new(
    "localhost",
    user="postgres",
    database="app",
    password="secret",
    port=5432,
    ssl_mode=VerifyFull,
    application_name="my-service",
    )
    }

    Config stores:

    • host: PostgreSQL host name or IP
    • hostaddr: optional concrete socket address when TCP routing should differ from certificate identity
    • port: PostgreSQL port, default 5432
    • user: login role
    • database: database name, default is the same as user
    • password: optional in the config, but required if the server selects cleartext password or SCRAM authentication
    • ssl_mode: SslMode::Disable, SslMode::VerifyCa, or SslMode::VerifyFull
    • ssl_root_cert: custom CA file, or "system" for the platform trust store
    • channel_binding: SCRAM channel-binding policy; Require rejects non-SCRAM authentication
    • application_name: value visible in PostgreSQL session metadata
    • options: optional PostgreSQL startup options string
    • connect_timeout_ms: optional end-to-end connect timeout
    • keepalives, keepalives_idle_s: TCP keepalive settings

    Use the SslMode constructors directly:

    • SslMode::Disable: never attempt TLS
    • SslMode::VerifyCa: require TLS and verify the certificate chain, but not the hostname or IP
    • SslMode::VerifyFull: require TLS, verify the certificate chain, and verify the hostname or IP

    #TLS Configuration

    The client now keeps only three TLS modes:

    • Disable: explicit plaintext. No encryption, no certificate validation, no server identity check.
    • VerifyCa: TLS is mandatory. Certificate-chain validation is required, but hostname or IP validation is skipped.
    • VerifyFull: TLS is mandatory. Certificate-chain validation and hostname or IP validation are both required.

    The removed prefer and require modes were easy to misread as "safe enough" while still leaving room for confusing downgrade and identity-checking behavior. The current surface keeps plaintext opt-in and makes the verification model visible in the API.

    Threat-model difference:

    • VerifyCa protects against an untrusted or forged certificate chain, but it still accepts any certificate from a trusted CA, even if it was issued for a different hostname.
    • VerifyFull adds endpoint identity checking, so the certificate must match the requested hostname or IP address. This is the default and recommended mode.

    sslrootcert=system follows libpq's stricter system-trust-store behavior in this package: it selects the platform trust store and requires ssl_mode = SslMode::VerifyFull. We reject weaker modes instead of silently accepting a configuration that looks stronger than it is.

    Additional TLS fields:

    • ssl_root_cert: custom CA file, or "system" for the platform trust store

    Default behavior:

    • VerifyFull requires an explicit host or hostaddr. If both are absent, the connection fails before the TLS handshake starts.

    Windows notes:

    • Disable, VerifyCa, and VerifyFull still work.
    • Custom sslrootcert files are handled by moonbitlang/async/tls.

    External references:

    Currently unsupported libpq TLS parameters:

    #TLS Migration

    • Replace prefer with verify_full when you want authenticated TLS, or with disable when plaintext is intentional.
    • Replace require with verify_full when the server certificate should match the target hostname or IP. Use verify_ca only when hostname validation is intentionally not part of the deployment model.
    • When translating libpq-style sslmode values, use only disable, verify-ca, and verify-full.
    • VerifyFull now requires an explicit host or hostaddr. Old code paths that relied on an implicit local default host become configuration errors.

    Examples:

    ///|
    fn _tls_examples() -> (@client.Config, @client.Config) {
    let direct = @client.Config::new(
    "db.example",
    user="postgres",
    database="app",
    password="secret",
    ssl_mode=VerifyFull,
    application_name="my-service",
    )
    let routed = @client.Config::new(
    "db.example",
    hostaddr="10.0.0.15",
    user="postgres",
    database="app",
    password="secret",
    ssl_mode=VerifyFull,
    ssl_root_cert="/etc/postgres/root.crt",
    application_name="my-service",
    )
    (direct, routed)
    }

    #SCRAM Channel Binding

    channel_binding controls whether SCRAM channel binding is disabled, preferred, or required. It does not enable TLS on its own.

    MD5 password authentication is deprecated by PostgreSQL and is not supported by this client. If the server requests AuthenticationMD5Password, connect fails during startup with ClientError::Authentication.

    This is not the same setting as TLS sslmode. The repository still does not support the removed libpq-style TLS aliases sslmode=prefer and sslmode=require. The Prefer and Require names here refer to SCRAM channel-binding policy, not TLS negotiation.

    • ChannelBinding::Disable: always use plain SCRAM-SHA-256
    • ChannelBinding::Prefer: use SCRAM-SHA-256-PLUS when TLS exposes a binding PostgreSQL can use, otherwise fall back to plain SCRAM-SHA-256
    • ChannelBinding::Require: fail unless SCRAM-SHA-256-PLUS is available; non-SCRAM authentication methods are rejected because they cannot employ PostgreSQL channel binding

    Use Prefer when you want stronger SCRAM when the deployment supports it but do not want to reject older servers yet. Use Require when every accepted connection must prove channel binding happened, and a silent fallback or a non-SCRAM password exchange would be the wrong outcome.

    ///|
    fn _channel_binding_example() -> @client.Config {
    @client.Config::new(
    "db.example",
    user="postgres",
    database="app",
    password="secret",
    ssl_mode=VerifyFull,
    channel_binding=Prefer,
    application_name="my-service",
    )
    }

    The pool package accepts the same setting through declarative config via channel_binding=Disable|Prefer|Require.

    Connection lifecycle APIs:

    • connect(config) -> (Client, Connection): open the PostgreSQL session and return the two cooperating handles
    • Connection::run(on_async?): own the socket, execute queued requests, and optionally forward notices / notifications / parameter updates to a callback
    • Client::close(): request a graceful shutdown, but do not wait for the socket to close
    • Client::is_closed(): tell whether the shared runtime is already closed
    • Client::check_connection(): send a cheap round trip and fail if the connection is no longer healthy
    • Client::parameter(name) and Connection::parameter(name): read server parameters such as server_version

    Cancelling the Connection::run task closes its socket and all request, COPY input, and notification queues. Waiting requests receive ClientError::Closed, and Client::is_closed() becomes true. The driver task remains cancelled.

    If you want out-of-band messages such as LISTEN / NOTIFY, parameter updates, or PostgreSQL notices, you can consume them from the callback or from Connection::next_message():

    ///|
    async fn _connection_run_example(config : @client.Config) -> Unit {
    @async.with_task_group(group => {
    let (client, connection) = @client.connect(config)
    group.spawn_bg(() => connection.run(on_async=msg => ignore(msg)))

    let version = client.parameter("server_version")
    let pending_message = connection.next_message()
    ignore(version)
    ignore(pending_message)

    let token = client.cancel_token()
    ignore(token.process_id())
    ignore(token.secret_key())

    client.close()
    })
    }

    Client::cancel_token() returns a CancelToken that can be passed to another task or timeout handler. CancelToken::cancel() opens PostgreSQL's separate cancellation connection and asks the server to interrupt the current backend operation.

    While consuming execution results, execute and execute_raw handle task cancellation by draining through ReadyForQuery before it propagates. execute then closes its temporary statement; execute_raw leaves the caller's statement open. This cleanup keeps the connection reusable, but can wait for the current SQL command to finish. It does not itself send a PostgreSQL cancel request.

    For LISTEN / NOTIFY and notices, pick one ownership model per connection:

    • pass on_async=... to Connection::run(...) when you want push-style handling
    • read Connection::next_message() from one dedicated task when you want a pull-style loop

    Here is the pull-style pattern:

    ///|
    async fn _listen_notify_example(config : @client.Config) -> Unit {
    @async.with_task_group(group => {
    let (listener, listener_connection) = @client.connect(config)
    let (publisher, publisher_connection) = @client.connect(config)
    group.spawn_bg(() => listener_connection.run())
    group.spawn_bg(() => publisher_connection.run())

    listener.batch_execute("LISTEN jobs")
    publisher.batch_execute("NOTIFY jobs, 'ready'")

    let message = listener_connection.next_message()
    ignore(message)

    publisher.close()
    listener.close()
    })
    }

    Client::clear_type_cache() is rarely needed in ordinary CRUD code. It is the escape hatch for sessions that create or alter PostgreSQL types at runtime and want later queries to lazily reload fresh metadata.

    #Parameters And Row Decoding

    Query parameters implement ToSql. Row decoding uses FromSql. The package ships built-in ToSql and FromSql implementations for:

    • Bool
    • Int
    • Int64
    • UInt
    • Float
    • Double
    • String
    • Bytes
    • Json
    • T? for any existing ToSql / FromSql codec
    • one-dimensional Array[T] and Array[T?] over supported built-in element codecs

    Typical mappings are:

    MoonBit typePostgreSQL types
    Boolbool
    Intint2, int4
    Int64int8
    UIntoid
    Floatfloat4
    Doublefloat8
    Stringtext, varchar, name, and other text-like types
    Bytesbytea, uuid, and raw byte-oriented formats
    Jsonjson, jsonb
    T?NULL on encode, optional decode on read
    Array[T] / Array[T?]one-dimensional arrays of supported built-in element types

    Example:

    ///|
    async fn _parameter_example(client : @client.Client) -> Unit {
    let name = "moonbit"
    let params : Array[&@client.ToSql] = [name as &@client.ToSql]
    let row = client.query_one("select $1::text as value", params~)
    let value : String = row.get_name("value")
    ignore(value)
    }

    Use Row and Column like this:

    • Row::get(index) / Row::get_name(name): strongly typed decode, raising ClientError::WrongType or ClientError::ColumnNotFound on mismatch
    • Row::try_get(index) / Row::try_get_name(name): return None only when the index or column name is missing; SQL NULL handling still depends on T versus T?
    • Row::get_raw(index) / Row::get_raw_name(name): raw bytes for custom decoding
    • Row::get_text(index) / Row::get_text_name(name): shorthand for nullable text access
    • Row::len() / Row::index_of(name): inspect row shape
    • Row.columns: full Column metadata for each field
    • Column.name, Column.table_oid, Column.column_id, Column.type_, Column.type_size, Column.type_modifier, Column.format: inspect where a column came from and which wire format PostgreSQL used

    If you want to stream rows incrementally:

    ///|
    async fn _streaming_query_example(client : @client.Client) -> Unit {
    let stream = client.query("select generate_series(1, 3)::int4 as value")
    guard stream.next() is Some(row) else { return }
    let first_value : Int = row.get_name("value")
    let summary = stream.finish()
    ignore(first_value)
    ignore(summary.row_count)
    ignore(summary.command_tag)
    }

    Warning: if you intend to discard a RowStream before reading it to completion, do not just drop it. Call detach() first, or later requests on the same connection can remain blocked behind the unfinished response.

    If you stop reading a RowStream early, either call finish() or detach().

    • finish() drains synchronously, returns a QuerySummary, and still raises a final database error to the caller if PostgreSQL reported one.
    • detach() returns immediately, drains the rest in the background, skips decoding discarded rows, and keeps later requests moving while swallowing any final database error locally.

    After detach(), the stream handle is terminal. Further next(), collect(), or finish() calls raise Closed.

    Both options release backpressure and let the driver close temporary server-side resources created by helpers such as query.

    #Custom codecs

    Use a custom ToSql / FromSql implementation when the built-in codecs are not enough but the PostgreSQL wire value still maps cleanly to one application type.

    The direct pattern is:

    1. decide which PostgreSQL OIDs the codec accepts
    2. choose WireFormat::Text or WireFormat::Binary
    3. write bytes in to_sql
    4. decode bytes in from_sql
    5. implement from_sql_null only when SQL NULL should decode into the type

    This is a minimal text codec that round-trips a wrapper around text / varchar:

    ///|
    struct EmailText {
    value : String
    } derive(Debug, Eq)

    ///|
    impl @client.ToSql for EmailText with fn format(_, _) {
    Text
    }

    ///|
    impl @client.ToSql for EmailText with fn accepts(_, type_) {
    type_.oid == @client.Type::text().oid ||
    type_.oid == @client.Type::varchar().oid
    }

    ///|
    impl @client.ToSql for EmailText with fn moonbit_type_name(_) {
    "EmailText"
    }

    ///|
    impl @client.ToSql for EmailText with fn to_sql(self, _, buf) {
    buf.write_bytes(@proto.utf8_encode(self.value))
    No
    }

    ///|
    impl @client.FromSql for EmailText with fn accepts(type_) {
    type_.oid == @client.Type::text().oid ||
    type_.oid == @client.Type::varchar().oid
    }

    ///|
    impl @client.FromSql for EmailText with fn moonbit_type_name() {
    "EmailText"
    }

    ///|
    impl @client.FromSql for EmailText with fn from_sql(_, _, raw) {
    { value: @utf8.decode(raw), }
    }

    ///|
    async fn _custom_codec_example(client : @client.Client) -> Unit {
    let email : EmailText = { value: "moonbit@example.com", }
    let params : Array[&@client.ToSql] = [email as &@client.ToSql]
    let row = client.query_one("select $1::text as email", params~)
    let decoded : EmailText = row.get_name("email")
    ignore(decoded)
    }

    If the column may be NULL, decode as EmailText? or implement FromSql::from_sql_null(...) yourself.

    #Next APIs To Learn

    The quick start only shows the smallest happy path. In real applications, the next six APIs worth learning are these.

    #Client::query_one

    Use query_one when the SQL contract is "there must be exactly one row". Good examples are:

    • reading one aggregate row such as count(*)
    • loading by primary key
    • asking PostgreSQL for one session value such as current_user

    query_one internally uses query, reads the first row, drains the stream, and then verifies the final row count. That means you get strong shape checking and the temporary statement is still cleaned up even if the row-count assertion fails.

    If zero rows or more than one row are returned, query_one raises ClientError::RowCount. If the row may be absent, prefer query_opt.

    ///|
    async fn _query_one_example(client : @client.Client) -> Unit {
    let row = client.query_one("select count(*)::int8 as count from pg_class")
    let count : Int64 = row.get_name("count")
    ignore(count)
    }

    #Client::execute

    Use execute when you want the affected row count and do not care about result rows. It is the normal choice for:

    • insert, update, and delete
    • DDL or maintenance commands where only completion matters
    • parameterized commands that should not be sent with the simple protocol

    execute prepares a temporary statement, runs it once, drains the command completion message, converts the PostgreSQL command tag to an Int, and then closes the temporary statement.

    If you need multi-statement raw SQL without parameters, use batch_execute instead. If you already have a reusable prepared statement, use execute_raw.

    ///|
    async fn _execute_example(client : @client.Client) -> Unit {
    let id = 42
    let params : Array[&@client.ToSql] = [id as &@client.ToSql]
    let affected = client.execute(
    "delete from items where id = $1::int4",
    params~,
    )
    ignore(affected)
    }

    #Client::transaction

    Use transaction() when several operations must succeed or fail together. The returned Transaction handle is a thin guard around the client API that:

    • starts with BEGIN
    • rejects further work after commit() or rollback()
    • lets you open nested transactions via PostgreSQL savepoints with tx.transaction()

    Pass TransactionOptions when you need BEGIN options such as SERIALIZABLE, READ ONLY, or DEFERRABLE.

    ///|
    async fn _transaction_example(client : @client.Client) -> Unit {
    let options = @client.TransactionOptions::new(
    isolation_level="SERIALIZABLE",
    read_only=true,
    deferrable=true,
    )
    let tx = client.transaction(options~)

    let nested = tx.transaction()
    nested.batch_execute("select 1")
    nested.rollback()
    tx.commit()
    }

    Use these transaction APIs according to scope:

    • Transaction::query, Transaction::execute, Transaction::batch_execute: run work inside the active transaction
    • Transaction::prepare, Transaction::bind, Transaction::query_portal: advanced statement and portal usage while keeping the transaction open
    • Transaction::commit(): commit or release the savepoint
    • Transaction::rollback(): roll back or roll back to the savepoint

    The handle does not auto-commit or auto-rollback for you. Finish it explicitly.

    Use Client::with_transaction(...) for callback-scoped transactions. It commits an unfinished transaction when the callback returns normally and attempts to roll it back if the callback raises or is cancelled. Rollback cleanup is protected from task cancellation.

    #Client::prepare

    Use prepare when the same SQL will be executed many times. A prepared statement lets PostgreSQL resolve parameter types and result columns once and then reuse the named server-side statement.

    The common follow-up APIs are:

    • Client::query_statement(statement, params~): run the statement and get a RowStream
    • Client::execute_raw(statement, params~): run the statement and get an affected-row count
    • Client::bind(statement, params~): create a reusable Portal
    • Statement::close(): explicitly close the server-side statement when you are done

    Use prepare_typed instead of prepare when PostgreSQL cannot infer parameter types from SQL context, for example select $1 or overloaded function calls with no cast information.

    ///|
    async fn _prepare_example(client : @client.Client) -> Unit {
    let statement = client.prepare("select $1::int4 as value")
    let value = 42
    let params : Array[&@client.ToSql] = [value as &@client.ToSql]
    let row = client.query_statement(statement, params~).collect()[0]
    let decoded : Int = row.get_name("value")
    ignore(decoded)
    statement.close()
    }

    Typed preparation looks like this:

    ///|
    async fn _prepare_typed_example(client : @client.Client) -> Unit {
    let statement = client.prepare_typed("select $1 as value", [
    @client.Type::text(),
    ])
    let value = "moonbit"
    let params : Array[&@client.ToSql] = [value as &@client.ToSql]
    let row = client.query_statement(statement, params~).collect()[0]
    let decoded : String = row.get_name("value")
    ignore(decoded)
    statement.close()
    }

    When result sets are large and you want explicit fetch windows, bind the statement into a portal and execute the portal in chunks:

    ///|
    async fn _portal_example(client : @client.Client) -> Unit {
    let statement = client.prepare("select generate_series(1, 5)::int4 as value")
    let portal = client.bind(statement)
    let stream = client.query_portal(portal, 2)
    let first_row = stream.next()
    let summary = stream.finish()
    ignore(first_row)
    ignore(summary.suspended)
    portal.close()
    statement.close()
    }

    QuerySummary.suspended == true means PostgreSQL stopped because the portal hit your max_rows limit. You can call query_portal again on the same portal to continue fetching, or close the portal if you no longer need it.

    #Client::copy_in

    Use copy_in(sql) for bulk import with COPY ... FROM STDIN. It returns a single-use CopyInSink:

    • CopyInSink::send(bytes_view): send one raw payload chunk
    • CopyInSink::finish(): tell PostgreSQL that the stream is complete and return the inserted row count
    • CopyInSink::abort(message?): abort the copy with a textual error message

    The package keeps payloads raw on purpose. It does not parse or produce CSV, text, or binary formats for you. That keeps the client generic and lets the application decide the framing.

    Use copy_in when:

    • importing many rows is more important than per-row convenience
    • the data is already available as CSV, text, or PostgreSQL binary COPY
    • you want streaming input instead of building one huge SQL statement

    ///|
    async fn _copy_in_example(client : @client.Client) -> Unit {
    let sink = client.copy_in(
    "COPY items(id, name) FROM STDIN WITH (FORMAT text)",
    )
    sink.send(b"1\tmoonbit\n"[:])
    let inserted = sink.finish()
    ignore(inserted)
    }

    After finish() or abort(), the sink is closed and cannot be reused.

    #Client::copy_out

    Use copy_out(sql) for bulk export with COPY ... TO STDOUT. It returns a CopyOutStream of raw payload chunks:

    • CopyOutStream::next(): read one chunk
    • CopyOutStream::collect(): read the rest into memory
    • CopyOutStream::finish(): drain the stream when you stop early
    • CopyOutStream::detach(): abandon the rest and drain in the background
    • CopyOutStream.formats: PostgreSQL wire formats for each output column

    Warning: if you intend to discard a CopyOutStream before it reaches the terminal state, call detach() first. Dropping it early can block later requests on the same connection.

    After detach(), treat the CopyOutStream handle as closed.

    Use it when:

    • exporting many rows is faster as COPY than as row-by-row queries
    • another layer already knows how to parse CSV, text, or binary COPY
    • you want streaming backpressure instead of materializing a large result set

    ///|
    async fn _copy_out_example(client : @client.Client) -> Unit {
    let stream = client.copy_out(
    "COPY (select 'moonbit'::text as name) TO STDOUT",
    )
    let chunk = stream.next()
    ignore(chunk)
    stream.finish()
    }

    #Simple Query

    Use simple_query when you explicitly want PostgreSQL's simple protocol. That usually means one of these cases:

    • the SQL string contains multiple statements
    • you want to inspect raw text-format result frames
    • you want SimpleQueryMessage::RowDescription, SimpleQueryMessage::Row, and SimpleQueryMessage::CommandComplete exactly as PostgreSQL emits them

    The returned SimpleQueryStream is lower-level than RowStream: every row is text-format only, and the stream exposes statement boundaries directly.

    Warning: if you intend to discard a SimpleQueryStream before it reaches the terminal state, call detach() first. Dropping it early can block later requests on the same connection.

    If you stop early, use finish() when you want synchronous completion or detach() when you want the remaining frames discarded in the background so later requests can continue promptly.

    After detach(), treat the SimpleQueryStream handle as closed.

    ///|
    async fn _simple_query_example(client : @client.Client) -> Unit {
    let stream = client.simple_query(
    "select 'hello'::text as greeting; select 1::text as number",
    )
    let first = stream.next()
    let rest = stream.collect()
    ignore(first)
    ignore(rest)
    }

    Use batch_execute when you still want the simple protocol but do not need to inspect the returned frames.

    #Complete Public API Reference

    This section is a compact reference for every public API in the package. Use it when you already know the package but need a reminder of what each exported type, method, or enum variant is for.

    #Top-Level, TLS, And Config

    • connect(config): open a PostgreSQL session and return (Client, Connection).
    • SslMode::Disable, SslMode::VerifyCa, SslMode::VerifyFull: TLS negotiation policies.
    • ChannelBinding::Disable, ChannelBinding::Prefer, ChannelBinding::Require: SCRAM channel-binding policies.
    • Config { host, hostaddr, port, user, database, password, ssl_mode, ssl_root_cert, channel_binding, application_name, options, connect_timeout_ms, keepalives, keepalives_idle_s }: immutable connection settings kept both for startup and later cancellation.
    • Config::new(host, hostaddr?, port?, user~, database?, password?, ssl_mode?, ssl_root_cert?, channel_binding?, application_name~, options?, connect_timeout_ms?, keepalives?, keepalives_idle_s?): build a config with secure defaults and an explicit PostgreSQL application_name.
    • Config::from_parts(host, hostaddr, port, user, database, password, ssl_mode, ssl_root_cert, channel_binding, application_name, options, connect_timeout_ms, keepalives, keepalives_idle_s): construct a fully resolved single-target config from explicit values.

    #Client And Connection

    • Client::batch_execute(sql): run one or more simple-protocol SQL commands and discard rows.
    • Client::bind(statement, params~): bind parameters to a prepared statement and return a reusable Portal.
    • Client::cancel_token(): create a CancelToken for out-of-band PostgreSQL cancellation.
    • Client::check_connection(): perform a round trip that confirms the request/response path is still healthy.
    • Client::clear_type_cache(): drop cached user-defined type metadata and keep only built-ins.
    • Client::close(): request graceful shutdown; wait for Connection::run to return if you need closure completion.
    • Client::copy_in(sql): start COPY ... FROM STDIN and return a CopyInSink.
    • Client::copy_out(sql): start COPY ... TO STDOUT and return a CopyOutStream.
    • Client::execute(sql, params~): execute parameterized SQL once and return affected rows.
    • Client::execute_raw(statement, params~): execute an already prepared statement and return affected rows.
    • Client::is_closed(): check whether the runtime has already shut down.
    • Client::parameter(name): read the latest server parameter value tracked by the client.
    • Client::prepare(sql): prepare a statement with PostgreSQL-inferred parameter types.
    • Client::prepare_typed(sql, types): prepare a statement with explicit parameter types.
    • Client::query(sql, params~): execute SQL and stream rows through RowStream.
    • Client::query_one(sql, params~): require exactly one row.
    • Client::query_opt(sql, params~): require zero or one row.
    • Client::query_portal(portal, max_rows): execute a bound portal and stream up to max_rows rows for that execution.
    • Client::query_statement(statement, params~): execute a prepared statement and stream its rows.
    • Client::query_typed(sql, param_types, params~): execute SQL with explicit parameter OIDs.
    • Client::query_typed_raw(sql, param_types, params~): backward-compatible alias for query_typed; prefer query_typed in new code.
    • Client::simple_query(sql): execute SQL via the simple protocol and inspect raw text frames.
    • Client::transaction(options?): begin a transaction with plain BEGIN or custom TransactionOptions.
    • Client::with_transaction(f, options?): run a callback inside a transaction and auto-complete it.
    • Connection::next_message(): read the next queued AsyncMessage, or None after the connection loop closes.
    • Connection::parameter(name): read the latest server parameter value from the connection handle.
    • Connection::run(on_async?): run the socket-owning event loop and optionally receive async messages via callback.
    • CancelToken::cancel(): open PostgreSQL's separate cancellation connection and interrupt the current backend operation.
    • CancelToken::process_id(): inspect the backend process ID embedded in the token.
    • CancelToken::secret_key(): inspect the backend secret key embedded in the token.

    #Statements, Portals, And Transactions

    • Statement { params, columns }: a prepared statement plus PostgreSQL-resolved parameter and result metadata.
    • Statement::close(): close the server-side prepared statement.
    • Portal { columns }: a bound portal ready for chunked or repeated execution.
    • Portal::close(): close the server-side portal.
    • Transaction::batch_execute(sql): run simple-protocol SQL inside the active transaction.
    • Transaction::bind(statement, params~): bind a prepared statement while the transaction is still open.
    • Transaction::commit(): commit the transaction or release the current savepoint.
    • Transaction::execute(sql, params~): execute SQL inside the transaction and return affected rows.
    • Transaction::execute_raw(statement, params~): execute an already prepared statement inside the transaction.
    • Transaction::prepare(sql): prepare a statement while the transaction is active.
    • Transaction::prepare_typed(sql, types): prepare a statement with explicit parameter types while the transaction is active.
    • Transaction::query(sql, params~): query rows inside the transaction.
    • Transaction::query_portal(portal, max_rows): execute a portal while the transaction is active.
    • Transaction::query_statement(statement, params~): execute a prepared statement while the transaction is active.
    • Transaction::rollback(): roll back the transaction or roll back to and release the savepoint.
    • Transaction::savepoint(name): create a named savepoint and return a nested transaction handle.
    • Transaction::transaction(): create a nested transaction implemented with a PostgreSQL savepoint.
    • TransactionOptions::new(isolation_level?, read_only?, deferrable?): configure custom BEGIN clauses.

    #Row Streams And Row Data

    • Column { name, table_oid, column_id, type_, type_size, type_modifier, format }: metadata for one result column.
    • QuerySummary { command_tag, row_count, suspended }: terminal information from a RowStream.
    • Row { columns, values }: one materialized row, storing both column metadata and raw field payloads.
    • Row::get(index): decode field index as T.
    • Row::get_name(name): decode the named field as T.
    • Row::get_raw(index): return the raw bytes at index.
    • Row::get_raw_name(name): return the raw bytes of the named field.
    • Row::get_text(index): decode a field as String?.
    • Row::get_text_name(name): decode a named field as String?.
    • Row::index_of(name): find a column index by label.
    • Row::len(): number of columns in the row.
    • Row::try_get(index): return None if the index is out of range, otherwise decode as T.
    • Row::try_get_name(name): return None if the named column is absent, otherwise decode as T.
    • RowStream { columns }: incremental extended-query result stream. columns holds the latest resolved metadata.
    • RowStream::collect(): collect the remaining rows into memory.
    • RowStream::detach(): abandon the remaining rows and drain in the background.
    • RowStream::finish(): drain the stream and return a QuerySummary.
    • RowStream::next(): pull the next row, or None after the terminal ReadyForQuery.

    #Simple Query Protocol

    • SimpleQueryMessage::RowDescription(columns): announces the text columns that future Row messages use.
    • SimpleQueryMessage::Row(row): one text-format row from the simple protocol.
    • SimpleQueryMessage::CommandComplete(tag): one statement inside the batch finished with the given command tag.
    • SimpleQueryRow { columns, values }: one text-format row from SimpleQueryStream.
    • SimpleQueryRow::get(index): get a nullable text field by index.
    • SimpleQueryRow::get_name(name): get a nullable text field by column name.
    • SimpleQueryRow::index_of(name): find a column index by label.
    • SimpleQueryRow::len(): number of columns in the row.
    • SimpleQueryStream::collect(): collect the remaining simple-query frames.
    • SimpleQueryStream::detach(): abandon the remaining frames and drain in the background.
    • SimpleQueryStream::finish(): drain the stream to its terminal ReadyForQuery.
    • SimpleQueryStream::next(): read the next SimpleQueryMessage.

    #COPY, Notifications, And Async Messages

    • CopyInSink::send(bytes_view): send one raw COPY FROM STDIN chunk.
    • CopyInSink::finish(): complete the copy and return PostgreSQL's inserted row count.
    • CopyInSink::abort(message?): abort the copy and drain PostgreSQL's completion sequence.
    • CopyOutStream { formats }: stream of raw COPY TO STDOUT chunks plus PostgreSQL column wire formats.
    • WireFormat::Text, WireFormat::Binary: PostgreSQL's text and binary wire formats.
    • CopyOutStream::collect(): collect the remaining chunks.
    • CopyOutStream::detach(): abandon the remaining chunks and drain in the background.
    • CopyOutStream::finish(): drain the copy stream when you stop early.
    • CopyOutStream::next(): read the next raw copy chunk.
    • Notification { process_id, channel, payload }: one PostgreSQL NOTIFY payload.
    • AsyncMessage::Notice(DatabaseError): PostgreSQL notice that did not fail the current request.
    • AsyncMessage::Notification(Notification): queued LISTEN / NOTIFY message.
    • AsyncMessage::ParameterStatus(name, value): server parameter update, such as server_version.

    #Type Descriptors And Codecs

    • Type { oid, name, kind }: stable description of a PostgreSQL type known to the client.
    • Kind::Simple: scalar type such as int4.
    • Kind::Array(element_oid): array type descriptor.
    • Kind::Enum(labels): enum type with all labels cached.
    • Kind::Composite(fields): record-like type with named Field members.
    • Kind::Domain(base_oid): domain over another PostgreSQL type.
    • Kind::Range(subtype_oid): range type whose subtype OID is known.
    • Kind::Pseudo: pseudo type such as void.
    • Kind::Unknown: placeholder shape when only the OID is known.
    • Field { name, type_oid }: one field inside Kind::Composite.
    • Type::bool(), Type::bytea(), Type::char(), Type::name_type(), Type::int2(), Type::int4(), Type::int8(), Type::oid_type(), Type::text(), Type::varchar(), Type::float4(), Type::float8(), Type::date(), Type::time(), Type::timestamp(), Type::timestamptz(), Type::uuid(), Type::json(), Type::jsonb(): built-in scalar descriptors to use for explicit parameter typing or metadata inspection.
    • Type::bool_array(), Type::bytea_array(), Type::int2_array(), Type::int4_array(), Type::int8_array(), Type::text_array(), Type::varchar_array(), Type::float4_array(), Type::float8_array(), Type::timestamp_array(), Type::date_array(), Type::uuid_array(), Type::json_array(), Type::jsonb_array(): built-in array descriptors.
    • Type::unknown(oid, name?): create a placeholder descriptor when only the OID is known.
    • ToSql: implement this trait for custom query parameter types.
    • ToSql::format(self, type_): choose WireFormat::Text or WireFormat::Binary; the default is binary.
    • ToSql::accepts(self, type_): declare whether the encoder supports the PostgreSQL target type.
    • ToSql::moonbit_type_name(self): provide the MoonBit-side type name for diagnostics.
    • ToSql::to_sql(self, type_, buf): write the encoded payload or mark it as NULL.
    • FromSql: implement this trait for custom row decoders.
    • FromSql::from_sql(type_, format, raw): decode a non-NULL field payload.
    • FromSql::accepts(type_): declare which PostgreSQL types the decoder accepts.
    • FromSql::moonbit_type_name(): provide the decoder's MoonBit-side type name for diagnostics.
    • FromSql::from_sql_null(type_, format): decode SQL NULL; format is a WireFormat, and the default implementation raises ClientError::Decode.
    • Built-in codec implementations exist for Bool, Int, Int64, UInt, Float, Double, String, Bytes, Json, T?, and one-dimensional Array[T] over supported built-in element codecs. Array[T?] supports SQL NULL elements; Array[T] rejects them during decoding. Array parameters and rows use PostgreSQL binary array format, and text array result decoding is intentionally not supported yet.
    • The Json codec supports PostgreSQL json and jsonb; parameters are sent in text format, while result decoding accepts text json/jsonb and binary jsonb.

    #Errors

    • DatabaseError { severity, code, message, detail, hint }: structured subset of PostgreSQL ErrorResponse and NoticeResponse fields.
    • WrongTypeError { moonbit_type, postgres_type }: describes an early driver-side type mismatch.
    • ClientError::Database(err): PostgreSQL returned an ErrorResponse.
    • ClientError::Authentication(message): startup authentication failed.
    • ClientError::Closed(message): the client, statement, portal, transaction, or copy sink is already closed.
    • ClientError::Protocol(message): a server reply violated protocol expectations during startup or message handling.
    • ClientError::Ssl(message): TLS negotiation or handshake failed.
    • ClientError::Encode(message): query parameter encoding failed.
    • ClientError::Decode(message): row decoding failed.
    • ClientError::WrongType(err): a ToSql or FromSql implementation rejected the PostgreSQL type.
    • ClientError::ColumnNotFound(name): named column lookup failed.
    • ClientError::RowCount(message): query_one or query_opt observed the wrong row count.
    • ClientError::UnexpectedMessage(message): the driver received a backend message that does not fit the current state.

    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. format: should PostgreSQL receive WireFormat::Text or WireFormat::Binary data?
    3. to_sql: write the actual payload or mark the value as NULL.

    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.
    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

    ClientError

    pub suberror ClientError {
    Database(DatabaseError)
    Authentication(String)
    Closed(String)
    Protocol(String)
    Ssl(String)
    Encode(String)
    Decode(String)
    WrongType(WrongTypeError)
    ColumnNotFound(String)
    RowCount(String)
    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 connection loop through the optional callback passed to Connection::run and can also be consumed later via Connection::next_message. In practice, pick one primary consumption style: pass on_async when you want push-style handling during run, or dedicate one reader task to next_message when you want pull-style handling.

    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.

    CancelToken::secret_key

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

    Return the backend secret key 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 connection task.

    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::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::bind

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

    Bind parameters to a prepared statement and create a server-side portal.

    Portals are primarily useful for chunked fetching with query_portal or for separating parameter binding from later execution.

    Client::cancel_token

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

    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

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

    Request a graceful shutdown of the client runtime.

    This method only enqueues a Terminate request; it does not block waiting for the socket to close. Callers that need to observe completion should wait for the Connection::run task to return.

    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.

    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.

    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::execute

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

    Execute SQL and return the affected row count.

    This convenience helper prepares a temporary statement, executes it once, waits for completion, and then closes the prepared statement. 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]) -> Int

    Execute a prepared statement and return the affected row count.

    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.

    Client::is_closed

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

    Return whether the shared runtime has fully closed.

    This becomes true after the connection loop exits or after a graceful shutdown request has been processed to completion.

    Client::parameter

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

    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 unnamed 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.

    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_portal

    fn Client::query_portal(self : Client, portal : Portal, max_rows : Int) -> RowStream raise

    Execute a portal and stream up to max_rows rows.

    When PostgreSQL suspends the portal because the limit was reached, the returned RowStream reports that via QuerySummary.suspended.

    Client::query_statement

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

    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::query_typed_raw

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

    Backward-compatible alias for query_typed.

    Client::simple_query

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

    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.

    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_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. If the callback raises or is cancelled, the transaction is rolled back best-effort unless it was already finished.

    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,
    Debug
    )

    Immutable configuration used by connect to establish a PostgreSQL session.

    The same value is also retained inside the client runtime so that later features, such as cancellation, can reopen a control connection with the exact same host and port.

    Config::from_parts

    fn Config::from_parts(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

    Construct a fully resolved single-target config from explicit parts.

    Config::new

    fn Config::new(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.

    Connection

    type Connection

    Socket-owning runtime task that executes queued requests and reads replies.

    connect returns a Connection alongside Client; callers must keep Connection::run alive for the client handle to make progress.

    Connection::next_message

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

    Receive the next asynchronous message buffered by the connection loop.

    Returns None after the async-message queue is closed. This is usually read from one dedicated task that owns async notices / notifications for the connection.

    Connection::parameter

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

    Look up the latest value of a server parameter.

    Connection::run

    async fn Connection::run(self : Connection, on_async? : (AsyncMessage) -> Unit) -> Unit

    Drive the PostgreSQL connection until graceful shutdown or failure.

    The loop alternates between:
    1. taking queued client requests,
    2. opportunistically pipelining additional non-barrier requests, and
    3. forwarding backend messages either to async listeners or to the oldest pending request.

    On exit, including cancellation, the loop closes all outstanding queues and the socket, and marks the shared runtime as closed. Ordinary errors are sent to waiting requests; cancellation closes them with ClientError::Closed while the driver task itself remains cancelled.

    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.

    CopyInSink::finish

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

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

    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 integer row count.

    CopyInSink::send

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

    Send one COPY data chunk to the server.

    CopyOutStream

    pub struct CopyOutStream {
    formats : Array[WireFormat]
    // private fields
    }

    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.

    CopyOutStream::next

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

    Read the next COPY OUT payload chunk.

    DatabaseError

    pub struct DatabaseError {
    severity : String?
    code : String?
    message : String
    detail : String?
    hint : String?
    } derive(Eq,
    Debug
    )

    Structured subset of PostgreSQL ErrorResponse and NoticeResponse fields.

    PostgreSQL emits many optional fields in protocol-level errors. The client currently preserves the most actionable ones so callers can branch on SQL state code, log a human-readable message, and surface hints or details to operators.

    Field

    pub struct Field {
    name : String
    type_oid : UInt
    } derive(Eq,
    Debug
    )

    Field descriptor stored inside Kind::Composite.

    Kind

    pub enum Kind {
    Simple
    Array(UInt)
    Enum(Array[String])
    Composite(Array[Field])
    Domain(UInt)
    Range(UInt)
    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.

    Notification

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

    Payload of PostgreSQL NotificationResponse.

    Portal

    pub struct Portal {
    columns : Array[Column]
    // private fields
    }

    Bound portal ready for repeated or chunked execution.

    Portals are useful when the caller wants to execute a prepared statement once, inspect the returned columns, and then fetch rows incrementally via query_portal.

    Portal::close

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

    Close the portal on the server.

    QuerySummary

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

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

    Row

    pub struct Row {
    columns : Array[Column]
    values : Array[Bytes?]
    } derive(Eq,
    Debug
    )

    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::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.

    RowStream

    pub struct RowStream {
    columns : Array[Column]
    // private fields
    }

    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.

    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::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.

    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.

    SimpleQueryMessage

    pub enum SimpleQueryMessage {
    RowDescription(Array[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

    pub struct SimpleQueryRow {
    columns : Array[String]
    values : Array[String?]
    } 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::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.

    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.

    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

    pub struct Statement {
    params : Array[Type]
    columns : Array[Column]
    // private fields
    }

    Prepared statement stored on the PostgreSQL server.

    The public params and columns fields expose the server-inferred parameter and result metadata 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.

    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::bind

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

    Bind parameters to a statement while the transaction is open.

    Transaction::commit

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

    Commit this transaction or release its savepoint.

    Transaction::execute

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

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

    Transaction::execute_raw

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

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

    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.

    Transaction::query_portal

    fn Transaction::query_portal(self : Transaction, portal : Portal, max_rows : Int) -> RowStream raise

    Execute a portal while the transaction is open.

    Transaction::query_statement

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

    Execute a previously prepared statement within this transaction scope.

    Transaction::rollback

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

    Roll back this transaction or roll back to its savepoint.

    Transaction::savepoint

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

    Create a named savepoint while the transaction is open.

    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.

    TransactionOptions

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

    Options for the BEGIN command used when opening a transaction.

    TransactionOptions::new

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

    Build transaction options for a future BEGIN command.

    Type

    pub struct Type {
    oid : UInt
    name : 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.

    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 a placeholder descriptor for a type that has not been catalogued.

    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.

    connect

    async fn connect(config : Config) -> (Client, Connection)

    Open a PostgreSQL connection and return both cooperating runtime handles.

    The returned Client only enqueues work; no I/O happens unless the paired Connection is actively driven by calling Connection::run in another task. This split keeps request submission cheap and makes backpressure explicit.