Errors and refusals

v3.0.0

The shape of an error reply, the six error tokens, which errors are worth retrying, and the message for each common situation.

In this article

Every error is a reply that starts with ERR. Six error kinds carry a token and a retryable= flag that client code can switch on. Only one of them, TXN_CONFLICT, is worth retrying. Every other error fails the same way when you send the same statement again.

Reply shape

On the text protocol, an error is one line:

ERR <TOKEN> retryable=<0|1> <message>     for the six coded errors
ERR <message>                             for every other error

The six tokens are TXN_CONFLICT, FK_VIOLATION, ASSERTION_VIOLATION, UNKNOWN_OUTCOME, UNSUPPORTED and NOT_IMPLEMENTED. Their spelling and the retryable= spelling will not change. The message text is prose and can change; do not parse it.

On the binary protocol (KWP/1), an error is an S_ERROR frame with these fields:

FieldMeaning
codecategory << 16 | detail. Switch on the category.
retryable1 only for a transaction conflict.
severity1: an error, the connection stays open. 2: fatal, the connection is closed after this frame. Only framing corruption and a failed handshake are fatal.
messageThe same text the text protocol shows.
detailOptional extra text.
positionOptional byte position in the statement.

After an S_ERROR, the server discards frames until the client’s next C_SYNC.

The error tokens

TokenretryableMeaningWhat to do
TXN_CONFLICT1Another transaction wrote a row this one needed, and it committed. Also used when a constraint check, a CREATE ASSERTION, a PURGE or a DDL statement meets another transaction’s unfinished work, and for a deadlock victim.ROLLBACK, then run the whole transaction again.
FK_VIOLATION0A foreign-key check failed: the parent row is missing on INSERT or UPDATE, or a live child exists on a parent DELETE.Fix the data or the order of writes.
ASSERTION_VIOLATION0The write would take a group past an assertion’s bound. The message names the assertion, the group and the enforced bound.Report it; do not retry.
UNKNOWN_OUTCOME0The statement may have run. Kept in the protocol for compatibility; nothing in this engine produces it today.Read the data back. Do not retry: a retry of an INSERT with an issued key would add a second row.
UNSUPPORTED0The engine’s design cannot accept this form. No later version will.Rewrite the statement.
NOT_IMPLEMENTED0The design allows this form, but this version has not built it.Rewrite it, or check a later version.

Examples produced on the current engine:

kdbs> INSERT INTO tickets VALUES (1, 5, '12C', 0, 0, NULL);
ERR ASSERTION_VIOLATION retryable=0 assertion "seat_limit" group (flight_id=1): COUNT(*) would exceed bound 4
kdbs> SELECT p.name FROM passengers AS p LEFT JOIN tickets AS t ON t.passenger_id = p.id;
ERR NOT_IMPLEMENTED retryable=0 outer joins are not supported: 'LEFT' at byte 35; only inner equi-joins (JOIN ... ON) are available

Retrying

Retry on retryable=1 and on nothing else. The flag is one bit wide on purpose: TXN_CONFLICT is the only retryable error. retryable=0, or no flag at all, means the same statement will fail the same way.

A conflict reaches the client only when waiting could not resolve it. A writer that meets an unfinished transaction waits for it to commit or roll back first. The conflict message varies, for example:

ERR TXN_CONFLICT retryable=1 row id=<n> was written by transaction <n>

A retry loop:

  1. Run the transaction.
  2. On retryable=1, send ROLLBACK and start again from BEGIN.
  3. On any other error, stop and report it.

Errors inside a transaction

A statement that fails while it runs inside an explicit transaction aborts the transaction. Until you send ROLLBACK, the session accepts only ROLLBACK, ABORT, SYNC, STOP and PING:

kdbs> INSERT INTO tickets VALUES (4, 1, '20B', 5, 0, NULL);
ERR ASSERTION_VIOLATION retryable=0 assertion "baggage_cap" group (passenger_id=1): SUM(baggage_kg) would exceed bound 46
kdbs> COMMIT;
ERR current transaction is aborted; commands are ignored until ROLLBACK

In tests on this build, a statement refused before it ran did not abort the transaction: an unknown column, SET DURABILITY inside a transaction, and PURGE inside a transaction (documented as not aborting it). A refused COMMIT ... with extra words commits nothing and leaves the transaction open.

An autocommit statement is its own transaction and is undone completely when it fails.

InvalidArgument, Unsupported and NotImplemented

The engine sorts refusals into three groups. Only the last two have tokens.

GroupShown asMeansExamples
InvalidArgumentERR <message>The statement is wrong.Unknown column, wrong number of values, a literal of the wrong type, != in an assertion.
UnsupportedERR UNSUPPORTED retryable=0 ...Understood and declined; the architecture cannot admit it.Updating a primary key, float, comparing decimals of different width or scale, the removed ASSIGNED key mode, PURGE of a system or heap table.
NotImplementedERR NOT_IMPLEMENTED retryable=0 ...Understood and declined; a later version could build it.Outer joins, HAVING, UNIQUE indexes, ALTER TABLE ADD COLUMN, more than 8 ORDER BY keys, more than 4 index key columns, a value over 8144 bytes, PURGE inside a transaction.

Neither UNSUPPORTED nor NOT_IMPLEMENTED is retryable. The difference matters across versions, not across retries.

The bundled KWP client library (tools/kwp.py, used by ckdbs_cli.py) prints UNSUPPORTED errors without the token; the category is still in the error code:

