async_postgres/pg_errors

Search:
Group by:

Exception hierarchy. Every library-raised exception derives from PgError.

The hierarchy encodes scope, not retryability (ask retryAdvice): a PgConnectionError (PgProtocolError, PgTimeoutError, PgSecurityError and PgUnavailableError included) ends one connection, so connect fails over past it and reconnect loops must see it; PgStateError and PgConfigError are deliberately siblings, being a programming error and a configuration fault that no reconnect fixes.

PgTypeError = a value the wire format or the local type cannot carry (parameter encoding and row/column decoding alike); PgQueryError = an error the server reported; ValueError = a precondition, and the one kind not under PgError (except DSN parsing).

Retry classification

retryAdvice returns one verdict per failure:

  • raRetry: a later attempt with the same config may succeed (a lost connection, a timeout, a transient SQLSTATE such as 40001). isTransientError is this case alone.
  • raStop: no attempt can succeed until something outside the process changes: a PgConfigError, a closed pool, or a refusal (isLastingRefusal: a startup answered with 28P01, 3D000 or 42501).
  • raUnclear: neither established (a PgProtocolError, a PgSecurityError, ...): retry within a budget.

raRetry does not make replaying safe (a connection lost during COMMIT may have committed), nor does it mean the connection is gone (check conn.state). Cap retries regardless: a mistyped host name or a server that stays down is transient too.

Types

ErrorField = object
  code*: char
  value*: string
A single field from an ErrorResponse or NoticeResponse message.
PgConfigError = object of PgError

A ConnConfig fault no retry can fix: a cert/key/CA that will not load, a cert without its key, an sslmode or channel_binding that contradicts another option. Every host shares one config, so connect raises it in place of the per-host PgConnectionError aggregate. A fault of a single host entry (a verify-full entry without a host name) is per host, not config-wide, and stays a PgConnectionError.

Deliberately not a PgConnectionError, so reconnect loops will not spin on it; a PgPool that sees one stops dialing (pekConfigFault).

PgConnectionError = object of PgError
  serverError*: ref PgQueryError ## The ErrorResponse behind the failure, nil if the server sent none: the
                                 ## one that rejected the session during startup, or the FATAL that ended
                                 ## it before the connection closed (e.g. ``57P01`` on shutdown). A
                                 ## statement's ERROR is never kept here, nor on ``connect``'s aggregate
                                 ## or ``sslmode=allow``'s pair: ``serverErrors`` collects their attempts'.
  attempts*: seq[ref CatchableError] ## The failed attempts this error sums up, in order: each host's latest
                                     ## failure for ``connect`` (a host that answered but did not match
                                     ## ``target_session_attrs`` included), the plaintext and SSL legs of
                                     ## ``sslmode=allow``, or a listen pump's last redial. Empty when it sums
                                     ## up nothing; ``connect``'s aggregate has one even for a single host.
Connection failures, disconnections, TLS handshake and auth errors. A TLS fault that stems from the config itself (a cert, key or CA that will not load) is a PgConfigError instead.
PgError = object of CatchableError
General PostgreSQL error. Base type for all pg-specific errors.
PgListenError = object of PgConnectionError
  reconnectionAttempted*: bool ## True if the pump attempted reconnection before giving up.
  transportAlive*: bool ## True when the pump died but the transport is still up. The pull API
                        ## raises ``PgListenStoppedError`` for that case instead.
Listen pump died permanently (reconnection failed or connection lost with no channels left to re-subscribe).
PgListenStoppedError = object of PgStateError
  reconnectionAttempted*: bool ## True if the pump attempted reconnection before giving up.
  transportAlive*: bool ## Always true here — the type is only raised for a live transport. Kept
                        ## so a caller reading the field need not special-case which error it got.
Listen pump gone from a connection that is still usable: call listen again to recover. Deliberately not a PgConnectionError, so a reconnect loop will not re-dial a live connection. Raised by waitNotification; the push API reports the same death as a PgListenError with transportAlive.
PgMessageTooLargeError = object of PgTypeError
An assembled protocol message exceeds the wire format's Int32 length. Caller data decides the size, so this is input, not connection health: nothing was sent and a reconnect loop must not fire.
PgNoRowsError = object of PgError
Raised by single-row/single-value queries when the result set is empty.
PgNotifyOverflowError = object of PgError
  dropped*: int              ## Number of notifications dropped due to queue overflow
