async_postgres/pg_connection/simple_query

Search:
Group by:

Simple Query Protocol: simpleQuery/simpleExec/ping, checkReady, cancel helpers (cancel/invalidateOnTimeout), checkSessionAttrs, quoteIdentifier, and quoteLiteral. Layer between buffer_io/ssl and lifecycle.

Internal module: not part of the public API. Import the pg_connection hub instead.

Procs

proc bytesToString(data: seq[byte]): string {....raises: [], tags: [], forbids: [].}
proc cancel(conn: PgConnection): Future[void] {....raises: [Exception,
    PgConnectionError, PgUnavailableError, OSError, IOError, ValueError,
    LibraryError, SslError, CatchableError, PgConfigError, CancelledError,
    PgTimeoutError], tags: [RootEffect, TimeEffect, WriteIOEffect], forbids: [].}

Send a CancelRequest over a separate connection to abort the running query, then wait for the server to close that connection, as libpq does: the request is then no longer in flight to hit a later query.

A TLS session sends it over TLS, checked as the session was and never falling back to plaintext: the secret key must not cross the wire less protected than the session that delivered it.

Raises PgTimeoutError if the whole exchange takes longer than connectTimeout or 10 seconds, whichever is longer.

proc cancelNoWait(conn: PgConnection) {....raises: [Exception], tags: [RootEffect,
    TimeEffect, WriteIOEffect], 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, WriteIOEffect], 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. A duplicated name resolves to its first column, as in columnIndex(fields, …).
proc invalidateOnCancel(conn: PgConnection; releaseTransport = true) {.
    ...raises: [Exception], tags: [RootEffect, TimeEffect, WriteIOEffect],
    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, TimeEffect, WriteIOEffect], 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,
    PgUnavailableError, CancelledError, CatchableError, PgTypeError,
    PgProtocolError, PgTimeoutError, AsyncTimeoutError],
    tags: [RootEffect, TimeEffect, WriteIOEffect], 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: [ValueError], tags: [],
    forbids: [].}
Quote a SQL identifier (e.g. table/channel name) with double quotes, escaping embedded quotes. Raises ValueError for an embedded NUL byte (same as quoteLiteral).
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).

Bytes are scanned individually, which relies on the pinned UTF8 client_encoding. A switch is only detected after the fact, so do not send a SET client_encoding in the same query or pipeline: under SJIS, BIG5, GBK or UHC a trail byte 0x5C would be read as 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, TimeEffect, WriteIOEffect], 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, WriteIOEffect], 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, WriteIOEffect], 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).
proc waitKeepingOutcome[T](fut: Future[T]; timeout: Duration): Future[T]

wait for callers that still read fut (await, completed(), read()) after an AsyncTimeoutError. On asyncdispatch the default onOrphan clears a late failure — one landing on the timer's tick is cleared before the timeout handler runs — so fut would read as a success holding a default value.

The caller owns the orphan's outcome: nothing drains it, so a late success keeps the value it delivered — a PgConnection that only the caller can hand back — and a late failure stays readable for a later completed() / failed() check.

Iterators

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

Macros

macro awaitOrInvalidate(connExpr: PgConnection; dest: untyped; fut: untyped;
                        timeout: Duration; reason: static string): untyped
Await fut with optional timeout. AsyncTimeoutError invalidates via invalidateOnTimeout; a cancellation via invalidateOnCancel and is re-raised.
macro awaitVoidOrInvalidate(connExpr: PgConnection; fut: untyped;
                            timeout: Duration; reason: static string): untyped
Void-returning variant of awaitOrInvalidate for Future[void] sites (e.g. close on a prepared statement or cursor).

Templates

template tracedSimpleExec(conn: PgConnection; stmt: string; timeout: Duration;
                          reason: static string; tag: untyped; check: untyped)
simpleExec's traced, timeout-bounded core, storing the last tag in tag. check runs inside the trace, so onQueryEnd reports what it raises.