Skip to Content
We are live but in Staging 🎉
Data EnginesSQLErrors & SQLSTATEs

Errors & SQLSTATEs

Connection and transient

SQLSTATEMeaningRetry?
08006The connection dropped, or an object-storage hop failed while the statement was in flight.Yes. Reconnect, then re-run. If the statement was a COMMIT, check whether it landed before replaying.
XX000Internal error. The message is preserved verbatim.Once. A repeat is a bug: report the message.
57014canceling statement due to statement timeout — your statement_timeout expired.Your call. The statement was cancelled, not applied.
53400A resource limit on the data plane was reached (for example the per-connection concurrency limit).Yes, with backoff.

Transactions

SQLSTATEMeaningRetry?
40001Serialization failure at COMMIT: a key this transaction wrote, or read under FOR UPDATE, was committed by another transaction first.Yes. Re-run the entire transaction from BEGIN and recompute every value. See Transactions.

Data

SQLSTATEMeaningExample
22001 since 0.1.43A string is longer than its declared VARCHAR(n) / CHAR(n). Nothing is stored, nothing is truncated.value too long for type character varying(3) — column "code"
22003A number is outside its column’s range (SMALLINT, 32-bit int, REAL overflow to infinity), a NUMERIC(p,s) exceeds its precision, or a value for a bare NUMERIC has more than 9 fractional digits. Nothing is wrapped or rounded away silently.value 40000 is out of range for column "s" (SMALLINT) · value for column "amount" is out of range for DECIMAL(18,2) · … out of range for NUMERIC(38,9): the scale was chosen by the engine (bare NUMERIC), and storing this value would round away digits past 9 fractional places
22012 since 0.1.43Division by zero in a NUMERIC expression.division by zero
22P02A value cannot be read as its column’s type: garbage in a BOOLEAN, DATE or TIMESTAMP, an impossible date such as 2026-13-45, a non-UUID string in a UUID column. Never stored as NULL.invalid input syntax for column "ts" (TIMESTAMP): "not-a-time" · invalid input syntax for column "u" (UUID): not-a-uuid

None of these is retryable.

Integrity

SQLSTATEMeaningExample
23505Primary key or UNIQUE INDEX violated. A plain INSERT of an existing key is this, by design.duplicate key value violates unique constraint "orders_pkey": Key (id)=(1) already exists.
23502NOT NULL violated, or a required column missing from the row.null value in column "user_id" violates not-null constraint
23514A CHECK constraint rejected the row.new row for relation "accounts" violates check constraint "accounts_balance_check"
23P90The write touches a row sealed by an IMMUTABLE rule (a closed accounting period, for instance).… sealed by immutable rule "period_2026_08"
23503An INSERT or UPSERT names a parent row that does not exist. Checked on the insert side only; deleting a parent with children succeeds.insert or update on table "child" violates foreign key constraint "child_pid_fkey": Key (id)=(999) is not present in table "parent".

Fix the data, not the retry loop. ORMs map these to their IntegrityError class.

Schema and SQL

SQLSTATEMeaningExample
42601Syntax error, or a statement outside the SQL surface: UPDATE/DELETE without WHERE, CREATE VIEW, CREATE SEQUENCE, LOCK TABLE, DROP INDEX, COPY … TO, DROP TABLE a, b, a function call inside VALUES. The message names the statement and the alternative.statement is not part of the tables SQL surface: CREATE VIEW …
42703Undefined column; also the unsupported ALTER TABLE forms (RENAME, SET DEFAULT, …) and a literal DEFAULT whose type does not match a NUMERIC column.column "nope" not found in table "t" · ALTER TABLE form not supported … · DEFAULT literal 0 does not match column type decimal(18,2)
42P01Undefined table; also pg_catalog objects and functions that are not emulated (pg_advisory_lock(), information_schema.table_constraints).table "orders" does not exist
42P07CREATE TABLE of a name that already exists (without IF NOT EXISTS).table "orders" already exists
42830A FOREIGN KEY references columns that are not the parent’s declared primary key.there is no unique constraint matching given keys for referenced table
42P01 (in DDL)A FOREIGN KEY names a parent table that does not exist. Create the parent first.table "parent" does not exist

