async_postgres/pg_connection/simple_query

Simple Query Protocol entry points and the cancellation helpers built on top of them.

Contains:

  • checkReady — assertion used by every operation that requires csReady.
  • simpleQuery / simpleExec / ping — text-mode multi-statement and single-statement query/exec via the simple query protocol.
  • cancel / cancelNoWait / invalidateOnTimeout — out-of-band cancel request over a separate socket plus the standard "the wait timed out, poison this connection" recovery path used by every timeout wrapper.
  • checkSessionAttrs — server-role probe (SHOW transaction_read_only / in_hot_standby / SELECT pg_catalog.pg_is_in_recovery()) used by the multi-host failover logic in lifecycle.connect.
  • quoteIdentifier — SQL identifier escaping used by LISTEN/UNLISTEN and other identifier-bearing simple-query call sites.
  • QueryResult helpers (len, columnIndex, rows, items).

Sits between buffer_io/cache (which it consumes) and lifecycle (which depends on checkSessionAttrs and the cancel helpers), so it avoids circular imports.

Re-exported through pg_connection.nim.

Procs

proc bytesToString(data: seq[byte]): string {....raises: [], tags: [], forbids: [].}
proc cancel(conn: PgConnection): Future[void] {....stackTrace: false,
    raises: [Exception, OSError, ValueError, IOError, SslError, LibraryError],
    tags: [RootEffect], forbids: [].}
Send a CancelRequest over a separate connection to abort the running query.
proc cancelNoWait(conn: PgConnection) {....raises: [Exception], tags: [RootEffect],
                                        forbids: [].}
Schedule a best-effort CancelRequest without waiting. For use in timeout handlers.
proc checkReady(conn: PgConnection) {....raises: [PgConnectionError, PgStateError],
                                      tags: [], forbids: [].}

Assert that the connection is in csReady before starting an operation.

A csClosed connection is genuinely gone, so this raises PgConnectionError — reconnecting is the correct recovery. Any other non-ready state (csBusy, csReplicating, …) means the connection is alive but already in use, almost always a single connection driven concurrently; that raises PgStateError (a programming error), which is not a PgConnectionError and so never feeds a reconnect-on-failure loop.

proc checkSessionAttrs(conn: PgConnection; attrs: TargetSessionAttrs): Future[
    bool] {....stackTrace: false,
            raises: [Exception, ValueError, CatchableError, PgConnectionError],
            tags: [RootEffect, TimeEffect], forbids: [].}
Check whether a connection matches the desired target_session_attrs. Follows libpq: tsaReadWrite/tsaReadOnly are judged on the session's read-only state, while tsaPrimary/tsaStandby are judged on the recovery state — the in_hot_standby ParameterStatus reported by PostgreSQL 14+, with a SELECT pg_catalog.pg_is_in_recovery() probe as fallback for older servers. tsaPreferStandby is permissive: as a standalone predicate any server matches (the multi-host failover in lifecycle.connect handles the standby preference with a two-pass scan). Raises on an indeterminate probe.
proc columnIndex(qr: QueryResult; name: string): int {....raises: [PgTypeError],
    tags: [], forbids: [].}
Find the index of a column by name in a query result.
proc invalidateOnTimeout(conn: PgConnection; reason: string) {.
    ...raises: [Exception, PgTimeoutError], tags: [RootEffect], forbids: [].}

Timeout recovery for a connection whose last request may have left the protocol out of sync. Schedules a best-effort CancelRequest via cancelNoWait, marks the connection csClosed so it cannot be reused, and raises PgTimeoutError with reason.

Under asyncdispatch this is the only safe recovery path: the inner future keeps running in the background after wait() fires, and may still write to the socket. Reusing the connection would interleave its stale write with a new request and corrupt the protocol stream. chronos cancels the inner future properly, but we still invalidate unconditionally — the server may have processed the request partially and the cached session state (prepared statements, portals, transaction status) is no longer reliable.

proc len(qr: QueryResult): int {.inline, ...raises: [], tags: [], forbids: [].}
Return the number of rows in the query result.
proc ping(conn: PgConnection; timeout = ZeroDuration): Future[void] {....raises: [
    Exception, ValueError, CatchableError, PgConnectionError, PgStateError,
    SslError, PgProtocolError, PgQueryError, PgTimeoutError, AsyncTimeoutError],
    tags: [RootEffect, TimeEffect], forbids: [].}
Lightweight health check using an empty simple query. Sends Query("") -> expects EmptyQueryResponse + ReadyForQuery. On timeout, the connection is marked csClosed (protocol out of sync).
proc quoteIdentifier(s: string): string {....raises: [], tags: [], forbids: [].}
Quote a SQL identifier (e.g. table/channel name) with double quotes, escaping embedded quotes.
proc rows(qr: QueryResult): seq[Row] {....raises: [], tags: [], forbids: [].}
Return all rows as lightweight Row views into the flat buffer.
proc simpleExec(conn: PgConnection; sql: string;
                timeout: Duration = ZeroDuration): Future[CommandResult] {.
    ...stackTrace: false, raises: [Exception, ValueError, CatchableError],
    tags: [RootEffect, TimeEffect], forbids: [].}

Execute a side-effect SQL command via the simple query protocol, returning the final command tag.

Lighter than exec for parameter-less commands — one Query message, no Parse/Bind/Describe round trip and no plan cache entry. Intended for session-level commands such as BEGIN, SET, VACUUM, LISTEN, NOTIFY.

The SQL string is sent verbatim (no parameters) — only use trusted input, or quote interpolated identifiers yourself via quoteIdentifier.

Multiple ;-separated statements are accepted, but only the last command tag is returned; use simpleQuery if you need per-statement results. For parameterised writes, prefer exec.

On timeout, the connection is marked csClosed (protocol out of sync).

proc simpleExecImpl(conn: PgConnection; sql: string): Future[string] {.
    ...stackTrace: false, raises: [Exception, ValueError, PgConnectionError,
                                PgStateError, SslError, PgProtocolError,
                                PgQueryError, AsyncTimeoutError, CatchableError],
    tags: [RootEffect, TimeEffect], forbids: [].}
proc simpleQuery(conn: PgConnection; sql: string;
                 timeout: Duration = ZeroDuration): Future[seq[QueryResult]] {.
    ...stackTrace: false, raises: [Exception, ValueError, CatchableError],
    tags: [RootEffect, TimeEffect], forbids: [].}

Execute one or more SQL statements via the simple query protocol.

Returns one QueryResult per statement; supports multiple statements separated by ; in a single round trip — this is the main reason to choose simpleQuery over query.

No parameters are supported (the SQL string is sent verbatim — only use trusted input) and rows are always in the text wire format. No server-side plan cache entry is created.

For single-statement parameterised reads, prefer query; for parameter-less commands without rows, prefer simpleExec.

On timeout, the connection is marked csClosed (protocol out of sync).

proc simpleQueryImpl(conn: PgConnection; sql: string): Future[seq[QueryResult]] {.
    ...stackTrace: false, raises: [Exception, ValueError, PgConnectionError,
                                PgStateError, SslError, PgProtocolError,
                                PgQueryError, AsyncTimeoutError, CatchableError],
    tags: [RootEffect, TimeEffect], forbids: [].}

Iterators

iterator items(qr: QueryResult): Row {....raises: [], tags: [], forbids: [].}
Iterate over all rows in the query result.