Errors and refusals
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:
| Field | Meaning |
|---|---|
code | category << 16 | detail. Switch on the category. |
retryable | 1 only for a transaction conflict. |
severity | 1: an error, the connection stays open. 2: fatal, the connection is closed after this frame. Only framing corruption and a failed handshake are fatal. |
message | The same text the text protocol shows. |
detail | Optional extra text. |
position | Optional byte position in the statement. |
After an S_ERROR, the server discards frames until the client’s next C_SYNC.
The error tokens
| Token | retryable | Meaning | What to do |
|---|---|---|---|
TXN_CONFLICT | 1 | Another 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_VIOLATION | 0 | A 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_VIOLATION | 0 | The 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_OUTCOME | 0 | The 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. |
UNSUPPORTED | 0 | The engine’s design cannot accept this form. No later version will. | Rewrite the statement. |
NOT_IMPLEMENTED | 0 | The 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:
- Run the transaction.
- On
retryable=1, sendROLLBACKand start again fromBEGIN. - 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.
| Group | Shown as | Means | Examples |
|---|---|---|---|
| InvalidArgument | ERR <message> | The statement is wrong. | Unknown column, wrong number of values, a literal of the wrong type, != in an assertion. |
| Unsupported | ERR 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. |
| NotImplemented | ERR 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.
| Situation | Reply |
|---|---|
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 DROP | ERR TXN_CONFLICT retryable=1 row id=<n> was written by transaction <n> (the message varies; the token does not) |
| Foreign-key violation | ERR FK_VIOLATION retryable=0 <message> |
| Assertion bound exceeded | ERR 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 transaction | ERR current transaction is aborted; commands are ignored until ROLLBACK |
BEGIN inside a transaction | ERR a transaction is already open; COMMIT or ROLLBACK first |
COMMIT or ROLLBACK with no transaction open | ERR no transaction is open |
| A row with the wrong number of values | ERR 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 integer | ERR 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 used | ERR 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 row | ERR duplicate primary key <k> already present at page <p> slot <s> (AlreadyExists, no position) |
ASSIGNED in CREATE TABLE | ERR UNSUPPORTED retryable=0 the ASSIGNED key mode no longer exists (byte <n>) - ... |
Assigning the primary key in UPDATE | ERR 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 UPDATE | ERR unknown column '<name>' at byte <n> |
A column assigned twice in one UPDATE | ERR column '<name>' is assigned more than once at byte <n> |
| Unknown statement | ERR unknown SQL keyword '<w>' (supported: CREATE, DROP, ALTER, INSERT, SELECT, UPDATE, DELETE, PURGE) |
Unknown SHOW target | ERR unknown SHOW target |
PURGE inside a transaction | ERR 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 table | ERR 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 integer | ERR 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 key | ERR 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 s | ERR 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 statement | ERR unexpected token '<t>' after end of statement |
decimal without precision and scale | ERR column '<c>' needs a precision and a scale - decimal(p, s) - at byte <n>; there is no default scale, ... |
A float column | Refused at CREATE TABLE: no on-disk encoding is decided. |
| A scalar subquery returns more than one row | CardinalityViolation, not retryable: ERR a scalar subquery returned more than one row; ... |
A statement reads more than max_rows_touched rows | ResourceExhausted, not retryable |
More rows than max_insert_rows in one INSERT | ERR INSERT of <n> rows exceeds max_insert_rows (<cap>) |
An explicit transaction older than txn_lifetime_ceiling_ms | RESOURCE_EXHAUSTED with the transaction-lifetime-ceiling detail, for every statement except ROLLBACK |
| A table’s id space is used up | OutOfRange. Ids never wrap. |
| A value wider than 8144 bytes | NotImplemented |
Comparing decimals of different scale or width, SUM(uint64), AVG of an integer column | Refused 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 full | ERR ... the WAL archive is failing (...) |
For backup and restore messages, see Back up and restore.
Related pages
- Limits: every cap and what happens when it is reached.
- BEGIN, COMMIT, ROLLBACK, SET
- Transactions and isolation