Feature not supported

0A000 is reserved for features the engine recognises and has deliberately not implemented; the message always says what to do instead. Every other refused shape comes back as 42601 or 42703 with the alternative named in the message (see above). The 0A000 cases:

  • triggers are not supported yet: a synchronous plpgsql-subset trigger engine … is on the roadmap. Today, react to writes with an ingest rule → pipeline, or an Action; validation belongs in CHECK constraints. since 0.1.43
  • cannot use subquery in check constraint, and a CHECK with arithmetic or function calls — the message lists the supported predicate forms.
  • A FOREIGN KEY declared DEFERRABLE, INITIALLY DEFERRED, or with ON DELETE / ON UPDATE CASCADE / SET NULL / SET DEFAULT. The engine enforces only what its write gate can keep, so these options are refused at DDL rather than accepted and ignored. Drop the option; cascade in the application.

The full current list is on Limits & differences.

Authentication

SQLSTATEMeaning
28P01FATAL: authentication failed at connect. Wrong API-key secret, service-account secret or expired JWT. The secret is never echoed.
28000The credential is valid but may not use this database (bucket), or the session context was rejected.

Building a retry loop

RETRY_TXN = {"40001"} # re-run the whole transaction RETRY_CONN = {"08006"} # reconnect first RETRY_ONCE = {"XX000"} def should_retry(sqlstate, attempt): if sqlstate in RETRY_TXN or sqlstate in RETRY_CONN: return attempt < 8 if sqlstate in RETRY_ONCE: return attempt < 1 return False # 22xxx, 23xxx, 42xxx, 0A000, 28xxx: never

Two rules make the loop safe. First, retry the transaction, not the statement: a 40001 means the values you computed are stale. Second, make the transaction idempotent before you retry a connection failure, because the previous attempt may have committed; the transfer pattern shows the journal-row technique.

NOTICEs you will see

A NOTICE is not an error. It arrives on the same connection and most drivers hand it to a callback rather than raising.

WhenNotice saysMeaning
Bare NUMERIC / DECIMAL in CREATE TABLEthe column defaults to NUMERIC(38,9); declare NUMERIC(p,s)Declare the precision and scale you mean, e.g. NUMERIC(18,2) for money, NUMERIC(38,0) for integer minor units.
FOREIGN KEY on a cluster whose operator has turned FK enforcement offconstraint accepted for compatibility but NOT ENFORCED by the tables engineNot the default. If you see it, referential integrity is the application’s job on that cluster.
SELECT … FOR UPDATE outside a transactionrow-locking clause outside a transaction has no effect: served as a snapshot readWrap it in BEGIN … COMMIT if you meant to guard it.
TIMESTAMP WITHOUT TIME ZONE in DDLTIMESTAMP WITHOUT TIME ZONE is stored with timestamptz semantics …The column is stored as UTC microseconds.
SET TimeZone = …TimeZone has no effect on Dodil Tables: timestamps are stored and rendered in UTC …The statement succeeded and changed nothing.
ALTER TABLE … ADD CONSTRAINT … UNIQUEaccepted, not enforcedUse CREATE UNIQUE INDEX for the enforced form.

VARCHAR(n) and TIMESTAMP(p) no longer raise a precision notice: both are honoured since 0.1.43.

Reading them:

# psycopg 3 conn.add_notice_handler(lambda d: print(d.severity, d.message_primary))
// node-postgres client.on('notice', (msg) => console.warn(msg.message))

A strong read that would be incomplete

Under a sustained write burst a read can be refused rather than served partially:

strong read aborted: the WAL overlay truncated at N entries, so the result would be incomplete — compaction is catching up; retry shortly

On the Postgres wire this arrives as XX000 with that message; on gRPC it is FAILED_PRECONDITION. The engine never returns a partial answer in its place. Compaction is already scheduled when this fires, so a retry after a short pause converges. If you see it often, run dodil data table compact after your bulk loads; see Performance & layout.