PgNullError = object of PgError
Raised by single-value queries when the value is SQL NULL and the caller requested a non-nullable result.
PgPoolError = object of PgError
  kind*: PoolErrorKind       ## Failure category (see `PoolErrorKind`).

Pool-level acquire failure (closed, acquire timeout, queue full, connect failed, refused, config fault, unservable batch, or a wrapped user-code Defect; the underlying error is preserved as parent). Operation timeouts are not PgPoolError: a pipelined pool.exec / pool.query wait and the pool withTransactionDeadline / withTransactionRetryDeadline deadline raise PgTimeoutError instead, so except PgPoolError alone misses them — catch PgTimeoutError first. retryAdvice returns raRetry for both timeout types.

kind classifies the failure programmatically; the message string is informational only. Errors built without newPoolError have kind == pekUnknown.

PgProtocolError = object of PgConnectionError
Raised on PostgreSQL wire protocol violations. The connection stream is desynchronised after this error and must be torn down.
PgQueryError = object of PgError
  sqlState*: string          ## 5-char SQLSTATE code (e.g. "42P01"), empty if unavailable.
  severity*: string ## e.g. "ERROR", "FATAL"; the non-localized 'V' field when the server
                    ## sends it (PG 9.6+), else the possibly localized 'S'.
  detail*: string            ## DETAIL field, empty if not present.
  hint*: string              ## HINT field, empty if not present.
  fields*: seq[ErrorField] ## All raw ErrorResponse fields as sent by the server, including any not
                           ## covered by the named accessors below.

SQL execution error reported by the server (ErrorResponse).

The most common fields are stored directly; everything else the server sent (schema/table/column/constraint name, error position, …) is kept verbatim in fields and exposed through the accessors below, such as constraintName and position.

PgSecurityError = object of PgConnectionError

The client refused the connection to uphold a config requirement (sslmode=require or stronger, failing closed where the backend cannot verify; channel_binding=require; require_auth; direct-SSL ALPN) or a tampering check a genuine server always passes (SCRAM signature, nonce, message order and iteration floor; data injected after 'S'; SCRAM-SHA-256-PLUS offered without TLS).

It names the requirement that failed, not whether a misconfiguration or a MITM caused it. The server's own refusals (a TLS alert, a wrong password) stay plain PgConnectionError. Per host: connect fails over past it, where libpq stops.

PgStateError = object of PgError

An operation attempted on a connection that is alive but in the wrong state for it — most commonly a single connection used concurrently: a second query started while the first is still in flight finds the connection csBusy.

A programming error, not a connection failure: deliberately not a PgConnectionError, so reconnect loops will not spin on it. Give each concurrent caller its own connection (e.g. via a PgPool).

PgTimeoutError = object of PgConnectionError

Raised when an operation times out.

A timeout on a query/exec/copy/prepare retires the connection (csClosed) unless the wire had settled (asyncdispatch always retires, leaving the timed-out operation on the socket); a transaction deadline retires whenever the body may still be running, whatever the wire looks like. Either is reported as a PgConnectionError so a reconnect loop handles it. waitNotification and an acquire timeout inside withTransactionDeadline / withTransactionRetryDeadline leave the connection usable; catch PgTimeoutError before PgConnectionError to tell them apart.

Timeout types differ by phase: a pool acquire reports its own deadline as PgPoolError (pekAcquireTimeout), while every operation timeout — a pipelined pool.exec / pool.query wait and the pool withTransactionDeadline / withTransactionRetryDeadline deadline, including an acquire phase that expires under it — raises PgTimeoutError. An except PgPoolError clause alone therefore misses operation timeouts; catch PgTimeoutError first. retryAdvice returns raRetry for both timeout types, so a retry loop can treat them alike.

PgTypeError = object of PgError

Raised when the caller's data cannot be carried by the wire format: a value that will not convert to or from the requested Nim type, or one the protocol cannot encode (count past Int16, length past Int32, embedded NUL).

Decode-failure messages report input lengths/offsets only, never the content (cells may hold PII or secrets), with positions written as (len=…, pos=…) where pos is a 0-based byte offset into the input.

A column accessor reports the cell it was given as "Column N: " (0-based) for the failures it attributes to that cell: a NULL cell, a malformed payload, or a wire element OID the payload carries (wire elemOid=). A mismatch judged from the column description instead (wire colOid=) names the accessor only.

