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 SqlParse(#[from] laminar_sql::parser::ParseError),
78
79 Shutdown,
81
82 Checkpoint(String),
84
85 CheckpointStore(#[from] laminar_core::storage::checkpoint_store::CheckpointStoreError),
87
88 UnresolvedConfigVar(String),
90
91 Connector(String),
93
94 ConnectorOp(#[from] laminar_connectors::error::ConnectorError),
96
97 Pipeline(String),
99
100 BackpressureFail(String),
102
103 ShuffleNotReady(String),
106
107 ShuffleTerminal(String),
110
111 ShufflePartialSend(String),
116
117 StatefulOperatorPartialApply(String),
121
122 QueryPipeline {
125 context: String,
127 translated: String,
130 },
131
132 MaterializedView(String),
134
135 Storage(String),
137
138 Config(String),
140
141 Unsupported(String),
143}
144
145impl DbError {
146 pub fn query_pipeline(
151 context: impl Into<String>,
152 df_error: &datafusion_common::DataFusionError,
153 ) -> Self {
154 let translated = laminar_sql::error::translate_datafusion_error(&df_error.to_string());
155 Self::QueryPipeline {
156 context: context.into(),
157 translated: translated.to_string(),
158 }
159 }
160
161 pub fn query_pipeline_with_columns(
164 context: impl Into<String>,
165 df_error: &datafusion_common::DataFusionError,
166 available_columns: &[&str],
167 ) -> Self {
168 let translated = laminar_sql::error::translate_datafusion_error_with_context(
169 &df_error.to_string(),
170 Some(available_columns),
171 );
172 Self::QueryPipeline {
173 context: context.into(),
174 translated: translated.to_string(),
175 }
176 }
177
178 pub fn query_pipeline_arrow(
180 context: impl Into<String>,
181 arrow_error: &arrow::error::ArrowError,
182 ) -> Self {
183 let translated = laminar_sql::error::translate_datafusion_error(&arrow_error.to_string());
184 Self::QueryPipeline {
185 context: context.into(),
186 translated: translated.to_string(),
187 }
188 }
189
190 #[must_use]
195 pub fn code(&self) -> &'static str {
196 match self {
197 Self::Sql(_) | Self::SqlParse(_) => error_codes::SQL_UNSUPPORTED,
198 Self::Engine(_) | Self::Streaming(_) => error_codes::INTERNAL,
199 Self::DataFusion(_) => error_codes::QUERY_EXECUTION_FAILED,
200 Self::SourceNotFound(_) => error_codes::SOURCE_NOT_FOUND,
201 Self::SinkNotFound(_) => error_codes::SINK_NOT_FOUND,
202 Self::QueryNotFound(_) | Self::StreamNotFound(_) | Self::TableNotFound(_) => {
203 error_codes::SQL_TABLE_NOT_FOUND
204 }
205 Self::SourceAlreadyExists(_)
206 | Self::StreamAlreadyExists(_)
207 | Self::TableAlreadyExists(_) => error_codes::SOURCE_ALREADY_EXISTS,
208 Self::SinkAlreadyExists(_) => error_codes::SINK_ALREADY_EXISTS,
209 Self::InsertError(_) => error_codes::CONNECTOR_WRITE_ERROR,
210 Self::SchemaMismatch(_) => error_codes::SCHEMA_MISMATCH,
211 Self::InvalidOperation(_)
212 | Self::SubscriptionReplayPruned { .. }
213 | Self::SubscriptionEpochNotCommitted { .. }
214 | Self::Unsupported(_) => error_codes::INVALID_OPERATION,
215 Self::Shutdown => error_codes::SHUTDOWN,
216 Self::Checkpoint(_) | Self::CheckpointStore(_) => error_codes::CHECKPOINT_FAILED,
217 Self::UnresolvedConfigVar(_) => error_codes::UNRESOLVED_CONFIG_VAR,
218 Self::Connector(_) | Self::ConnectorOp(_) => error_codes::CONNECTOR_CONNECTION_FAILED,
219 Self::Pipeline(_)
220 | Self::BackpressureFail(_)
221 | Self::ShuffleNotReady(_)
222 | Self::ShuffleTerminal(_)
223 | Self::ShufflePartialSend(_)
224 | Self::StatefulOperatorPartialApply(_) => error_codes::PIPELINE_ERROR,
225 Self::QueryPipeline { .. } => error_codes::QUERY_PIPELINE_ERROR,
226 Self::MaterializedView(_) => error_codes::MATERIALIZED_VIEW_ERROR,
227 Self::Storage(_) => error_codes::WAL_ERROR,
228 Self::Config(_) => error_codes::INVALID_CONFIG,
229 }
230 }
231
232 #[must_use]
234 pub fn is_shuffle_not_ready(&self) -> bool {
235 matches!(self, Self::ShuffleNotReady(_))
236 }
237
238 #[must_use]
240 pub fn requires_pipeline_halt(&self) -> bool {
241 matches!(self, Self::BackpressureFail(_) | Self::ShuffleTerminal(_))
242 }
243
244 #[must_use]
246 pub fn requires_pipeline_recovery(&self) -> bool {
247 matches!(
248 self,
249 Self::Checkpoint(_)
250 | Self::ShufflePartialSend(_)
251 | Self::StatefulOperatorPartialApply(_)
252 )
253 }
254
255 #[must_use]
257 pub fn is_transient(&self) -> bool {
258 match self {
259 Self::Streaming(_)
260 | Self::Connector(_)
261 | Self::Checkpoint(_)
262 | Self::CheckpointStore(_) => true,
263 Self::ConnectorOp(e) => e.is_transient(),
264 _ => false,
265 }
266 }
267}
268
269impl std::fmt::Display for DbError {
270 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
271 match self {
272 Self::Sql(e) => write!(f, "SQL error: {e}"),
273 Self::Engine(e) => write!(f, "Engine error: {e}"),
274 Self::Streaming(e) => write!(f, "Streaming error: {e}"),
275 Self::DataFusion(e) => {
276 let translated = laminar_sql::error::translate_datafusion_error(&e.to_string());
277 write!(f, "{translated}")
278 }
279 Self::SourceNotFound(name) => {
280 write!(f, "[{}] Source '{name}' not found", self.code())
281 }
282 Self::SinkNotFound(name) => {
283 write!(f, "[{}] Sink '{name}' not found", self.code())
284 }
285 Self::QueryNotFound(name) => {
286 write!(f, "[{}] Query '{name}' not found", self.code())
287 }
288 Self::SourceAlreadyExists(name) => {
289 write!(f, "[{}] Source '{name}' already exists", self.code())
290 }
291 Self::SinkAlreadyExists(name) => {
292 write!(f, "[{}] Sink '{name}' already exists", self.code())
293 }
294 Self::StreamNotFound(name) => {
295 write!(f, "[{}] Stream '{name}' not found", self.code())
296 }
297 Self::StreamAlreadyExists(name) => {
298 write!(f, "[{}] Stream '{name}' already exists", self.code())
299 }
300 Self::TableNotFound(name) => {
301 write!(f, "[{}] Table '{name}' not found", self.code())
302 }
303 Self::TableAlreadyExists(name) => {
304 write!(f, "[{}] Table '{name}' already exists", self.code())
305 }
306 Self::InsertError(msg) => {
307 write!(f, "[{}] Insert error: {msg}", self.code())
308 }
309 Self::SchemaMismatch(msg) => {
310 write!(f, "[{}] Schema mismatch: {msg}", self.code())
311 }
312 Self::InvalidOperation(msg) => {
313 write!(f, "[{}] Invalid operation: {msg}", self.code())
314 }
315 Self::SubscriptionReplayPruned {
316 name,
317 requested,
318 earliest_retained,
319 } => write!(
320 f,
321 "[{}] Epoch {requested} for stream '{name}' is no longer retained (earliest retained is {earliest_retained})",
322 self.code()
323 ),
324 Self::SubscriptionEpochNotCommitted {
325 name,
326 requested,
327 latest_committed,
328 } => match latest_committed {
329 Some(latest) => write!(
330 f,
331 "[{}] Epoch {requested} for stream '{name}' is not committed (latest committed is {latest})",
332 self.code()
333 ),
334 None => write!(
335 f,
336 "[{}] Epoch {requested} for stream '{name}' is not committed (no committed epoch is available)",
337 self.code()
338 ),
339 },
340 Self::SqlParse(e) => write!(f, "SQL parse error: {e}"),
341 Self::Shutdown => {
342 write!(f, "[{}] Database is shut down", self.code())
343 }
344 Self::Checkpoint(msg) => {
345 write!(f, "[{}] Checkpoint error: {msg}", self.code())
346 }
347 Self::CheckpointStore(e) => {
348 write!(f, "[{}] Checkpoint store error: {e}", self.code())
349 }
350 Self::UnresolvedConfigVar(msg) => {
351 write!(f, "[{}] Unresolved config variable: {msg}", self.code())
352 }
353 Self::Connector(msg) => {
354 write!(f, "[{}] Connector error: {msg}", self.code())
355 }
356 Self::ConnectorOp(e) => {
357 write!(f, "[{}] Connector error: {e}", self.code())
358 }
359 Self::Pipeline(msg) => {
360 write!(f, "[{}] Pipeline error: {msg}", self.code())
361 }
362 Self::BackpressureFail(msg) => {
363 write!(f, "[{}] Backpressure fail: {msg}", self.code())
364 }
365 Self::ShuffleNotReady(msg) => {
366 write!(f, "[{}] Shuffle target not ready: {msg}", self.code())
367 }
368 Self::ShuffleTerminal(msg) => {
369 write!(f, "[{}] Terminal shuffle routing error: {msg}", self.code())
370 }
371 Self::ShufflePartialSend(msg) => {
372 write!(f, "[{}] Shuffle partial send: {msg}", self.code())
373 }
374 Self::StatefulOperatorPartialApply(msg) => {
375 write!(
376 f,
377 "[{}] Stateful operator partial apply: {msg}",
378 self.code()
379 )
380 }
381 Self::QueryPipeline {
382 context,
383 translated,
384 } => write!(f, "Stream '{context}': {translated}"),
385 Self::MaterializedView(msg) => {
386 write!(f, "[{}] Materialized view error: {msg}", self.code())
387 }
388 Self::Storage(msg) => {
389 write!(f, "[{}] Storage error: {msg}", self.code())
390 }
391 Self::Config(msg) => {
392 write!(f, "[{}] Config error: {msg}", self.code())
393 }
394 Self::Unsupported(msg) => {
395 write!(f, "[{}] Unsupported: {msg}", self.code())
396 }
397 }
398 }
399}