1use laminar_core::error_codes;
4
5#[derive(Debug, thiserror::Error)]
7pub enum DbError {
8 Sql(#[from] laminar_sql::Error),
10
11 Engine(#[from] laminar_core::Error),
13
14 Streaming(#[from] laminar_core::streaming::StreamingError),
16
17 DataFusion(#[from] datafusion_common::DataFusionError),
19
20 SourceNotFound(String),
22
23 SinkNotFound(String),
25
26 QueryNotFound(String),
28
29 SourceAlreadyExists(String),
31
32 SinkAlreadyExists(String),
34
35 StreamNotFound(String),
37
38 StreamAlreadyExists(String),
40
41 TableNotFound(String),
43
44 TableAlreadyExists(String),
46
47 InsertError(String),
49
50 SchemaMismatch(String),
52
53 InvalidOperation(String),
55
56 SubscriptionReplayPruned {
58 name: String,
60 requested: u64,
62 earliest_retained: u64,
64 },
65
66 SubscriptionEpochNotCommitted {
68 name: String,
70 requested: u64,
72 latest_committed: Option<u64>,
74 },
75
76 Subscription(#[from] crate::subscription::ClusterSubscriptionError),
78
79 SqlParse(#[from] laminar_sql::parser::ParseError),
81
82 Shutdown,
84
85 Checkpoint(String),
87
88 CheckpointStore(#[from] laminar_core::checkpoint::checkpoint_store::CheckpointStoreError),
90
91 UnresolvedConfigVar(String),
93
94 Connector(String),
96
97 ConnectorOp(#[from] laminar_connectors::error::ConnectorError),
99
100 Pipeline(String),
102
103 PipelineTerminal(String),
105
106 BackpressureFail(String),
108
109 ShuffleNotReady(String),
112
113 ShuffleTerminal(String),
116
117 ShufflePartialSend(String),
122
123 StatefulOperatorPartialApply(String),
128
129 ManagedStateBudgetExceeded {
133 context: String,
135 accounted_bytes: usize,
137 limit_bytes: usize,
139 },
140
141 QueryPipeline {
144 context: String,
146 translated: String,
149 },
150
151 MaterializedView(String),
153
154 Storage(String),
156
157 Config(String),
159
160 Unsupported(String),
162}
163
164impl DbError {
165 pub fn query_pipeline(
170 context: impl Into<String>,
171 df_error: &datafusion_common::DataFusionError,
172 ) -> Self {
173 let translated = laminar_sql::error::translate_datafusion_error(&df_error.to_string());
174 Self::QueryPipeline {
175 context: context.into(),
176 translated: translated.to_string(),
177 }
178 }
179
180 pub fn query_pipeline_with_columns(
183 context: impl Into<String>,
184 df_error: &datafusion_common::DataFusionError,
185 available_columns: &[&str],
186 ) -> Self {
187 let translated = laminar_sql::error::translate_datafusion_error_with_context(
188 &df_error.to_string(),
189 Some(available_columns),
190 );
191 Self::QueryPipeline {
192 context: context.into(),
193 translated: translated.to_string(),
194 }
195 }
196
197 pub fn query_pipeline_arrow(
199 context: impl Into<String>,
200 arrow_error: &arrow::error::ArrowError,
201 ) -> Self {
202 let translated = laminar_sql::error::translate_datafusion_error(&arrow_error.to_string());
203 Self::QueryPipeline {
204 context: context.into(),
205 translated: translated.to_string(),
206 }
207 }
208
209 #[must_use]
214 pub fn code(&self) -> &'static str {
215 match self {
216 Self::Sql(_) | Self::SqlParse(_) => error_codes::SQL_UNSUPPORTED,
217 Self::Engine(_) | Self::Streaming(_) => error_codes::INTERNAL,
218 Self::DataFusion(_) => error_codes::QUERY_EXECUTION_FAILED,
219 Self::SourceNotFound(_) => error_codes::SOURCE_NOT_FOUND,
220 Self::SinkNotFound(_) => error_codes::SINK_NOT_FOUND,
221 Self::QueryNotFound(_) | Self::StreamNotFound(_) | Self::TableNotFound(_) => {
222 error_codes::SQL_TABLE_NOT_FOUND
223 }
224 Self::SourceAlreadyExists(_)
225 | Self::StreamAlreadyExists(_)
226 | Self::TableAlreadyExists(_) => error_codes::SOURCE_ALREADY_EXISTS,
227 Self::SinkAlreadyExists(_) => error_codes::SINK_ALREADY_EXISTS,
228 Self::InsertError(_) => error_codes::CONNECTOR_WRITE_ERROR,
229 Self::SchemaMismatch(_) => error_codes::SCHEMA_MISMATCH,
230 Self::InvalidOperation(_)
231 | Self::SubscriptionReplayPruned { .. }
232 | Self::SubscriptionEpochNotCommitted { .. }
233 | Self::Unsupported(_) => error_codes::INVALID_OPERATION,
234 Self::Subscription(error) => error.code(),
235 Self::Shutdown => error_codes::SHUTDOWN,
236 Self::Checkpoint(_) | Self::CheckpointStore(_) => error_codes::CHECKPOINT_FAILED,
237 Self::UnresolvedConfigVar(_) => error_codes::UNRESOLVED_CONFIG_VAR,
238 Self::Connector(_) | Self::ConnectorOp(_) => error_codes::CONNECTOR_CONNECTION_FAILED,
239 Self::Pipeline(_)
240 | Self::PipelineTerminal(_)
241 | Self::BackpressureFail(_)
242 | Self::ShuffleNotReady(_)
243 | Self::ShuffleTerminal(_)
244 | Self::ShufflePartialSend(_)
245 | Self::StatefulOperatorPartialApply(_) => error_codes::PIPELINE_ERROR,
246 Self::ManagedStateBudgetExceeded { .. } => error_codes::MANAGED_STATE_BUDGET_EXCEEDED,
247 Self::QueryPipeline { .. } => error_codes::QUERY_PIPELINE_ERROR,
248 Self::MaterializedView(_) => error_codes::MATERIALIZED_VIEW_ERROR,
249 Self::Storage(_) => error_codes::WAL_ERROR,
250 Self::Config(_) => error_codes::INVALID_CONFIG,
251 }
252 }
253
254 #[must_use]
256 pub fn is_shuffle_not_ready(&self) -> bool {
257 matches!(self, Self::ShuffleNotReady(_))
258 }
259
260 #[must_use]
262 pub fn requires_pipeline_halt(&self) -> bool {
263 matches!(
264 self,
265 Self::PipelineTerminal(_)
266 | Self::BackpressureFail(_)
267 | Self::ShuffleTerminal(_)
268 | Self::ManagedStateBudgetExceeded { .. }
269 )
270 }
271
272 #[must_use]
274 pub fn requires_pipeline_recovery(&self) -> bool {
275 matches!(
276 self,
277 Self::Checkpoint(_)
278 | Self::ShufflePartialSend(_)
279 | Self::StatefulOperatorPartialApply(_)
280 )
281 }
282
283 #[must_use]
285 pub fn is_transient(&self) -> bool {
286 match self {
287 Self::Streaming(_)
288 | Self::Connector(_)
289 | Self::Checkpoint(_)
290 | Self::CheckpointStore(_) => true,
291 Self::ConnectorOp(e) => e.is_transient(),
292 _ => false,
293 }
294 }
295}
296
297impl std::fmt::Display for DbError {
298 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
299 match self {
300 Self::Sql(e) => write!(f, "SQL error: {e}"),
301 Self::Engine(e) => write!(f, "Engine error: {e}"),
302 Self::Streaming(e) => write!(f, "Streaming error: {e}"),
303 Self::DataFusion(e) => {
304 let translated = laminar_sql::error::translate_datafusion_error(&e.to_string());
305 write!(f, "{translated}")
306 }
307 Self::SourceNotFound(name) => {
308 write!(f, "[{}] Source '{name}' not found", self.code())
309 }
310 Self::SinkNotFound(name) => {
311 write!(f, "[{}] Sink '{name}' not found", self.code())
312 }
313 Self::QueryNotFound(name) => {
314 write!(f, "[{}] Query '{name}' not found", self.code())
315 }
316 Self::SourceAlreadyExists(name) => {
317 write!(f, "[{}] Source '{name}' already exists", self.code())
318 }
319 Self::SinkAlreadyExists(name) => {
320 write!(f, "[{}] Sink '{name}' already exists", self.code())
321 }
322 Self::StreamNotFound(name) => {
323 write!(f, "[{}] Stream '{name}' not found", self.code())
324 }
325 Self::StreamAlreadyExists(name) => {
326 write!(f, "[{}] Stream '{name}' already exists", self.code())
327 }
328 Self::TableNotFound(name) => {
329 write!(f, "[{}] Table '{name}' not found", self.code())
330 }
331 Self::TableAlreadyExists(name) => {
332 write!(f, "[{}] Table '{name}' already exists", self.code())
333 }
334 Self::InsertError(msg) => {
335 write!(f, "[{}] Insert error: {msg}", self.code())
336 }
337 Self::SchemaMismatch(msg) => {
338 write!(f, "[{}] Schema mismatch: {msg}", self.code())
339 }
340 Self::InvalidOperation(msg) => {
341 write!(f, "[{}] Invalid operation: {msg}", self.code())
342 }
343 Self::SubscriptionReplayPruned {
344 name,
345 requested,
346 earliest_retained,
347 } => write!(
348 f,
349 "[{}] Epoch {requested} for stream '{name}' is no longer retained (earliest retained is {earliest_retained})",
350 self.code()
351 ),
352 Self::SubscriptionEpochNotCommitted {
353 name,
354 requested,
355 latest_committed,
356 } => match latest_committed {
357 Some(latest) => write!(
358 f,
359 "[{}] Epoch {requested} for stream '{name}' is not committed (latest committed is {latest})",
360 self.code()
361 ),
362 None => write!(
363 f,
364 "[{}] Epoch {requested} for stream '{name}' is not committed (no committed epoch is available)",
365 self.code()
366 ),
367 },
368 Self::Subscription(error) => write!(f, "[{}] {error}", self.code()),
369 Self::SqlParse(e) => write!(f, "SQL parse error: {e}"),
370 Self::Shutdown => write!(f, "[{}] Database is shut down", self.code()),
371 Self::Checkpoint(msg) => {
372 write!(f, "[{}] Checkpoint error: {msg}", self.code())
373 }
374 Self::CheckpointStore(e) => {
375 write!(f, "[{}] Checkpoint store error: {e}", self.code())
376 }
377 Self::UnresolvedConfigVar(msg) => {
378 write!(f, "[{}] Unresolved config variable: {msg}", self.code())
379 }
380 Self::Connector(msg) => {
381 write!(f, "[{}] Connector error: {msg}", self.code())
382 }
383 Self::ConnectorOp(e) => {
384 write!(f, "[{}] Connector error: {e}", self.code())
385 }
386 Self::Pipeline(msg) => {
387 write!(f, "[{}] Pipeline error: {msg}", self.code())
388 }
389 Self::PipelineTerminal(msg) => {
390 write!(f, "[{}] Terminal pipeline error: {msg}", self.code())
391 }
392 Self::BackpressureFail(msg) => {
393 write!(f, "[{}] Backpressure fail: {msg}", self.code())
394 }
395 Self::ShuffleNotReady(msg) => {
396 write!(f, "[{}] Shuffle target not ready: {msg}", self.code())
397 }
398 Self::ShuffleTerminal(msg) => {
399 write!(f, "[{}] Terminal shuffle routing error: {msg}", self.code())
400 }
401 Self::ShufflePartialSend(msg) => {
402 write!(f, "[{}] Shuffle partial send: {msg}", self.code())
403 }
404 Self::StatefulOperatorPartialApply(msg) => {
405 write!(
406 f,
407 "[{}] Stateful operator partial apply: {msg}",
408 self.code()
409 )
410 }
411 Self::ManagedStateBudgetExceeded {
412 context,
413 accounted_bytes,
414 limit_bytes,
415 } => write!(
416 f,
417 "[{}] Managed state budget exceeded during {context}: accounted={accounted_bytes} bytes, limit={limit_bytes} bytes",
418 self.code()
419 ),
420 Self::QueryPipeline {
421 context,
422 translated,
423 } => write!(f, "Stream '{context}': {translated}"),
424 Self::MaterializedView(msg) => {
425 write!(f, "[{}] Materialized view error: {msg}", self.code())
426 }
427 Self::Storage(msg) => {
428 write!(f, "[{}] Storage error: {msg}", self.code())
429 }
430 Self::Config(msg) => {
431 write!(f, "[{}] Config error: {msg}", self.code())
432 }
433 Self::Unsupported(msg) => {
434 write!(f, "[{}] Unsupported: {msg}", self.code())
435 }
436 }
437 }
438}