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.
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 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.
Consts
SqlStateAdminShutdown = "57P01"
SqlStateCannotConnectNow = "57P03"
SqlStateCheckViolation = "23514"
SqlStateConfigurationLimitExceeded = "53400"
SqlStateCrashShutdown = "57P02"
SqlStateDeadlockDetected = "40P01"
SqlStateDuplicateObject = "42710"
SqlStateExclusionViolation = "23P01"
SqlStateForeignKeyViolation = "23503"
SqlStateIdleInTransactionSessionTimeout = "25P03"
SqlStateIdleSessionTimeout = "57P05"
SqlStateInFailedSqlTransaction = "25P02"
SqlStateInsufficientPrivilege = "42501"
SqlStateInvalidCatalogName = "3D000"
SqlStateInvalidPassword = "28P01"
SqlStateLockNotAvailable = "55P03"
SqlStateNotNullViolation = "23502"
SqlStateObjectInUse = "55006"
SqlStateProtocolViolation = "08P01"
SqlStateQueryCanceled = "57014"
SqlStateSerializationFailure = "40001"
SqlStateSyntaxError = "42601"
SqlStateTransactionTimeout = "25P04"
SqlStateUndefinedTable = "42P01"
SqlStateUniqueViolation = "23505"
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).