async_postgres/pg_client/cursor

Server-side portal-based cursors: openCursor, fetchNext, close, and the scoped withCursor macro.

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

Types

Cursor = ref object
A server-side portal for incremental row fetching via declareCursor/fetch. Handle fields are private; use the conn / fields / exhausted accessors for read-only access.

Procs

proc close(cursor: Cursor): Future[void] {....stackTrace: false, raises: [
    Exception, PgStateError, ValueError, PgQueryError, PgTypeError,
    PgProtocolError, PgConnectionError, CatchableError, PgTimeoutError,
    CancelledError, AsyncTimeoutError],
    tags: [RootEffect, TimeEffect, WriteIOEffect], forbids: [].}
Close the cursor and return the connection to ready state. Outside a transaction block the closing Sync commits the statement; a commit failure raises PgQueryError and still leaves the cursor closed. On timeout, the connection is retired (csClosed) unless the wire had settled (asyncdispatch always retires: the timed-out op stays on the socket). Concurrent fetchNext/close on the same cursor raises PgStateError.
proc columnIndex(cursor: Cursor; name: string): int {....raises: [PgTypeError],
    tags: [], forbids: [].}
Find the index of a column by name in a cursor. A duplicated name resolves to its first column, as in columnIndex(fields, …).
func conn(cursor: Cursor): PgConnection {.inline, ...raises: [], tags: [],
    forbids: [].}
The connection this cursor was opened on.
func exhausted(cursor: Cursor): bool {.inline, ...raises: [], tags: [], forbids: [].}
True once the portal reported completion and no rows remain.
proc fetchNext(cursor: Cursor): Future[seq[Row]] {....stackTrace: false, raises: [
    Exception, ValueError, PgStateError, PgConnectionError, PgQueryError,
    PgTypeError, PgProtocolError, CatchableError, PgTimeoutError,
    CancelledError, AsyncTimeoutError],
    tags: [RootEffect, TimeEffect, WriteIOEffect], forbids: [].}
Fetch the next chunk of rows from the cursor. Returns an empty seq when the cursor is exhausted. A server error raises PgQueryError. The call that exhausts the cursor also closes the portal with a Sync, which commits the statement outside a transaction block; a commit failure (deferred constraint, serialization) raises too: that call's rows are discarded and the statement's writes rolled back. On timeout, the connection is retired (csClosed) unless the wire had settled (asyncdispatch always retires: the timed-out op stays on the socket). A closed connection raises PgStateError after a deliberate close(), PgConnectionError after a lost one. Concurrent fetchNext/close on the same cursor raises PgStateError.
func fields(cursor: Cursor): seq[FieldDescription] {.inline, ...raises: [],
    tags: [], forbids: [].}
Column metadata from the cursor's RowDescription (a snapshot copy).
proc openCursor(conn: PgConnection; sql: string; params: seq[PgParam] = @[];
                resultFormat: ResultFormat = rfAuto; chunkSize: int32 = 100;
                timeout: Duration = ZeroDuration): Future[Cursor] {.
    ...stackTrace: false, raises: [Exception, ValueError, PgQueryError,
                                PgStateError, PgConnectionError, PgTypeError,
                                PgMessageTooLargeError, PgProtocolError,
                                CatchableError, PgTimeoutError, CancelledError,
                                AsyncTimeoutError],
    tags: [RootEffect, TimeEffect, WriteIOEffect], forbids: [].}
Open a server-side cursor for streaming rows in chunks. chunkSize must be positive: 0 would mean Execute maxRows 0 (unlimited) and silently fetch all rows at once instead of streaming. The cursor holds conn until it is exhausted, closed or a fetch fails: any other operation on conn meanwhile raises PgStateError. withCursor closes it on scope exit. On timeout, the connection is retired (csClosed) unless the wire had settled (asyncdispatch always retires: the timed-out op stays on the socket). Raises PgTypeError for a non-positive chunkSize, and as fetchNext does, PgStateError / PgConnectionError on a closed connection and PgQueryError on a server error, including a commit failure when the first chunk exhausts the cursor.

Macros

macro withCursor(conn: PgConnection; sql: string; chunks: int32;
                 cursorName, body: untyped;
                 cursorTimeout: Duration = ZeroDuration): untyped

Open a cursor, execute body, then close the cursor automatically. chunks must be positive (see openCursor). The cursor is available as cursorName inside the body.

A failure in the automatic close never masks an exception raised by body: the body error is captured and re-raised after the close attempt. (A finally block cannot be used here — on asyncdispatch a failing await in a finally replaces the in-flight exception, silently discarding the body's error.) If body succeeds, a close failure propagates to the caller. A Defect from body is re-raised raw after the close attempt. (On chronos, a Defect raised in an awaited proc after it suspends escapes the event loop directly, so no close runs.)

Body return / break / continue that would escape the body are rejected at compile time so the close is not skipped (which would leave the connection busy with the open portal).