1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
// SPDX-License-Identifier: Apache-2.0
//! Stable numeric error codes for programmatic error handling.
use std::fmt;
use serde::{Deserialize, Serialize};
/// Stable numeric error codes for programmatic error handling.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ErrorCode(pub u16);
/// Defines each named code once, and [`ErrorCode::ALL`] from the same list,
/// so the list of every code cannot miss a constant.
macro_rules! error_codes {
( $( $(#[$meta:meta])* $name:ident = $value:literal; )* ) => {
impl ErrorCode {
$(
$(#[$meta])*
pub const $name: Self = Self($value);
)*
/// Every named code, in declaration order.
pub const ALL: &'static [ErrorCode] = &[ $( Self::$name ),* ];
}
};
}
error_codes! {
// Write path (1000–1099)
CONSTRAINT_VIOLATION = 1000;
WRITE_CONFLICT = 1001;
DEADLINE_EXCEEDED = 1002;
PREVALIDATION_REJECTED = 1003;
APPEND_ONLY_VIOLATION = 1010;
BALANCE_VIOLATION = 1011;
PERIOD_LOCKED = 1012;
STATE_TRANSITION_VIOLATION = 1013;
TRANSITION_CHECK_VIOLATION = 1014;
RETENTION_VIOLATION = 1015;
LEGAL_HOLD_ACTIVE = 1016;
/// A period-lock reference row exists but does not carry the
/// configured `status_column` — a misconfigured column name, not a
/// locked period.
PERIOD_LOCK_MISCONFIGURED = 1017;
TYPE_MISMATCH = 1020;
OVERFLOW = 1021;
INSUFFICIENT_BALANCE = 1022;
RATE_EXCEEDED = 1023;
TYPE_GUARD_VIOLATION = 1024;
/// A transaction rolled back for a reason other than a serialization
/// conflict, such as a participant error. The client retries it.
TRANSACTION_ROLLBACK = 1030;
/// The statement cannot run inside an explicit transaction block.
ACTIVE_SQL_TRANSACTION = 1031;
// Read path (1100–1199)
COLLECTION_NOT_FOUND = 1100;
DOCUMENT_NOT_FOUND = 1101;
COLLECTION_DRAINING = 1102;
COLLECTION_DEACTIVATED = 1103;
/// The named database does not exist.
DATABASE_NOT_FOUND = 1110;
/// A named catalog object (type, role, index, alert, …) other than a
/// collection or database does not exist. Generic: use the object name
/// in the message for specifics.
UNDEFINED_OBJECT = 1111;
/// A named catalog object already exists under that name.
ALREADY_EXISTS = 1112;
/// The target object exists but is not in a state that accepts this
/// operation (locked, busy, mid-transition).
OBJECT_NOT_READY = 1113;
/// A requested value/record does not exist. Generic: use for lookups
/// that don't fit `DOCUMENT_NOT_FOUND`'s collection/id shape.
NOT_FOUND = 1114;
/// A drop or revoke refused because other objects still depend on the
/// named object.
DEPENDENT_OBJECTS_EXIST = 1115;
// Query (1200–1299)
PLAN_ERROR = 1200;
SQL_NOT_ENABLED = 1202;
/// A function call names no registered scalar/aggregate/window function.
UNDEFINED_FUNCTION = 1203;
/// Expression evaluation divided or took a modulus by zero.
DIVISION_BY_ZERO = 1204;
/// A LIMIT/OFFSET/FETCH bound resolved outside `[0, usize::MAX]`.
INVALID_LIMIT_VALUE = 1205;
/// A column reference names no column of any relation in scope.
UNDEFINED_COLUMN = 1206;
/// A bare column name resolves against more than one relation in scope.
AMBIGUOUS_COLUMN = 1207;
/// A function received a value it cannot compute on: a vector of the
/// wrong dimension, an argument of the wrong shape, a malformed path.
DATA_EXCEPTION = 1208;
/// A statement exceeded a server limit on its own size or depth: a
/// recursion depth, a per-transaction staging budget.
PROGRAM_LIMIT_EXCEEDED = 1209;
// Engine ops (1300–1399)
ARRAY = 1300;
// Quota (1400–1499)
/// The proposed quota allocation would push the sum of all database quotas
/// past the configured global ceiling, or the sum of all tenant quotas past
/// the database ceiling.
QUOTA_OVERCOMMIT = 1400;
/// A request was rejected because the calling tenant has exhausted its quota
/// (QPS, memory, connections, or storage).
TENANT_QUOTA_EXCEEDED = 1401;
/// A request was rejected because the target database has exhausted its quota.
DATABASE_QUOTA_EXCEEDED = 1402;
/// The server is under global resource pressure and cannot accept new requests.
SERVER_OVERLOAD = 1403;
// Clone (1500–1599)
/// A `CLONE DATABASE` would exceed the maximum clone chain depth of 8.
CLONE_DEPTH_EXCEEDED = 1500;
/// A mirror database cannot be cloned; promote the mirror first.
CANNOT_CLONE_MIRROR = 1501;
/// The source database cannot be dropped while clones depend on it.
CLONE_DEPENDENCY = 1502;
/// A bitemporal `AS OF` query timestamp predates the clone's creation LSN.
CLONE_PREDATES_QUERY_TIME = 1503;
/// A write targeted a `Shadowed`/`Materializing` clone collection whose
/// engine has no copy-on-write support; `MATERIALIZE` the clone first.
CLONE_WRITE_REQUIRES_MATERIALIZE = 1504;
// Mirror (1700–1799)
/// Write attempted on a mirror database that has not yet been promoted.
MIRROR_READ_ONLY = 1700;
/// Strong consistency read requested on a mirror; mirrors cannot serve
/// strong reads. The client should retry against the source cluster.
STALE_READ_NOT_LEADER = 1701;
/// Operation requires the mirror to be promoted, but it has not been.
MIRROR_NOT_PROMOTED = 1702;
/// `DROP DATABASE` targeted the built-in `default` database, which is
/// immutable. Shares SQLSTATE `0A000` with `SQL_NOT_ENABLED` and
/// `CANNOT_CLONE_MIRROR`, so it must be constructed explicitly rather
/// than derived from the bare SQLSTATE string.
CANNOT_DROP_DEFAULT_DATABASE = 1710;
// Move Tenant (1600–1699)
/// `MOVE TENANT` drain phase timed out; source left unchanged.
MOVE_TENANT_DRAIN_TIMEOUT = 1600;
/// `MOVE TENANT` pre-flight failed; collection schema incompatibility.
MOVE_TENANT_PREFLIGHT_FAILED = 1601;
/// `MOVE TENANT` snapshot phase failed; source left unchanged.
MOVE_TENANT_SNAPSHOT_FAILED = 1602;
/// `MOVE TENANT` cutover phase failed; source still holds the data.
MOVE_TENANT_CUTOVER_FAILED = 1603;
/// Tenant is already at the target database; `MOVE TENANT` was a no-op.
MOVE_TENANT_ALREADY_AT_TARGET = 1604;
// Backup / Restore (1800–1899)
/// RESTORE targeted a tenant different from the one the envelope belongs to.
BACKUP_TENANT_MISMATCH = 1800;
/// Backup envelope did not decrypt under this server's configured backup KEK.
BACKUP_KEY_MISMATCH = 1801;
// Auth / Security (2000–2099)
AUTHORIZATION_DENIED = 2000;
AUTH_EXPIRED = 2001;
/// Authentication failed: a wrong password, an unknown user, or missing
/// credentials. One code for all of them, so a client cannot tell them
/// apart.
AUTHENTICATION_FAILED = 2002;
/// Vector insert or index rejected because the vector dimension exceeds the
/// tenant's `max_vector_dim` quota.
TENANT_VECTOR_DIM_EXCEEDED = 2010;
/// Graph traversal rejected because the requested depth exceeds the tenant's
/// `max_graph_depth` quota.
TENANT_GRAPH_DEPTH_EXCEEDED = 2011;
// Protocol handshake (2100–2199)
HANDSHAKE_FAILED = 2100;
// Sync (3000–3099)
SYNC_CONNECTION_FAILED = 3000;
SYNC_DELTA_REJECTED = 3001;
SHAPE_SUBSCRIPTION_FAILED = 3002;
// Storage (4000–4099)
STORAGE = 4000;
SEGMENT_CORRUPTED = 4001;
COLD_STORAGE = 4002;
// WAL (4100–4199)
WAL = 4100;
// Serialization (4200–4299)
SERIALIZATION = 4200;
CODEC = 4201;
// Config (5000–5099)
CONFIG = 5000;
BAD_REQUEST = 5001;
// Cluster (6000–6099)
NO_LEADER = 6000;
NOT_LEADER = 6001;
MIGRATION_IN_PROGRESS = 6002;
NODE_UNREACHABLE = 6003;
CLUSTER = 6010;
// Memory (7000–7099)
MEMORY_EXHAUSTED = 7000;
// Encryption (8000–8099)
ENCRYPTION = 8000;
// Internal (9000–9099)
INTERNAL = 9000;
BRIDGE = 9001;
DISPATCH = 9002;
}
impl fmt::Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "NDB-{:04}", self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_code_display() {
assert_eq!(ErrorCode::CONSTRAINT_VIOLATION.to_string(), "NDB-1000");
assert_eq!(ErrorCode::INTERNAL.to_string(), "NDB-9000");
assert_eq!(ErrorCode::WAL.to_string(), "NDB-4100");
}
#[test]
fn every_code_has_a_distinct_value() {
let mut seen = std::collections::HashSet::new();
for code in ErrorCode::ALL {
assert!(seen.insert(code.0), "{code} is defined twice");
}
assert!(ErrorCode::ALL.contains(&ErrorCode::DATABASE_QUOTA_EXCEEDED));
assert!(ErrorCode::ALL.contains(&ErrorCode::DISPATCH));
}
}