Simple Query Protocol: simpleQuery/simpleExec/ping, checkReady, cancel helpers (cancel/invalidateOnTimeout), checkSessionAttrs, quoteIdentifier, and quoteLiteral. Layer between buffer_io and lifecycle.
Internal module: not part of the public API. Import the pg_connection hub instead; what it re-exports is the supported surface (see tests/api_surface.golden).
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: [PgStateError, PgConnectionError], tags: [], forbids: [].}
-
Assert that the connection is in csReady before starting an operation.
Closed: PgStateError for a deliberate close(), PgConnectionError for a lost connection — only the second is worth reconnecting. Any other non-ready state (csBusy, …) is a live connection already in use, almost always driven concurrently, and raises PgStateError.
proc checkSessionAttrs(conn: PgConnection; attrs: TargetSessionAttrs): Future[ bool] {....stackTrace: false, raises: [Exception, ValueError, CatchableError, PgTypeError, PgConnectionError], tags: [RootEffect, TimeEffect], forbids: [].}
- Check target_session_attrs (libpq semantics). tsaPreferStandby always matches standalone; failover handles preference. Raises on 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 invalidateOnCancel(conn: PgConnection; releaseTransport = true) {. ...raises: [Exception], tags: [RootEffect], forbids: [].}
-
Invalidate a cancelled round trip.
The request went out and its ReadyForQuery was never drained, so the stream is desynchronised and no later operation recovers it. Leaving csBusy instead made every subsequent call fail checkReady with a PgStateError no reconnect loop acts on.
releaseTransport says whether this frame is the last one that knows about the connection: true for the operation wrappers, which cancellation unwinds past, false for the transaction and savepoint macros, whose own scope still hands the connection back.
Whoever runs first decides, not the nesting: a cancellation inside an awaited operation is claimed by that operation's wrapper, so an enclosing macro's call finds the counters zeroed and is a no-op — and its ROLLBACK cleanup is then skipped as csrConnInvalidated. The macro's false only covers a cancellation landing between operations.
proc invalidateOnTimeout(conn: PgConnection; reason: string) {. ...raises: [Exception, PgTimeoutError], tags: [RootEffect], forbids: [].}
-
Invalidate a timed-out round trip and raise PgTimeoutError.
For the frame that was driving the wire, so what the wire owes decides what it leaves behind. The transport is left alone: the caller is still in scope, so the pool's release or the user's close tears it down.
On asyncdispatch wait cannot cancel the future it gave on, so the timed-out operation stays live on the socket, still reading into the shared receive buffer. A settled wire says nothing about that orphan: the connection is retired regardless, and the read path's csClosed check ends it.
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, PgStateError, PgConnectionError, CancelledError, CatchableError, PgTypeError, 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 retired (csClosed) unless the wire had settled (asyncdispatch always retires: the timed-out op stays on the socket).
proc quoteIdentifier(s: string): string {....raises: [], tags: [], forbids: [].}
- Quote a SQL identifier (e.g. table/channel name) with double quotes, escaping embedded quotes.
proc quoteLiteral(s: string): string {....raises: [ValueError], tags: [], forbids: [].}
-
Quote a SQL string literal for simple-query SQL, escaping embedded quotes as ''. Backslash-bearing input is emitted in `` E'...'`` form with the backslashes doubled, so the result parses identically under either standard_conforming_strings setting (with off the standard parser treats \ as an escape, which plain quoting cannot contain). That form carries a leading space, as libpq's PQescapeLiteral does, so a result concatenated directly after an identifier or numeric constant cannot merge into it (LIKE & E'a\\b' would otherwise lex as likee).
Assumes an ASCII-compatible client_encoding: bytes are scanned individually, so under a client encoding whose multi-byte trail bytes may be 0x5C (SJIS, BIG5, GBK, UHC) an embedded character can be mistaken for a backslash.
Raises ValueError for an embedded NUL byte: the wire protocol terminates the query string there, so it cannot be represented.
proc retireOnTimeout(conn: PgConnection; reason: string) {. ...raises: [Exception, PgTimeoutError], tags: [RootEffect], forbids: [].}
-
Invalidate a timed-out scope whose body is still running, and raise PgTimeoutError.
Retirement is owed to ownership, not to the wire: a wait that cannot cancel what it gave on (asyncdispatch) leaves the body holding the connection, free to COMMIT a transaction the caller was told had timed out. So this one never hands the connection back.
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: [].}
- Simple-query exec (one Query msg, no Parse/Bind). Parameter-less only; verbatim SQL — quote via quoteIdentifier. Returns last tag. On timeout, the connection is retired (csClosed) unless the wire had settled (asyncdispatch always retires: the timed-out op stays on the socket).
proc simpleQuery(conn: PgConnection; sql: string; timeout: Duration = ZeroDuration): Future[seq[QueryResult]] {. ...stackTrace: false, raises: [Exception, ValueError, CatchableError], tags: [RootEffect, TimeEffect], forbids: [].}
- Simple-query multi-statement exec. One QueryResult per ;-separated stmt, text rows, no params/cache. Verbatim SQL — only trusted input; quote via quoteIdentifier. On timeout, the connection is retired (csClosed) unless the wire had settled (asyncdispatch always retires: the timed-out op stays on the socket).
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. AsyncTimeoutError invalidates via invalidateOnTimeout; a cancellation via invalidateOnCancel and is re-raised.
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).