kdbs> UPDATE passengers SET id = 9 WHERE id = 1;
ERR primary-key column 'id' cannot be updated at byte 22; it is the tuple's identity, not a field of it

Other error kinds have no token and appear as ERR <message>: OutOfRange, AlreadyExists, ResourceExhausted, CardinalityViolation, IoError, and permission errors.

Byte positions

Refusals of a statement’s text name where the problem is, as (byte <n>) or at byte <n> in the message, and in the position field on KWP/1. The position is a 0-based byte offset into the statement:

kdbs> SELECT nosuch FROM passengers;
ERR no relation in scope has a column 'nosuch' at byte 7

Some refusals carry no position, for example a key the engine issued itself.

Messages by situation

From the SQL manual’s error table. <...> marks values that vary.

SituationReply
Write conflict, foreign-key check meeting an unfinished writer, CREATE ASSERTION on a table with unfinished writes, a name held by another transaction’s uncommitted DROPERR TXN_CONFLICT retryable=1 row id=<n> was written by transaction <n> (the message varies; the token does not)
Foreign-key violationERR FK_VIOLATION retryable=0 <message>
Assertion bound exceededERR ASSERTION_VIOLATION retryable=0 <message naming the group and the enforced bound>. CHECK COUNT(*) < 5 refuses at 5 with “would exceed bound 4”.
Statement in an aborted transactionERR current transaction is aborted; commands are ignored until ROLLBACK
BEGIN inside a transactionERR a transaction is already open; COMMIT or ROLLBACK first
COMMIT or ROLLBACK with no transaction openERR no transaction is open
A row with the wrong number of valuesERR expected <n> value(s) including primary-key column '<name>', or <n-1> to have it issued; got <k>
A supplied primary key that is not an integerERR primary-key column '<name>' needs an integer literal (byte <n>)
A supplied primary key outside [1, 2^48 - 1]ERR primary key <k> is outside the Keystone id space [1, 281474976710655] (byte <n>) (OutOfRange)
A supplied primary key that is already usedERR duplicate primary key <k> already present at page <p> slot <s> (byte <n>), or for a deleted row’s key, ERR duplicate primary key <k>: a row with this key was deleted; PURGE frees its key (page <p> slot <s>) (byte <n>) (AlreadyExists)
An issued key that met a concurrently supplied key 8 times in a rowERR duplicate primary key <k> already present at page <p> slot <s> (AlreadyExists, no position)
ASSIGNED in CREATE TABLEERR UNSUPPORTED retryable=0 the ASSIGNED key mode no longer exists (byte <n>) - ...
Assigning the primary key in UPDATEERR UNSUPPORTED retryable=0 primary-key column '<name>' cannot be updated at byte <n>; it is the tuple's identity, not a field of it
Unknown SET target in UPDATEERR unknown column '<name>' at byte <n>
A column assigned twice in one UPDATEERR column '<name>' is assigned more than once at byte <n>
Unknown statementERR unknown SQL keyword '<w>' (supported: CREATE, DROP, ALTER, INSERT, SELECT, UPDATE, DELETE, PURGE)
Unknown SHOW targetERR unknown SHOW target
PURGE inside a transactionERR NOT_IMPLEMENTED retryable=0 PURGE inside a transaction is not implemented; run it in autocommit (byte 0). The transaction is not aborted.
PURGE of a system or heap tableERR UNSUPPORTED retryable=0 '<t>' is a system relation; PURGE frees keys of user relations only (byte <n>), or ... is a heap relation, ...
PURGE with no WHERE, or a condition that is not a primary-key comparison with an integerERR NOT_IMPLEMENTED retryable=0 PURGE needs a WHERE naming the primary key; ... (byte <n>), or ... PURGE admits only comparisons of the primary key with an integer literal (=, <, <=, >, >=, BETWEEN); this conjunct is not one (byte <n>)
PURGE naming one live key, or a negative keyERR primary key <k> is live; PURGE frees only a key whose row was deleted (byte <n>), or ERR primary key literal <k> is negative, and no key is below 1 (byte <n>)
PURGE blocked by an older snapshot or an unfinished writer for 1 sERR TXN_CONFLICT retryable=1 PURGE cannot free primary key <k> yet: ...; nothing was purged, and running it again once that ends will
Text after a complete statementERR unexpected token '<t>' after end of statement
decimal without precision and scaleERR column '<c>' needs a precision and a scale - decimal(p, s) - at byte <n>; there is no default scale, ...
A float columnRefused at CREATE TABLE: no on-disk encoding is decided.
A scalar subquery returns more than one rowCardinalityViolation, not retryable: ERR a scalar subquery returned more than one row; ...
A statement reads more than max_rows_touched rowsResourceExhausted, not retryable
More rows than max_insert_rows in one INSERTERR INSERT of <n> rows exceeds max_insert_rows (<cap>)
An explicit transaction older than txn_lifetime_ceiling_msRESOURCE_EXHAUSTED with the transaction-lifetime-ceiling detail, for every statement except ROLLBACK
A table’s id space is used upOutOfRange. Ids never wrap.
A value wider than 8144 bytesNotImplemented
Comparing decimals of different scale or width, SUM(uint64), AVG of an integer columnRefused at compile time, with a position
A role that is too low (with auth = scram)ERR permission: <cmd> needs <role>; this connection is <role>
A write while the WAL archive is failing and the WAL disk is nearly fullERR ... the WAL archive is failing (...)

For backup and restore messages, see Back up and restore.