PgUnavailableError = object of PgConnectionError
The server could not take the session now or went away (a lost connection, a failed lookup, a refused dial, ...). Transient unless its serverError says otherwise.
PoolErrorKind = enum
  pekUnknown, ## Default: a `PgPoolError` built without an explicit `kind`; do not
               ## treat as `pekClosed`.
  pekClosed,                ## The pool is permanently closed; retrying cannot succeed.
  pekAcquireTimeout, ## An acquire deadline elapsed (`acquireTimeout` or cluster fallback);
                      ## retrying later may succeed. Operation timeouts (a pipelined
                      ## `pool.exec` / `pool.query` wait, a pool `withTransactionDeadline` /
                      ## `withTransactionRetryDeadline` deadline) raise `PgTimeoutError`
                      ## instead, even when the deadline variant expires during its acquire.
  pekQueueFull, ## The acquire waiter queue — or, when `pipelined`, the `pendingOps`
                 ## queue — is full (`maxWaiters` bound); retrying later may succeed.
  pekConnectFailed, ## A connect attempt failed during acquire (underlying error in `parent`);
                     ## retrying may succeed. A refusal surfaces as `pekRefused` instead.
  pekRefused, ## A server refused the session for a cause that recurs for the same
               ## config (preserved as `parent`): a wrong password, a database that does
               ## not exist. See `PgPool.connectRefusal`.
  pekConfigFault, ## The pool's `connConfig` can never connect: a connect raised
                   ## `PgConfigError` (preserved as `parent`). Retrying cannot succeed, so
                   ## the pool stops opening connections and every later acquire that needs
                   ## one fails with this kind without dialing.
  pekBatchFailed,           ## A pipelined batch was unservable; no connection was acquired.
  pekDefectWrapped ## A user-code `Defect` (body/release block or session reset) wrapped to
                   ## cross an async boundary; preserved as `parent`.
Machine-readable category of a PgPoolError.
ProtocolError {....deprecated: "use PgProtocolError".} = PgProtocolError
Deprecated: use PgProtocolError
Deprecated alias for PgProtocolError, kept for backwards compatibility.
RetryAdvice = enum
  raRetry,                  ## A later attempt with the same config may succeed.
  raUnclear,                ## Neither known to clear nor known to be permanent: retry within a budget.
  raStop ## Retrying cannot succeed until something outside the process changes: a
         ## config fault, a closed pool, or a refusal (see `isLastingRefusal`).
What a retry loop should do about a failure, as one verdict.

Procs

func columnName(e: ref PgQueryError): string {....raises: [], tags: [], forbids: [].}
Column the error refers to.
func constraintName(e: ref PgQueryError): string {....raises: [], tags: [],
    forbids: [].}
Constraint the error refers to (e.g. the violated unique index).
func dataTypeName(e: ref PgQueryError): string {....raises: [], tags: [],
    forbids: [].}
Data type the error refers to.
func errorField(e: ref PgQueryError; code: char): string {....raises: [], tags: [],
    forbids: [].}
Raw ErrorResponse field by single-char code, "" if not present.
func getErrorField(fields: seq[ErrorField]; code: char): string {....raises: [],
    tags: [], forbids: [].}
Get the value of an error field by its single-char code (e.g. 'M' for message).
func internalPosition(e: ref PgQueryError): int {....raises: [], tags: [],
    forbids: [].}
Like position but for internalQuery, 0 if not reported.
func internalQuery(e: ref PgQueryError): string {....raises: [], tags: [],
    forbids: [].}
Text of the internally-generated query that failed (e.g. inside a function).
func isCheckViolation(e: ref PgQueryError): bool {....raises: [], tags: [],
    forbids: [].}
func isDeadlockDetected(e: ref PgQueryError): bool {....raises: [], tags: [],
    forbids: [].}
func isDuplicateObject(e: ref PgQueryError): bool {....raises: [], tags: [],
    forbids: [].}
func isExclusionViolation(e: ref PgQueryError): bool {....raises: [], tags: [],
    forbids: [].}
func isForeignKeyViolation(e: ref PgQueryError): bool {....raises: [], tags: [],
    forbids: [].}
func isIntegrityConstraintViolation(e: ref PgQueryError): bool {....raises: [],
    tags: [], forbids: [].}
