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, PgTypeError, 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 checkTxIdle(conn: PgConnection) {....raises: [PgStateError], tags: [], forbids: [].}
- Reject entry to a top-level BEGIN/COMMIT scope when a transaction is already active: nested BEGIN is a server-side no-op, so the inner COMMIT would confirm the outer transaction's work. Use withSavepoint to nest.
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 the inner future keeps running in the background after wait() times out (see wait()'s onOrphan hook in async_backend). Reusing the connection would interleave its stale write with a new request and corrupt the protocol stream, so we mark csClosed 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, PgQueryError, PgConnectionError, PgStateError, CatchableError, PgProtocolError, 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 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).
Iterators
iterator items(qr: QueryResult): Row {....raises: [], tags: [], forbids: [].}
- Iterate over all rows in the query result.
Templates
template awaitOrInvalidate(connExpr: PgConnection; dest: untyped; fut: untyped; timeout: Duration; reason: static string)
-
Await fut with optional timeout, assigning the result to dest. On timeout, invalidates connExpr via invalidateOnTimeout (marks csClosed, dispatches CancelRequest, raises PgTimeoutError).
Consolidates the repeated if timeout > ZeroDuration / try / except AsyncTimeoutError / else pattern so a single-site omission cannot leave the connection poisoned for the next borrower — see the invalidateOnTimeout doc-comment for why in-flight timeouts must always retire the connection under asyncdispatch.
template awaitVoidOrInvalidate(connExpr: PgConnection; fut: untyped; timeout: Duration; reason: static string)
- Void-returning variant of awaitOrInvalidate for Future[void] sites (e.g. close on a prepared statement or cursor).