moonpostgres

    Pure-MoonBit PostgreSQL wire-protocol driver implementing @moondb.AsyncDriver — no C, like asyncpg.

    postgres
    postgresql
    pg
    driver
    database
    sql
    wire-protocol
    protocol
    client
    async
    scram
    tls
    copy
    listen-notify
    prepared-statement
    asyncpg
    moondb
    wire
    moonbit
    Download zip
    Version
    0.6.9
    License
    Apache-2.0
    Last updated
    11 hours ago
    Downloads
    317

    #moonpostgres

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

    A pure-MoonBit PostgreSQL driver — the v3 frontend/backend wire protocol spoken directly over async TCP, with zero C and no libpq. Like asyncpg or pg8000, it talks to a real PostgreSQL server itself. PgConn implements the @moondb.AsyncDriver contract, so a moondb-based stack (e.g. moonorm) can sit on top of it.

    moon add moonbitstack/moonpostgres

    Previously published as Lfan-ke/moon-postgres.

    Supports native and wasm hosts with TCP socket bindings, such as moonrun. Browser wasm is not supported because it cannot open raw TCP sockets.

    #Quickstart

    The real driver is the async PgConn (asyncpg-shaped), used inside an event loop (async fn main / async test):

    async fn run() -> Unit raise {
    let conn = @moonpostgres.PgConn::connect(
    "127.0.0.1", 5432, "postgres", "postgres", "test",
    )
    conn.execute("CREATE TABLE hero (id int, name text)", []) |> ignore

    // '?' placeholders are translated to '$1','$2' and bound out-of-band
    // (never spliced into the SQL) — injection-safe to the wire.
    conn.execute("INSERT INTO hero VALUES (?, ?)", [
    @moondb.Int(1), @moondb.Text("Boromir"),
    ]) |> ignore

    let rows = conn.query("SELECT id, name FROM hero WHERE id = ?", [@moondb.Int(1)])
    println(rows[0].text_by("name")) // Boromir

    conn.begin()
    conn.execute("UPDATE hero SET name = ? WHERE id = ?", [
    @moondb.Text("Faramir"), @moondb.Int(1),
    ]) |> ignore
    conn.commit()

    conn.close()
    }

    #What it speaks

    sequenceDiagram participant C as PgConn (client) participant S as PostgreSQL C->>S: StartupMessage (user, database, client_encoding) S-->>C: Authentication (Ok / MD5 / cleartext) C->>S: PasswordMessage (md5… token) %% if requested S-->>C: AuthenticationOk S-->>C: ParameterStatus*, BackendKeyData, ReadyForQuery Note over C,S: simple query (no params) C->>S: Query 'Q' S-->>C: RowDescription 'T', DataRow 'D'*, CommandComplete 'C', ReadyForQuery 'Z' Note over C,S: extended query (bound params, ?→$n) C->>S: Parse 'P', Bind 'B', Describe 'D', Execute 'E', Sync 'S' S-->>C: ParseComplete, BindComplete, RowDescription, DataRow*, CommandComplete, ReadyForQuery

    • Framing — every message is a 1-byte type tag, a big-endian Int32 self-inclusive length, and a payload (the untagged StartupMessage aside).
    • Auth — AuthenticationOk (trust), cleartext, and MD5 (md5(md5(password+user)+salt), with a self-contained pure-MoonBit MD5). SCRAM-SHA-256 is on the roadmap.
    • Simple query (Q) when there are no parameters; extended query (Parse/Bind/Describe/Execute/Sync) when there are, with ?→$n translation and out-of-band text-format binding.
    • Decoding — RowDescription + DataRow decode into @moondb.Row; column type OIDs map int2/int4→Int, int8→Int64, float4/float8→Double, bool→Bool, bytea→Blob, everything else→Text.

    #Why the driver is async

    @moondb.Driver's methods are synchronous (fn execute(...) raise DbError), which fits an FFI-backed driver like moonsqlite whose C calls block. PostgreSQL is reached over TCP, and MoonBit's only socket stack (moonbitlang/async) is async-only: an async fn cannot be called from a synchronous one, and the runtime exposes no public "run this async thunk to completion" bridge (with_event_loop lives in an import-blocked internal package). A synchronous method therefore cannot perform a PostgreSQL round trip.

    That is why moondb has a second seam. PgConn implements [@moondb.AsyncDriver] — the same eight operations as Driver, every one of them awaited — and PgDriver is now just the connection descriptor you open one from. Use it inside an event loop (async test / async fn main):

    async fn main {
    let conn = PgDriver::new(host="127.0.0.1", user="postgres", database="app").connect()
    defer conn.close()
    let drv : &@moondb.AsyncDriver = conn
    let rows = drv.query("SELECT id, name FROM users WHERE id = ?", [@moondb.Int(1)])
    }

    Before this, PgDriver implemented the synchronous Driver by raising from every row-touching method. That was worse than useless in one specific way: ping is a defaulted method built on query, so it swallowed the raise and returned false, and any Pool with pre_ping judged every connection permanently unhealthy. The raising façade is gone.

    This is the language-idiomatic equivalent, not a shortcut: asyncpg is async for exactly the same reason.

    #Testing

    • Unit (moon test) — MD5 vectors, ?→$n translation (literals/identifiers/comments/dollar-quotes/??), value encode/decode by OID, and message framing + RowDescription/DataRow decode against synthetic backend messages.
    • Integration (moon test, gated on MOON_PG_TEST) — connect / DDL / parameterised INSERT / SELECT round trip and transaction commit+rollback against a real PostgreSQL. Skipped when MOON_PG_TEST is unset (local checkout without a database); CI stands up a postgres:16 service and runs it. Connection parameters come from the standard PGHOST/PGPORT/PGUSER/PGPASSWORD/PGDATABASE variables.

    The DataRow decoder is mutation-checked: breaking it makes the decode test fail with a type error.

    #Roadmap

    This round covers connect + auth (MD5/trust) + simple query + text decode + ?→$n + @moondb.AsyncDriver conformance + a real round trip. Later rounds:

    • SCRAM-SHA-256 authentication (the default for PostgreSQL 14+ with a password set).
    • Binary result/parameter format and a type-OID registry (numeric, dates/times, arrays, JSON, UUID).
    • Prepared statements (named, cached) and a streaming Rows cursor.
    • RETURNING-based last_insert_id, COPY, LISTEN/NOTIFY, TLS, and a connection pool.

    #License

    Apache-2.0 © Leo Cheng.

    DbError

    The single database error type every fallible operation raises, aliased from moondb so driver signatures read raise DbError.

    ExecResult

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

    Row

    using @moonbitstack/moondb { type Row }

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

    Value

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

    PgConn

    pub struct PgConn {
    stream : PgWire
    host : String
    port : Int
    closed : Bool
    streaming : Bool
    params : Map[String, String]
    pid : Int
    secret : Int
    statements : Map[String, String]
    next_stmt : Int
    }

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

    PgConn::backend_pid

    fn PgConn::backend_pid(self : PgConn) -> Int

    The backend process id, from BackendKeyData.

    PgConn::begin

    async fn PgConn::begin(self : PgConn) -> Unit raise
    DbError

    Begin an explicit transaction (BEGIN).

    PgConn::cancel

    async fn PgConn::cancel(self : PgConn) -> Unit raise
    DbError

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

    PgConn::close

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

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

    PgConn::close_cursor

    async fn PgConn::close_cursor(self : PgConn, name : String) -> Unit raise
    DbError

    Close a declared cursor.

    PgConn::commit

    async fn PgConn::commit(self : PgConn) -> Unit raise
    DbError

    Commit the current transaction (COMMIT).

    PgConn::connect

    async fn PgConn::connect(host : String, port : Int, user : String, password : String, database : String, tls? :
    Client
    ) -> PgConn raise
    DbError

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

    PgConn::copy_in

    async fn PgConn::copy_in(self : PgConn, sql : String, data : Bytes) ->
    ExecResult
    raise
    DbError

    Run a COPY … FROM STDIN statement, streaming data to the server, and report the command tag's affected-row count.

    PgConn::copy_out

    async fn PgConn::copy_out(self : PgConn, sql : String) -> Bytes raise
    DbError

    Run a COPY … TO STDOUT statement and return the bytes the server streams.

    PgConn::declare_cursor

    async fn PgConn::declare_cursor(self : PgConn, name : String, sql : String) -> Unit raise
    DbError

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

    PgConn::execute

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

    PgConn::fetch_cursor

    async fn PgConn::fetch_cursor(self : PgConn, name : String, count : Int) -> Array[
    Row
    ] raise
    DbError

    Fetch up to count rows forward from a declared cursor.

    PgConn::is_closed

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

    Whether close has been called. A closed connection refuses further statements with [@moondb.Closed].

    PgConn::is_streaming

    fn PgConn::is_streaming(self : PgConn) -> Bool

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

    PgConn::listen

    async fn PgConn::listen(self : PgConn, channel : String) -> Unit raise
    DbError

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

    PgConn::next_notification

    async fn PgConn::next_notification(self : PgConn) -> (String, String) raise
    DbError

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

    PgConn::notify

    async fn PgConn::notify(self : PgConn, channel : String, payload? : String) -> Unit raise
    DbError

    Send a notification on channel, optionally carrying payload.

    PgConn::parameter

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

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

    PgConn::ping

    async fn PgConn::ping(self : PgConn) -> Bool

    PgConn::query

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

    PgConn::query_stream

    async fn PgConn::query_stream(self : PgConn, sql : String, params : Array[
    Value
    ]) -> PgRowStream raise
    DbError

    Send sql (with bound params) and return a streaming cursor over its rows.

    PgConn::rollback

    async fn PgConn::rollback(self : PgConn) -> Unit raise
    DbError

    Roll back the current transaction (ROLLBACK).

    PgConn::unlisten

    async fn PgConn::unlisten(self : PgConn, channel : String) -> Unit raise
    DbError

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

    PgDriver

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

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

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

    PgDriver::connect

    async fn PgDriver::connect(self : PgDriver) -> PgConn raise
    DbError

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

    PgDriver::new

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

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

    PgRowStream

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

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

    PgRowStream::close

    async fn PgRowStream::close(self : PgRowStream) -> Unit raise
    DbError

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

    PgRowStream::next

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

    PgWire

    pub struct PgWire {
    // private fields
    }

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

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

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

    PgWire::close

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

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

    PgWire::pipe

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

    PgWire::read_exactly

    async fn PgWire::read_exactly(self : PgWire, n : Int) -> Bytes

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

    PgWire::tcp

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

    PgWire::write

    async fn PgWire::write(self : PgWire, bytes : Bytes) -> Unit

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

    OID_BOOL

    let OID_BOOL : Int

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

    OID_BYTEA

    let OID_BYTEA : Int

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

    OID_DATE

    let OID_DATE : Int

    The pg_type OID of date.

    OID_FLOAT4

    let OID_FLOAT4 : Int

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

    OID_FLOAT8

    let OID_FLOAT8 : Int

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

    OID_INT2

    let OID_INT2 : Int

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

    OID_INT4

    let OID_INT4 : Int

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

    OID_INT8

    let OID_INT8 : Int

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

    OID_JSON

    let OID_JSON : Int

    The pg_type OID of json.

    OID_JSONB

    let OID_JSONB : Int

    The pg_type OID of jsonb.

    OID_NUMERIC

    let OID_NUMERIC : Int

    The pg_type OID of numeric.

    OID_TIME

    let OID_TIME : Int

    The pg_type OID of time.

    OID_TIMESTAMP

    let OID_TIMESTAMP : Int

    The pg_type OID of timestamp.

    OID_TIMESTAMPTZ

    let OID_TIMESTAMPTZ : Int

    The pg_type OID of timestamptz.

    OID_TIMETZ

    let OID_TIMETZ : Int

    The pg_type OID of timetz.

    OID_UUID

    let OID_UUID : Int

    The pg_type OID of uuid.

    build_bind

    fn build_bind(params : Array[
    Value
    ]) -> Bytes

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

    build_bind_named

    fn build_bind_named(portal : String, statement : String, params : Array[
    Value
    ]) -> Bytes

    Bind ('B') params to a named (or unnamed) prepared statement, producing a named (or unnamed) portal. All parameters and result columns use text format.

    build_cancel_request

    fn build_cancel_request(pid : Int, secret : Int) -> Bytes

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

    build_copy_data

    fn build_copy_data(chunk : Bytes) -> Bytes

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

    build_copy_done

    fn build_copy_done() -> Bytes

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

    build_copy_fail

    fn build_copy_fail(message : String) -> Bytes

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

    build_describe_portal

    fn build_describe_portal() -> Bytes

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

    build_execute

    fn build_execute() -> Bytes

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

    build_parse

    fn build_parse(sql : String) -> Bytes

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

    build_parse_named

    fn build_parse_named(name : String, sql : String) -> Bytes

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

    build_password

    fn build_password(token : String) -> Bytes

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

    build_query

    fn build_query(sql : String) -> Bytes

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

    build_sasl_initial

    fn build_sasl_initial(mechanism : String, client_first : String) -> Bytes

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

    build_sasl_response

    fn build_sasl_response(client_final : String) -> Bytes

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

    build_ssl_request

    fn build_ssl_request() -> Bytes

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

    build_startup

    fn build_startup(user : String, database : String) -> Bytes

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

    build_sync

    fn build_sync() -> Bytes

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

    build_terminate

    fn build_terminate() -> Bytes

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

    concat_bytes

    fn concat_bytes(a : Bytes, b : Bytes) -> Bytes

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

    count_placeholders

    fn count_placeholders(sql : String) -> Int

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

    decode_bytea

    fn decode_bytea(s : String) -> Bytes

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

    decode_value

    fn decode_value(oid : Int, is_null : Bool, raw : String) ->
    Value

    Decode a text-format result cell (raw, already UTF-8) tagged with its column oid into a [Value]. is_null marks a wire NULL (length -1), which decodes to Null regardless of type. Numeric and boolean OIDs decode to their typed cases; everything else — including numeric, dates, and unknown OIDs — rides back as Text, exactly the dialect-neutral contract moondb documents (temporal/numeric typing is a roadmap item).

    encode_param

    fn encode_param(v :
    Value
    ) -> Bytes?

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

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

    parse_double

    fn parse_double(s : String) -> Double?

    Parse a floating-point literal from PostgreSQL text (123.45, -1e10, Infinity, NaN), or None if malformed. Handles sign, fraction, and a base-10 exponent; the special IEEE tokens PostgreSQL emits are recognised.

    parse_int

    fn parse_int(s : String) -> Int?

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

    parse_int64

    fn parse_int64(s : String) -> Int64?

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

    pg_md5_password

    fn pg_md5_password(user : String, password : String, salt : Bytes) -> String

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

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

    saslprep

    fn saslprep(password : String) -> String

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

    scram_client_final

    fn scram_client_final(password : Bytes, client_first_bare : String, server_first : String, gs2_header? : String, cbind? : Bytes) -> (String, Bytes) raise
    DbError

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

    scram_client_proof

    fn scram_client_proof(salted : Bytes, auth_message : Bytes) -> Bytes

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

    scram_server_signature

    fn scram_server_signature(salted : Bytes, auth_message : Bytes) -> Bytes

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

    translate_placeholders

    fn translate_placeholders(sql : String) -> String

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

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

    utf8_decode

    fn utf8_decode(data : Bytes) -> String

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