Any SQLSTATE in class 23 (integrity constraint violation).
func isLastingRefusal(e: ref CatchableError): bool {....raises: [], gcsafe,
    tags: [], forbids: [].}

Whether e reports a server refusing the session for a cause that recurs for the same config (a wrong password, a database that does not exist). One is enough: the server has stated it.

Only the ErrorResponse that answered a startup counts, and only a 28P01, 3D000 or 42501 one; a FATAL that ended an established session never does. Narrower than not isTransientError(e): an unclassified failure such as PgProtocolError, 08P01, a proxy's XX000 or a 28000 (an LDAP server that is down answers with it too) is not a refusal, and a fresh dial may get past it. An error summing up attempts is one only when every attempt is: another host or sslmode=allow leg may still take the session, so a mix is raUnclear.

func isNotNullViolation(e: ref PgQueryError): bool {....raises: [], tags: [],
    forbids: [].}
func isQueryCanceled(e: ref PgQueryError): bool {....raises: [], tags: [],
    forbids: [].}
func isSerializationFailure(e: ref PgQueryError): bool {....raises: [], tags: [],
    forbids: [].}
func isSessionFatal(severity: string): bool {....raises: [], tags: [], forbids: [].}
Whether an ErrorResponse of severity ends the session: a FATAL or PANIC, never a statement's ERROR.
func isTransientError(e: ref CatchableError): bool {....raises: [], gcsafe,
    tags: [], forbids: [].}

Whether retrying what failed with e may succeed later with the same config: a PgUnavailableError or PgTimeoutError, a single host's connectTimeout, a pool's full queue or acquire timeout, or a server error whose SQLSTATE may clear (40001, 57P03, a FATAL 08xxx, ...). Errors with attempts are judged by them (for connect, every host must clear), and the 25P02 raised for a COMMIT answered with ROLLBACK by its parent; anything unclassified is not transient.

True does not mean the connection is gone (check conn.state) or that replaying is safe (a lost COMMIT may have committed). Cap retries.

A loop deciding when to give up wants retryAdvice: false here does not separate raStop from raUnclear.

func isUniqueViolation(e: ref PgQueryError): bool {....raises: [], tags: [],
    forbids: [].}
proc newStartupError(msg: string; serverError: ref PgQueryError): ref PgConnectionError {.
    inline, ...raises: [], tags: [], forbids: [].}
The ErrorResponse that answered the startup. Sibling-only, like setPerHost.
func position(e: ref PgQueryError): int {....raises: [], tags: [], forbids: [].}
1-based character index into the original query where the error occurred, 0 if the server did not report a position.
func retryAdvice(e: ref CatchableError): RetryAdvice {....raises: [], gcsafe,
    tags: [], forbids: [].}

The three-valued verdict for one failure: what a retry loop should call to decide whether to keep going.

A PgSecurityError is raUnclear even where it recurs for the same config: require_auth or channel_binding=require the server does not meet, a verified SSL connection without a host name. Some clear on a server-side change (SSL or SCRAM turned on) and the error does not say which, so a loop meets the rest on every attempt: bound it. A requirement no server can meet is a PgConfigError before any dial, so raStop.

func schemaName(e: ref PgQueryError): string {....raises: [], tags: [], forbids: [].}
Schema containing the object the error refers to.
func serverErrors(e: ref Exception): seq[ref PgQueryError] {....raises: [],
    tags: [], forbids: [].}
Every ErrorResponse behind e, depth-first: a PgConnectionError's serverError, then its attempts'; a PgQueryError that is a FATAL or PANIC; any other error's parent's (e.g. a PgPoolError wrapping connect's failure). A statement's ERROR is left out, as serverError leaves it.
proc setPerHost(e: ref PgConnectionError; value: bool) {.inline, ...raises: [],
    tags: [], forbids: [].}
Set perHost. Sibling-only: the hub does not re-export it.
func tableName(e: ref PgQueryError): string {....raises: [], tags: [], forbids: [].}
Table the error refers to.
func where(e: ref PgQueryError): string {....raises: [], tags: [], forbids: [].}
Context call stack (PL/pgSQL traceback etc.).

Templates

template newPoolError(errKind: PoolErrorKind; message: string;
                      parentErr: ref Exception = nil): untyped
Create a PgPoolError with the given errKind (see PoolErrorKind).