Skip to main content

nodedb_types/error/
sqlstate.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Standard PostgreSQL SQLSTATE code constants.
4//!
5//! A single source of truth for every five-character SQLSTATE string used
6//! across the codebase. Grouping follows the PostgreSQL documentation
7//! appendix (Class 00–XX).  All constants are `&'static str` so they compose
8//! directly with `pgwire::error::ErrorInfo` without any allocation.
9//!
10//! Add new codes here when a new error path needs one; never inline a literal
11//! elsewhere — a typo in a SQLSTATE string is undetectable at compile time.
12
13// ── Class 00 — Successful Completion ────────────────────────────────────────
14
15/// `00000` — `successful_completion`
16pub const SUCCESS: &str = "00000";
17
18// ── Class 01 — Warning ───────────────────────────────────────────────────────
19
20/// `01000` — `warning` (generic warning)
21pub const WARNING: &str = "01000";
22
23// ── Class 02 — No Data ───────────────────────────────────────────────────────
24
25/// `02000` — `no_data` (document / row not found)
26pub const NO_DATA: &str = "02000";
27
28// ── Class 0A — Feature Not Supported ────────────────────────────────────────
29
30/// `0A000` — `feature_not_supported`
31pub const FEATURE_NOT_SUPPORTED: &str = "0A000";
32
33/// `0A000` — Cannot drop the built-in `default` database, which is immutable.
34/// Aliased to `feature_not_supported` per the PostgreSQL convention for
35/// unsupported DDL operations on reserved objects.
36pub const CANNOT_DROP_DEFAULT_DATABASE: &str = "0A000";
37
38// ── Class 22 — Data Exception ────────────────────────────────────────────────
39
40/// `22003` — `numeric_value_out_of_range`
41pub const NUMERIC_VALUE_OUT_OF_RANGE: &str = "22003";
42
43// ── Class 23 — Integrity Constraint Violation ────────────────────────────────
44
45/// `23000` — `integrity_constraint_violation` (generic)
46pub const INTEGRITY_CONSTRAINT_VIOLATION: &str = "23000";
47
48/// `23502` — `not_null_violation`
49pub const NOT_NULL_VIOLATION: &str = "23502";
50
51/// `23503` — `foreign_key_violation` (dangling-edge rejection)
52pub const FOREIGN_KEY_VIOLATION: &str = "23503";
53
54/// `23505` — `unique_violation`
55pub const UNIQUE_VIOLATION: &str = "23505";
56
57/// `23514` — `check_violation`
58pub const CHECK_VIOLATION: &str = "23514";
59
60/// `23601` — NodeDB extension: append-only write rejected.
61pub const APPEND_ONLY_VIOLATION: &str = "23601";
62
63/// `23602` — NodeDB extension: balance constraint violated.
64pub const BALANCE_VIOLATION: &str = "23602";
65
66/// `23603` — NodeDB extension: period lock; writes rejected.
67pub const PERIOD_LOCKED: &str = "23603";
68
69/// `23604` — NodeDB extension: state-transition constraint violated.
70pub const STATE_TRANSITION_VIOLATION: &str = "23604";
71
72/// `23605` — NodeDB extension: transition-check constraint violated.
73pub const TRANSITION_CHECK_VIOLATION: &str = "23605";
74
75/// `23606` — NodeDB extension: retention policy blocks deletion.
76pub const RETENTION_VIOLATION: &str = "23606";
77
78/// `23607` — NodeDB extension: legal hold blocks deletion.
79pub const LEGAL_HOLD_ACTIVE: &str = "23607";
80
81/// `23608` — NodeDB extension: type-guard constraint violated.
82pub const TYPE_GUARD_VIOLATION: &str = "23608";
83
84// ── Class 28 — Invalid Authorization Specification ───────────────────────────
85
86/// `28000` — `invalid_authorization_specification` (no valid credentials)
87pub const INVALID_AUTHORIZATION: &str = "28000";
88
89// ── Class 40 — Transaction Rollback ──────────────────────────────────────────
90
91/// `40001` — `serialization_failure` (write conflict; client should retry)
92pub const SERIALIZATION_FAILURE: &str = "40001";
93
94// ── Class 42 — Syntax Error or Access Rule Violation ─────────────────────────
95
96/// `42501` — `insufficient_privilege`
97pub const INSUFFICIENT_PRIVILEGE: &str = "42501";
98
99/// `42601` — `syntax_error`
100pub const SYNTAX_ERROR: &str = "42601";
101
102/// `42846` — `cannot_coerce`
103pub const CANNOT_COERCE: &str = "42846";
104
105/// `42P01` — `undefined_table` (collection not found)
106pub const UNDEFINED_TABLE: &str = "42P01";
107
108// ── Class 53 — Insufficient Resources ────────────────────────────────────────
109
110/// `53200` — `out_of_memory`
111pub const OUT_OF_MEMORY: &str = "53200";
112
113/// `53300` — `too_many_connections` (closest match for rate-limit denial)
114pub const TOO_MANY_CONNECTIONS: &str = "53300";
115
116/// `53400` — `configuration_limit_exceeded` (quota exceeded)
117pub const CONFIGURATION_LIMIT_EXCEEDED: &str = "53400";
118
119// ── Class 54 — Program Limit Exceeded ────────────────────────────────────────
120
121/// `54000` — `program_limit_exceeded` (generic over-cap)
122pub const PROGRAM_LIMIT_EXCEEDED: &str = "54000";
123
124/// `54001` — `statement_too_complex` (fan-out / rate limit exceeded)
125pub const STATEMENT_TOO_COMPLEX: &str = "54001";
126
127// ── Class 55 — Object Not In Prerequisite State ──────────────────────────────
128
129/// `55P03` — `lock_not_available` (no cluster leader)
130pub const LOCK_NOT_AVAILABLE: &str = "55P03";
131
132// ── Class 57 — Operator Intervention ─────────────────────────────────────────
133
134/// `57014` — `query_canceled` (deadline exceeded)
135pub const QUERY_CANCELED: &str = "57014";
136
137/// `57P03` — `cannot_connect_now` (collection is draining)
138pub const CANNOT_CONNECT_NOW: &str = "57P03";
139
140/// `57P04` — `database_dropped` (not-leader redirect; client should retry elsewhere)
141pub const DATABASE_DROPPED: &str = "57P04";
142
143// ── Quota-specific aliases (Class 53 / 57) ───────────────────────────────────
144//
145// PostgreSQL class 53 "Insufficient Resources" is the closest match for quota
146// exhaustion conditions.  Class 57P03 "cannot_connect_now" covers transient
147// overload situations where the server is running but cannot accept the request.
148
149/// `53400` — `configuration_limit_exceeded`: sum of tenant/database quotas would
150/// exceed the configured global or parent ceiling (`QUOTA_OVERCOMMIT`).
151/// Alias for [`CONFIGURATION_LIMIT_EXCEEDED`].
152pub const QUOTA_OVERCOMMIT: &str = "53400";
153
154/// `53400` — `configuration_limit_exceeded`: tenant or database has exhausted its
155/// configured resource budget (`TENANT_QUOTA_EXCEEDED`, `DATABASE_QUOTA_EXCEEDED`).
156/// Class 53 is preferred over 54 because the limit is a runtime configuration
157/// setting, not a hard-coded program limit.
158pub const QUOTA_EXCEEDED: &str = "53400";
159
160/// `57P03` — `cannot_connect_now`: server is under global resource pressure and
161/// cannot accept new requests (`SERVER_OVERLOAD`). Using `57P03` rather than
162/// `53300` (too_many_connections) because the condition is transient and the
163/// server may accept requests again shortly — clients should retry after backoff.
164pub const SERVER_OVERLOAD: &str = "57P03";
165
166// ── Clone DDL (Class 54 / 55 / 0A) ──────────────────────────────────────────
167
168/// `54011` — NodeDB extension: clone chain depth exceeds `MAX_CLONE_DEPTH`.
169///
170/// Uses Class 54 "Program Limit Exceeded" because the limit is a hard-coded
171/// structural cap (depth 8), not a runtime quota setting.
172pub const CLONE_DEPTH_EXCEEDED: &str = "54011";
173
174/// `0A000` — NodeDB extension: a mirror database cannot be cloned.
175///
176/// Aliased to `feature_not_supported` — cloning a mirror creates ambiguous
177/// lineage; the operator must promote the mirror to a writable database first.
178pub const CANNOT_CLONE_MIRROR: &str = "0A000";
179
180/// `55006` — NodeDB extension: source database has active clone dependents.
181///
182/// Uses Class 55 "Object Not In Prerequisite State" because the source is in
183/// the correct state for normal use but cannot be dropped until dependents are
184/// resolved.
185pub const CLONE_DEPENDENCY: &str = "55006";
186
187/// `22023` — NodeDB extension: `AS OF` timestamp predates the clone's
188/// creation point; the database did not exist at that time.
189///
190/// Uses Class 22 "Data Exception" / `22023` (invalid parameter value) because
191/// the user-supplied timestamp is valid in general but out of range for this
192/// specific clone.
193pub const CLONE_PREDATES_QUERY_TIME: &str = "22023";
194
195/// `55P03` — NodeDB extension: a strong or bounded-staleness read was requested
196/// on a mirror database that cannot satisfy the requested consistency level.
197///
198/// Uses Class 55 "Object Not In Prerequisite State" because the mirror is a
199/// valid database but is not in the state (Raft leader) required to serve the
200/// requested consistency level. The client should redirect to the source cluster.
201pub const STALE_READ_NOT_LEADER: &str = "55P03";
202
203// ── Move Tenant DDL (Class 55 / 57) ─────────────────────────────────────────
204
205/// `57014` — `query_canceled`: drain phase timed out; client should re-try after
206/// ensuring the tenant has no active connections on the source database.
207pub const MOVE_TENANT_DRAIN_TIMEOUT: &str = "57014";
208
209/// `55P02` — `lock_not_available`: pre-flight check found schema incompatibility
210/// between the source and target databases; no state was mutated.
211pub const MOVE_TENANT_PREFLIGHT_FAILED: &str = "55P02";
212
213/// `XX000` — internal error during snapshot phase; source left unchanged.
214pub const MOVE_TENANT_SNAPSHOT_FAILED: &str = "XX000";
215
216/// `XX000` — internal error during cutover phase; source still holds data.
217pub const MOVE_TENANT_CUTOVER_FAILED: &str = "XX000";
218
219/// `02000` — `no_data`: tenant is already present in the target database;
220/// the `MOVE TENANT` is a no-op (idempotent retry of a completed move).
221pub const MOVE_TENANT_ALREADY_AT_TARGET: &str = "02000";
222
223// ── Class XX — Internal Error ────────────────────────────────────────────────
224
225/// `XX000` — `internal_error`
226pub const INTERNAL_ERROR: &str = "XX000";
227
228#[cfg(test)]
229mod tests {
230    use super::*;
231
232    #[test]
233    fn all_codes_are_five_chars() {
234        let codes = [
235            SUCCESS,
236            WARNING,
237            NO_DATA,
238            FEATURE_NOT_SUPPORTED,
239            NUMERIC_VALUE_OUT_OF_RANGE,
240            INTEGRITY_CONSTRAINT_VIOLATION,
241            NOT_NULL_VIOLATION,
242            FOREIGN_KEY_VIOLATION,
243            UNIQUE_VIOLATION,
244            CHECK_VIOLATION,
245            APPEND_ONLY_VIOLATION,
246            BALANCE_VIOLATION,
247            PERIOD_LOCKED,
248            STATE_TRANSITION_VIOLATION,
249            TRANSITION_CHECK_VIOLATION,
250            RETENTION_VIOLATION,
251            LEGAL_HOLD_ACTIVE,
252            TYPE_GUARD_VIOLATION,
253            INVALID_AUTHORIZATION,
254            SERIALIZATION_FAILURE,
255            INSUFFICIENT_PRIVILEGE,
256            SYNTAX_ERROR,
257            CANNOT_COERCE,
258            UNDEFINED_TABLE,
259            OUT_OF_MEMORY,
260            TOO_MANY_CONNECTIONS,
261            CONFIGURATION_LIMIT_EXCEEDED,
262            PROGRAM_LIMIT_EXCEEDED,
263            STATEMENT_TOO_COMPLEX,
264            LOCK_NOT_AVAILABLE,
265            QUERY_CANCELED,
266            CANNOT_CONNECT_NOW,
267            DATABASE_DROPPED,
268            INTERNAL_ERROR,
269            CANNOT_DROP_DEFAULT_DATABASE,
270            QUOTA_OVERCOMMIT,
271            QUOTA_EXCEEDED,
272            SERVER_OVERLOAD,
273            CLONE_DEPTH_EXCEEDED,
274            CANNOT_CLONE_MIRROR,
275            CLONE_DEPENDENCY,
276            CLONE_PREDATES_QUERY_TIME,
277            STALE_READ_NOT_LEADER,
278        ];
279        for code in &codes {
280            assert_eq!(
281                code.len(),
282                5,
283                "SQLSTATE '{code}' must be exactly 5 characters"
284            );
285        }
286    }
287
288    #[test]
289    fn spot_check_well_known_codes() {
290        assert_eq!(UNIQUE_VIOLATION, "23505");
291        assert_eq!(UNDEFINED_TABLE, "42P01");
292        assert_eq!(INSUFFICIENT_PRIVILEGE, "42501");
293        assert_eq!(QUERY_CANCELED, "57014");
294        assert_eq!(INTERNAL_ERROR, "XX000");
295        assert_eq!(FEATURE_NOT_SUPPORTED, "0A000");
296    }
297}