Skip to main content

laminar_core/
error_codes.rs

1//! LaminarDB structured error code registry.
2//!
3//! Every error in LaminarDB carries a stable `LDB-NNNN` code that is:
4//! - Present in the error message (grep-able in logs)
5//! - Present in the source code (grep-able in code)
6//! - Stable across versions (codes are never reused)
7//!
8//! # Code Ranges
9//!
10//! | Range | Category |
11//! |-------|----------|
12//! | `LDB-0xxx` | General / configuration |
13//! | `LDB-1xxx` | SQL parsing & validation |
14//! | `LDB-2xxx` | Window / watermark operations |
15//! | `LDB-3xxx` | Join operations |
16//! | `LDB-4xxx` | Serialization / state |
17//! | `LDB-5xxx` | Connector / I/O |
18//! | `LDB-6xxx` | Checkpoint / recovery |
19//! | `LDB-7xxx` | DataFusion / Arrow interop |
20//! | `LDB-8xxx` | Internal / should-not-happen |
21//!
22//! This module is the canonical registry for all error code ranges.
23//! Downstream crates (`laminar-sql`, `laminar-db`, etc.) re-export from here.
24
25// ── General / Configuration (LDB-0xxx) ──
26
27/// Invalid configuration value.
28pub const INVALID_CONFIG: &str = "LDB-0001";
29/// Missing required configuration key.
30pub const MISSING_CONFIG: &str = "LDB-0002";
31/// Unresolved config variable (e.g. `${VAR}` placeholder).
32pub const UNRESOLVED_CONFIG_VAR: &str = "LDB-0003";
33/// Database is shut down.
34pub const SHUTDOWN: &str = "LDB-0004";
35/// Invalid operation for the current state.
36pub const INVALID_OPERATION: &str = "LDB-0005";
37/// Schema mismatch between Rust type and SQL definition.
38pub const SCHEMA_MISMATCH: &str = "LDB-0006";
39
40// ── SQL Parsing & Validation (LDB-1xxx) ──
41
42/// Unsupported SQL syntax.
43pub const SQL_UNSUPPORTED: &str = "LDB-1001";
44/// Query planning failed.
45pub const SQL_PLANNING_FAILED: &str = "LDB-1002";
46/// Column not found.
47pub const SQL_COLUMN_NOT_FOUND: &str = "LDB-1100";
48/// Table or source not found.
49pub const SQL_TABLE_NOT_FOUND: &str = "LDB-1101";
50/// Type mismatch.
51pub const SQL_TYPE_MISMATCH: &str = "LDB-1200";
52
53// ── Window / Watermark (LDB-2xxx) ──
54
55/// Watermark required for this operation.
56pub const WATERMARK_REQUIRED: &str = "LDB-2001";
57/// Invalid window specification.
58pub const WINDOW_INVALID: &str = "LDB-2002";
59/// Window size must be positive.
60pub const WINDOW_SIZE_INVALID: &str = "LDB-2003";
61/// Late data rejected by window policy.
62pub const LATE_DATA_REJECTED: &str = "LDB-2004";
63
64// ── Join (LDB-3xxx) ──
65
66/// Join key column not found or invalid.
67pub const JOIN_KEY_MISSING: &str = "LDB-3001";
68/// Time bound required for stream-stream join.
69pub const JOIN_TIME_BOUND_MISSING: &str = "LDB-3002";
70/// Temporal join requires a primary key on the right-side table.
71pub const TEMPORAL_JOIN_NO_PK: &str = "LDB-3003";
72/// Unsupported join type for streaming queries.
73pub const JOIN_TYPE_UNSUPPORTED: &str = "LDB-3004";
74
75// ── Serialization / State (LDB-4xxx) ──
76
77/// State serialization failed for an operator.
78pub const SERIALIZATION_FAILED: &str = "LDB-4001";
79/// State deserialization failed for an operator.
80pub const DESERIALIZATION_FAILED: &str = "LDB-4002";
81/// JSON parse error (connector config, CDC payload, etc.).
82pub const JSON_PARSE_ERROR: &str = "LDB-4003";
83/// Base64 decode error (inline checkpoint state).
84pub const BASE64_DECODE_ERROR: &str = "LDB-4004";
85/// State store key not found.
86pub const STATE_KEY_MISSING: &str = "LDB-4005";
87/// State corruption detected (checksum mismatch, invalid data).
88pub const STATE_CORRUPTION: &str = "LDB-4006";
89
90// ── Connector / I/O (LDB-5xxx) ──
91
92/// Connector failed to establish a connection.
93pub const CONNECTOR_CONNECTION_FAILED: &str = "LDB-5001";
94/// Connector authentication failed.
95pub const CONNECTOR_AUTH_FAILED: &str = "LDB-5002";
96/// Connector read error.
97pub const CONNECTOR_READ_ERROR: &str = "LDB-5003";
98/// Connector write error.
99pub const CONNECTOR_WRITE_ERROR: &str = "LDB-5004";
100/// Connector configuration error.
101pub const CONNECTOR_CONFIG_ERROR: &str = "LDB-5005";
102/// Source not found.
103pub const SOURCE_NOT_FOUND: &str = "LDB-5010";
104/// Sink not found.
105pub const SINK_NOT_FOUND: &str = "LDB-5011";
106/// Source already exists.
107pub const SOURCE_ALREADY_EXISTS: &str = "LDB-5012";
108/// Sink already exists.
109pub const SINK_ALREADY_EXISTS: &str = "LDB-5013";
110/// Connector serde (serialization/deserialization) error.
111pub const CONNECTOR_SERDE_ERROR: &str = "LDB-5020";
112/// Schema inference or compatibility error.
113pub const CONNECTOR_SCHEMA_ERROR: &str = "LDB-5021";
114/// Exactly-once requires all sources to support replay.
115pub const EXACTLY_ONCE_NON_REPLAYABLE: &str = "LDB-5030";
116/// Exactly-once requires all sinks to support exactly-once semantics.
117pub const EXACTLY_ONCE_SINK_UNSUPPORTED: &str = "LDB-5031";
118/// Exactly-once requires checkpointing to be enabled.
119pub const EXACTLY_ONCE_NO_CHECKPOINT: &str = "LDB-5032";
120/// Mixed delivery capabilities — some sources are non-replayable.
121pub const MIXED_DELIVERY_CAPABILITIES: &str = "LDB-5033";
122
123// ── Checkpoint / Recovery (LDB-6xxx) ──
124
125/// Checkpoint creation failed.
126pub const CHECKPOINT_FAILED: &str = "LDB-6001";
127/// Checkpoint not found.
128pub const CHECKPOINT_NOT_FOUND: &str = "LDB-6002";
129/// Checkpoint recovery failed.
130pub const RECOVERY_FAILED: &str = "LDB-6003";
131/// Sink rollback failed during checkpoint abort.
132pub const SINK_ROLLBACK_FAILED: &str = "LDB-6004";
133/// WAL (write-ahead log) error.
134pub const WAL_ERROR: &str = "LDB-6005";
135/// WAL entry has invalid length (possible corruption).
136pub const WAL_INVALID_LENGTH: &str = "LDB-6006";
137/// WAL checksum mismatch.
138pub const WAL_CHECKSUM_MISMATCH: &str = "LDB-6007";
139/// Checkpoint manifest persistence failed.
140pub const MANIFEST_PERSIST_FAILED: &str = "LDB-6008";
141/// Checkpoint prune (old checkpoint cleanup) failed.
142pub const CHECKPOINT_PRUNE_FAILED: &str = "LDB-6009";
143/// Sidecar state data missing or corrupted.
144pub const SIDECAR_CORRUPTION: &str = "LDB-6010";
145/// Source offset metadata missing during recovery.
146pub const OFFSET_METADATA_MISSING: &str = "LDB-6011";
147/// State durability gate returned false before sink commit. One or more
148/// vnodes had not persisted their partials for the epoch. The coordinator
149/// rolls back sinks and retries on the next checkpoint.
150pub const DURABILITY_GATE_MISS: &str = "LDB-6020";
151/// Sink rollback failed after a durability-gate miss. Sinks may be in an
152/// inconsistent state; recovery uses `sink_commit_statuses` to resolve.
153pub const DURABILITY_GATE_ROLLBACK_FAILED: &str = "LDB-6021";
154/// State backend returned an error during the durability gate (backend
155/// unreachable, permission denied). Treated as a gate miss; sinks rolled
156/// back and the next checkpoint retries.
157pub const DURABILITY_GATE_BACKEND_ERROR: &str = "LDB-6022";
158/// Sink rollback failed after a durability-gate backend error.
159pub const DURABILITY_GATE_ROLLBACK_ON_ERROR_FAILED: &str = "LDB-6023";
160
161// ── DataFusion / Arrow Interop (LDB-7xxx) ──
162
163/// Query execution failed (`DataFusion` engine error).
164/// Note: the SQL layer uses `LDB-9001` for execution failures visible to users;
165/// `LDB-7001` is for internal `DataFusion` interop issues.
166pub const QUERY_EXECUTION_FAILED: &str = "LDB-7001";
167/// Arrow schema or record batch error.
168pub const ARROW_ERROR: &str = "LDB-7002";
169/// `DataFusion` plan optimization failed.
170pub const PLAN_OPTIMIZATION_FAILED: &str = "LDB-7003";
171
172// ── Internal / Should-Not-Happen (LDB-8xxx) ──
173
174/// Internal error — this is a bug.
175pub const INTERNAL: &str = "LDB-8001";
176/// Pipeline error (start/shutdown lifecycle).
177pub const PIPELINE_ERROR: &str = "LDB-8002";
178/// Materialized view error.
179pub const MATERIALIZED_VIEW_ERROR: &str = "LDB-8003";
180/// Query pipeline error (stream execution context).
181pub const QUERY_PIPELINE_ERROR: &str = "LDB-8004";
182/// No compiled projection or cached plan for pre-aggregation query.
183pub const NO_COMPILED_PROJECTION: &str = "LDB-8050";
184
185// ── Ring 0 Hot Path Errors ──
186
187/// Ring 0 error — no heap allocation, no formatting on construction.
188///
189/// Only formatted when actually displayed (which happens outside Ring 0).
190/// Uses `Copy` and `repr(u16)` for zero-cost error reporting via counters.
191#[derive(Debug, Clone, Copy, PartialEq, Eq)]
192#[repr(u16)]
193pub enum HotPathError {
194    /// Event arrived after watermark — dropped.
195    LateEvent = 0x0001,
196    /// State store key not found.
197    StateKeyMissing = 0x0002,
198    /// Downstream backpressure — event buffered.
199    Backpressure = 0x0003,
200    /// Serialization buffer overflow.
201    SerializationOverflow = 0x0004,
202    /// Record batch schema does not match expected.
203    SchemaMismatch = 0x0005,
204    /// Aggregate state corruption detected.
205    AggregateStateCorruption = 0x0006,
206    /// Queue is full — cannot push event.
207    QueueFull = 0x0007,
208    /// Channel is closed/disconnected.
209    ChannelClosed = 0x0008,
210}
211
212impl HotPathError {
213    /// Returns a static error message. Cost: one match. No allocation.
214    #[must_use]
215    pub const fn message(self) -> &'static str {
216        match self {
217            Self::LateEvent => "Event arrived after watermark; dropped",
218            Self::StateKeyMissing => "State key not found in store",
219            Self::Backpressure => "Downstream backpressure; event buffered",
220            Self::SerializationOverflow => "Serialization buffer capacity exceeded",
221            Self::SchemaMismatch => "Record batch schema does not match expected",
222            Self::AggregateStateCorruption => "Aggregate state checksum mismatch detected",
223            Self::QueueFull => "Queue is full; cannot push event",
224            Self::ChannelClosed => "Channel is closed or disconnected",
225        }
226    }
227
228    /// Numeric code for metrics counters. Zero-cost.
229    #[must_use]
230    pub const fn code(self) -> u16 {
231        self as u16
232    }
233
234    /// Returns the `LDB-NNNN` error code string for this hot path error.
235    #[must_use]
236    pub const fn ldb_code(self) -> &'static str {
237        match self {
238            Self::LateEvent => LATE_DATA_REJECTED,
239            Self::StateKeyMissing => STATE_KEY_MISSING,
240            Self::Backpressure => "LDB-8010",
241            Self::SerializationOverflow => SERIALIZATION_FAILED,
242            Self::SchemaMismatch => SCHEMA_MISMATCH,
243            Self::AggregateStateCorruption => STATE_CORRUPTION,
244            Self::QueueFull => "LDB-8011",
245            Self::ChannelClosed => "LDB-8012",
246        }
247    }
248}
249
250impl std::fmt::Display for HotPathError {
251    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252        write!(f, "[{}] {}", self.ldb_code(), self.message())
253    }
254}
255
256impl std::error::Error for HotPathError {}
257
258#[cfg(test)]
259mod tests {
260    use super::*;
261
262    #[test]
263    fn hot_path_error_is_copy_and_small() {
264        let e = HotPathError::LateEvent;
265        let e2 = e; // Copy
266        assert_eq!(e, e2);
267        assert_eq!(std::mem::size_of::<HotPathError>(), 2);
268    }
269
270    #[test]
271    fn hot_path_error_codes_are_nonzero() {
272        let variants = [
273            HotPathError::LateEvent,
274            HotPathError::StateKeyMissing,
275            HotPathError::Backpressure,
276            HotPathError::SerializationOverflow,
277            HotPathError::SchemaMismatch,
278            HotPathError::AggregateStateCorruption,
279            HotPathError::QueueFull,
280            HotPathError::ChannelClosed,
281        ];
282        for v in &variants {
283            assert!(v.code() > 0, "{v:?} has zero code");
284            assert!(!v.message().is_empty(), "{v:?} has empty message");
285            assert!(
286                v.ldb_code().starts_with("LDB-"),
287                "{v:?} has bad ldb_code: {}",
288                v.ldb_code()
289            );
290        }
291    }
292
293    #[test]
294    fn hot_path_error_display() {
295        let e = HotPathError::LateEvent;
296        let s = e.to_string();
297        assert!(s.starts_with("[LDB-"));
298        assert!(s.contains("watermark"));
299    }
300
301    #[test]
302    fn error_codes_are_stable_strings() {
303        assert_eq!(INVALID_CONFIG, "LDB-0001");
304        assert_eq!(SERIALIZATION_FAILED, "LDB-4001");
305        assert_eq!(CHECKPOINT_FAILED, "LDB-6001");
306        assert_eq!(INTERNAL, "LDB-8001");
307    }
308}
309
310/// Severity level for warnings (schema inference, recovery, etc.).
311#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
312pub enum WarningSeverity {
313    /// Informational — operation succeeded but with caveats.
314    Info,
315    /// Warning — result may be inaccurate or degraded.
316    Warning,
317    /// Error — operation for this item failed; a fallback was used.
318    Error,
319}