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.
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).