Skip to main content

laminar_db/
error.rs

1//! Error types for the `LaminarDB` facade.
2
3use laminar_core::error_codes;
4
5/// Errors from database operations.
6#[derive(Debug, thiserror::Error)]
7pub enum DbError {
8    /// SQL parse error
9    Sql(#[from] laminar_sql::Error),
10
11    /// Core engine error
12    Engine(#[from] laminar_core::Error),
13
14    /// Streaming API error
15    Streaming(#[from] laminar_core::streaming::StreamingError),
16
17    /// `DataFusion` error (translated to user-friendly messages on display)
18    DataFusion(#[from] datafusion_common::DataFusionError),
19
20    /// Source not found
21    SourceNotFound(String),
22
23    /// Sink not found
24    SinkNotFound(String),
25
26    /// Query not found
27    QueryNotFound(String),
28
29    /// Source already exists
30    SourceAlreadyExists(String),
31
32    /// Sink already exists
33    SinkAlreadyExists(String),
34
35    /// Stream not found
36    StreamNotFound(String),
37
38    /// Stream already exists
39    StreamAlreadyExists(String),
40
41    /// Table not found
42    TableNotFound(String),
43
44    /// Table already exists
45    TableAlreadyExists(String),
46
47    /// A local reference-table update or replacement exceeded its live quota before mutation.
48    ReferenceTableQuotaExceeded {
49        /// Table whose final state was rejected.
50        table: String,
51        /// Projected live row count.
52        rows: usize,
53        /// Projected retained-memory charge.
54        bytes: usize,
55        /// Configured per-table row limit.
56        max_rows: usize,
57        /// Configured per-table retained-memory limit.
58        max_bytes: usize,
59    },
60
61    /// Insert error
62    InsertError(String),
63
64    /// Schema mismatch between Rust type and SQL definition
65    SchemaMismatch(String),
66
67    /// Invalid SQL statement for the operation
68    InvalidOperation(String),
69
70    /// Requested subscription epoch has committed but its replay suffix was pruned.
71    SubscriptionReplayPruned {
72        /// Subscription object name.
73        name: String,
74        /// Checkpoint epoch requested by the subscriber.
75        requested: u64,
76        /// Earliest checkpoint epoch whose cut remains replayable.
77        earliest_retained: u64,
78    },
79
80    /// Requested subscription epoch has not committed.
81    SubscriptionEpochNotCommitted {
82        /// Subscription object name.
83        name: String,
84        /// Checkpoint epoch requested by the subscriber.
85        requested: u64,
86        /// Newest durably committed checkpoint epoch, if one exists.
87        latest_committed: Option<u64>,
88    },
89
90    /// Requested sequence resume needs entries that are no longer retained.
91    SubscriptionSequencePruned {
92        /// Subscription object name.
93        name: String,
94        /// Last sequence received by the subscriber.
95        requested_sequence: u64,
96        /// Earliest replayable sequence; `None` when no history is retained.
97        earliest_retained_sequence: Option<u64>,
98    },
99
100    /// Requested subscription sequence has not been published.
101    SubscriptionSequenceNotPublished {
102        /// Subscription object name.
103        name: String,
104        /// Sequence requested by the subscriber.
105        requested_sequence: u64,
106        /// Sequence that the next published entry will receive.
107        next_sequence: u64,
108    },
109
110    /// Structured committed cluster-subscription failure.
111    Subscription(#[from] crate::subscription::ClusterSubscriptionError),
112
113    /// Typed durable topology authority failure.
114    #[cfg(feature = "cluster")]
115    Topology(#[from] laminar_core::cluster::control::TopologyError),
116
117    /// SQL-generated request identity retained when submission has an uncertain outcome.
118    #[cfg(feature = "cluster")]
119    TopologySubmission {
120        /// UUID to use for definitive status reads and retries.
121        operation_id: laminar_core::cluster::control::TopologyOperationId,
122        /// Original typed failure, preserving its registry code and source chain.
123        source: Box<Self>,
124    },
125
126    /// SQL parse error (from streaming parser)
127    SqlParse(#[from] laminar_sql::parser::ParseError),
128
129    /// Database is shut down
130    Shutdown,
131
132    /// Checkpoint error
133    Checkpoint(String),
134
135    /// Checkpoint store error (preserves structured source error).
136    CheckpointStore(#[from] laminar_core::checkpoint::checkpoint_store::CheckpointStoreError),
137
138    /// Unresolved config variable
139    UnresolvedConfigVar(String),
140
141    /// Connector error
142    Connector(String),
143
144    /// Connector operation error (preserves structured source error).
145    ConnectorOp(#[from] laminar_connectors::error::ConnectorError),
146
147    /// Pipeline error (start/shutdown lifecycle)
148    Pipeline(String),
149
150    /// A deterministic record-path failure that retry or checkpoint recovery cannot repair.
151    PipelineTerminal(String),
152
153    /// `BackpressurePolicy::Fail` tripped; coordinator halts the pipeline.
154    BackpressureFail(String),
155
156    /// Prospective graph input exceeds a port's retained-byte or batch limit. Operator state
157    /// may already have changed, so the generation halts before output publication or retry.
158    GraphBufferBudgetExceeded {
159        /// Destination operator or source name.
160        node: String,
161        /// Destination input port.
162        port: u8,
163        /// Projected retained batch count, including the rejected input.
164        batches: usize,
165        /// Projected retained Arrow charge, including the rejected input.
166        bytes: usize,
167        /// Configured batch limit; zero disables the count limit.
168        max_batches: usize,
169        /// Configured byte limit; `None` disables the byte limit.
170        max_bytes: Option<usize>,
171    },
172
173    /// A cross-node shuffle target isn't reachable yet (cluster formation).
174    /// Recoverable — `OperatorGraph::execute_single_operator` defers on it.
175    ShuffleNotReady(String),
176
177    /// A permanent structural shuffle-routing failure. Retrying or restoring the same input cannot
178    /// repair it, so the pipeline must halt instead of entering a recovery loop.
179    ShuffleTerminal(String),
180
181    /// A cross-node shuffle send failed after an earlier frame was admitted to the transport and
182    /// may reach its peer. Unlike [`Self::ShuffleNotReady`], this must not be replayed locally: a
183    /// retry could double-fold the admitted rows. Recovery rewinds the complete failure domain to a
184    /// durable cut before replay.
185    ShufflePartialSend(String),
186
187    /// A cross-node shuffle send waited the configured stall timeout without progress, so its
188    /// peer's stream stopped draining. Retrying would wait again; recovery rewinds the failure
189    /// domain to a durable cut.
190    ShuffleSendStalled(String),
191
192    /// A stateful operator may have changed local state before the attempt failed. This denotes an
193    /// indeterminate apply outcome, not proof that exactly a prefix was applied. The graph must not
194    /// replay that input against the possibly changed state; recovery rewinds the failure domain to
195    /// its last durable cut.
196    StatefulOperatorPartialApply(String),
197
198    /// Managed working state crossed the configured pipeline-wide charged-byte envelope. The
199    /// current graph generation must halt: replaying the same input against the same limit would
200    /// loop, and a record-path detection may follow state mutation but always precedes output.
201    ManagedStateBudgetExceeded {
202        /// Boundary at which the excess was detected.
203        context: String,
204        /// Combined live, prepared, and retired charged bytes.
205        accounted_bytes: usize,
206        /// Configured pipeline-wide maximum.
207        limit_bytes: usize,
208    },
209
210    /// A keyed aggregate or window would hold more groups than its cardinality cap. It halts for
211    /// the same reason as [`Self::ManagedStateBudgetExceeded`]: replay refills the same groups.
212    GroupLimitExceeded {
213        /// Boundary at which the excess was detected.
214        context: String,
215        /// Projected group count.
216        groups: usize,
217        /// Group cap.
218        limit: usize,
219    },
220
221    /// Query pipeline error — wraps a `DataFusion` error with stream context.
222    /// Unlike `Pipeline`, this variant is translated to user-friendly messages.
223    QueryPipeline {
224        /// The stream or query name where the error occurred.
225        context: String,
226        /// The translated error message (already processed through
227        /// `translate_datafusion_error`).
228        translated: String,
229    },
230
231    /// Materialized view error
232    MaterializedView(String),
233
234    /// A read of materialized views at a committed checkpoint could not be served.
235    CommittedRead(crate::mv_store::CommittedReadError),
236
237    /// A local materialized-view update or restore exceeded its live or staging quota.
238    MaterializedViewQuotaExceeded {
239        /// View whose projected state was rejected.
240        view: String,
241        /// Projected live or staged row count (distinct rows for multisets).
242        rows: usize,
243        /// Projected retained-memory charge.
244        bytes: usize,
245        /// Effective per-view row limit for the rejected phase.
246        max_rows: usize,
247        /// Effective per-view byte limit for the rejected phase.
248        max_bytes: usize,
249    },
250
251    /// Storage backend error.
252    Storage(String),
253
254    /// Configuration / profile validation error
255    Config(String),
256
257    /// Operation is not yet implemented.
258    Unsupported(String),
259}
260
261impl DbError {
262    /// Create a `QueryPipeline` error from a `DataFusion` error with stream context.
263    ///
264    /// The `DataFusion` error is translated to a user-friendly message with
265    /// structured error codes. The raw `DataFusion` internals are never exposed.
266    pub fn query_pipeline(
267        context: impl Into<String>,
268        df_error: &datafusion_common::DataFusionError,
269    ) -> Self {
270        let translated = laminar_sql::error::translate_datafusion_error(&df_error.to_string());
271        Self::QueryPipeline {
272            context: context.into(),
273            translated: translated.to_string(),
274        }
275    }
276
277    /// Create a `QueryPipeline` error from a `DataFusion` error with stream
278    /// context and available column names for typo suggestions.
279    pub fn query_pipeline_with_columns(
280        context: impl Into<String>,
281        df_error: &datafusion_common::DataFusionError,
282        available_columns: &[&str],
283    ) -> Self {
284        let translated = laminar_sql::error::translate_datafusion_error_with_context(
285            &df_error.to_string(),
286            Some(available_columns),
287        );
288        Self::QueryPipeline {
289            context: context.into(),
290            translated: translated.to_string(),
291        }
292    }
293
294    /// Create a `QueryPipeline` error from an Arrow error with stream context.
295    pub fn query_pipeline_arrow(
296        context: impl Into<String>,
297        arrow_error: &arrow::error::ArrowError,
298    ) -> Self {
299        let translated = laminar_sql::error::translate_datafusion_error(&arrow_error.to_string());
300        Self::QueryPipeline {
301            context: context.into(),
302            translated: translated.to_string(),
303        }
304    }
305
306    /// Returns the structured `LDB-NNNN` error code for this error.
307    ///
308    /// Every `DbError` variant maps to a stable error code that can be used
309    /// for programmatic handling, log searching, and metrics.
310    #[must_use]
311    pub fn code(&self) -> &'static str {
312        match self {
313            Self::Sql(_) | Self::SqlParse(_) => error_codes::SQL_UNSUPPORTED,
314            Self::Engine(_) | Self::Streaming(_) => error_codes::INTERNAL,
315            Self::DataFusion(_) => error_codes::QUERY_EXECUTION_FAILED,
316            Self::SourceNotFound(_) => error_codes::SOURCE_NOT_FOUND,
317            Self::SinkNotFound(_) => error_codes::SINK_NOT_FOUND,
318            Self::QueryNotFound(_) | Self::StreamNotFound(_) | Self::TableNotFound(_) => {
319                error_codes::SQL_TABLE_NOT_FOUND
320            }
321            Self::SourceAlreadyExists(_)
322            | Self::StreamAlreadyExists(_)
323            | Self::TableAlreadyExists(_) => error_codes::SOURCE_ALREADY_EXISTS,
324            Self::SinkAlreadyExists(_) => error_codes::SINK_ALREADY_EXISTS,
325            Self::InsertError(_) => error_codes::CONNECTOR_WRITE_ERROR,
326            Self::SchemaMismatch(_) => error_codes::SCHEMA_MISMATCH,
327            Self::InvalidOperation(_)
328            | Self::ReferenceTableQuotaExceeded { .. }
329            | Self::SubscriptionReplayPruned { .. }
330            | Self::SubscriptionEpochNotCommitted { .. }
331            | Self::SubscriptionSequencePruned { .. }
332            | Self::SubscriptionSequenceNotPublished { .. }
333            | Self::Unsupported(_) => error_codes::INVALID_OPERATION,
334            Self::Subscription(error) => error.code(),
335            #[cfg(feature = "cluster")]
336            Self::Topology(error) => error.code(),
337            #[cfg(feature = "cluster")]
338            Self::TopologySubmission { source, .. } => source.code(),
339            Self::Shutdown => error_codes::SHUTDOWN,
340            Self::Checkpoint(_) | Self::CheckpointStore(_) => error_codes::CHECKPOINT_FAILED,
341            Self::UnresolvedConfigVar(_) => error_codes::UNRESOLVED_CONFIG_VAR,
342            Self::Connector(_) | Self::ConnectorOp(_) => error_codes::CONNECTOR_CONNECTION_FAILED,
343            Self::Pipeline(_)
344            | Self::PipelineTerminal(_)
345            | Self::BackpressureFail(_)
346            | Self::GraphBufferBudgetExceeded { .. }
347            | Self::ShuffleNotReady(_)
348            | Self::ShuffleTerminal(_)
349            | Self::ShufflePartialSend(_)
350            | Self::ShuffleSendStalled(_)
351            | Self::StatefulOperatorPartialApply(_) => error_codes::PIPELINE_ERROR,
352            Self::ManagedStateBudgetExceeded { .. } | Self::GroupLimitExceeded { .. } => {
353                error_codes::MANAGED_STATE_BUDGET_EXCEEDED
354            }
355            Self::QueryPipeline { .. } => error_codes::QUERY_PIPELINE_ERROR,
356            Self::MaterializedView(_)
357            | Self::MaterializedViewQuotaExceeded { .. }
358            | Self::CommittedRead(_) => error_codes::MATERIALIZED_VIEW_ERROR,
359            Self::Storage(_) => error_codes::WAL_ERROR,
360            Self::Config(_) => error_codes::INVALID_CONFIG,
361        }
362    }
363
364    /// `true` for [`Self::ShuffleNotReady`] — the operator defers instead of failing the cycle.
365    #[must_use]
366    pub fn is_shuffle_not_ready(&self) -> bool {
367        matches!(self, Self::ShuffleNotReady(_))
368    }
369
370    /// `true` when retry or recovery cannot repair the current input and the pipeline must stop.
371    #[must_use]
372    pub fn requires_pipeline_halt(&self) -> bool {
373        matches!(
374            self,
375            Self::PipelineTerminal(_)
376                | Self::BackpressureFail(_)
377                | Self::GraphBufferBudgetExceeded { .. }
378                | Self::ShuffleTerminal(_)
379                | Self::ManagedStateBudgetExceeded { .. }
380                | Self::GroupLimitExceeded { .. }
381                | Self::MaterializedViewQuotaExceeded { .. }
382        )
383    }
384
385    /// `true` when failure-domain isolation cannot safely keep the current pipeline alive.
386    #[must_use]
387    pub fn requires_pipeline_recovery(&self) -> bool {
388        matches!(
389            self,
390            Self::Checkpoint(_)
391                | Self::ShufflePartialSend(_)
392                | Self::ShuffleSendStalled(_)
393                | Self::StatefulOperatorPartialApply(_)
394        )
395    }
396
397    /// Whether this error is transient (retryable).
398    #[must_use]
399    pub fn is_transient(&self) -> bool {
400        match self {
401            Self::Streaming(_)
402            | Self::Connector(_)
403            | Self::Checkpoint(_)
404            | Self::CheckpointStore(_) => true,
405            Self::ConnectorOp(e) => e.is_transient(),
406            _ => false,
407        }
408    }
409}
410
411impl std::fmt::Display for DbError {
412    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
413        match self {
414            Self::Sql(e) => write!(f, "SQL error: {e}"),
415            Self::Engine(e) => write!(f, "Engine error: {e}"),
416            Self::Streaming(e) => write!(f, "Streaming error: {e}"),
417            Self::DataFusion(e) => {
418                let translated = laminar_sql::error::translate_datafusion_error(&e.to_string());
419                write!(f, "{translated}")
420            }
421            Self::SourceNotFound(name) => {
422                write!(f, "[{}] Source '{name}' not found", self.code())
423            }
424            Self::SinkNotFound(name) => {
425                write!(f, "[{}] Sink '{name}' not found", self.code())
426            }
427            Self::QueryNotFound(name) => {
428                write!(f, "[{}] Query '{name}' not found", self.code())
429            }
430            Self::SourceAlreadyExists(name) => {
431                write!(f, "[{}] Source '{name}' already exists", self.code())
432            }
433            Self::SinkAlreadyExists(name) => {
434                write!(f, "[{}] Sink '{name}' already exists", self.code())
435            }
436            Self::StreamNotFound(name) => {
437                write!(f, "[{}] Stream '{name}' not found", self.code())
438            }
439            Self::StreamAlreadyExists(name) => {
440                write!(f, "[{}] Stream '{name}' already exists", self.code())
441            }
442            Self::TableNotFound(name) => {
443                write!(f, "[{}] Table '{name}' not found", self.code())
444            }
445            Self::TableAlreadyExists(name) => {
446                write!(f, "[{}] Table '{name}' already exists", self.code())
447            }
448            Self::InsertError(msg) => {
449                write!(f, "[{}] Insert error: {msg}", self.code())
450            }
451            Self::SchemaMismatch(msg) => {
452                write!(f, "[{}] Schema mismatch: {msg}", self.code())
453            }
454            Self::InvalidOperation(msg) => {
455                write!(f, "[{}] Invalid operation: {msg}", self.code())
456            }
457            Self::SubscriptionReplayPruned { .. }
458            | Self::SubscriptionEpochNotCommitted { .. }
459            | Self::SubscriptionSequencePruned { .. }
460            | Self::SubscriptionSequenceNotPublished { .. } => self.fmt_subscription_start_error(f),
461            Self::Subscription(error) => write!(f, "[{}] {error}", self.code()),
462            #[cfg(feature = "cluster")]
463            Self::Topology(error) => write!(f, "{error}"),
464            #[cfg(feature = "cluster")]
465            Self::TopologySubmission { operation_id, source } => write!(f,
466                "{source}; topology operation {} may be admitted; query status and retry the same identity",
467                operation_id.get()),
468            Self::SqlParse(e) => write!(f, "SQL parse error: {e}"),
469            Self::Shutdown => write!(f, "[{}] Database is shut down", self.code()),
470            Self::Checkpoint(msg) => {
471                write!(f, "[{}] Checkpoint error: {msg}", self.code())
472            }
473            Self::CheckpointStore(e) => {
474                write!(f, "[{}] Checkpoint store error: {e}", self.code())
475            }
476            Self::UnresolvedConfigVar(msg) => {
477                write!(f, "[{}] Unresolved config variable: {msg}", self.code())
478            }
479            Self::Connector(msg) => {
480                write!(f, "[{}] Connector error: {msg}", self.code())
481            }
482            Self::ConnectorOp(e) => {
483                write!(f, "[{}] Connector error: {e}", self.code())
484            }
485            Self::Pipeline(_)
486            | Self::PipelineTerminal(_)
487            | Self::BackpressureFail(_)
488            | Self::GraphBufferBudgetExceeded { .. }
489            | Self::ShuffleNotReady(_)
490            | Self::ShuffleTerminal(_)
491            | Self::ShufflePartialSend(_)
492            | Self::ShuffleSendStalled(_)
493            | Self::StatefulOperatorPartialApply(_)
494            | Self::ReferenceTableQuotaExceeded { .. }
495            | Self::MaterializedViewQuotaExceeded { .. }
496            | Self::ManagedStateBudgetExceeded { .. }
497            | Self::GroupLimitExceeded { .. } => self.fmt_execution_error(f),
498            Self::QueryPipeline {
499                context,
500                translated,
501            } => write!(f, "Stream '{context}': {translated}"),
502            Self::MaterializedView(msg) => {
503                write!(f, "[{}] Materialized view error: {msg}", self.code())
504            }
505            Self::CommittedRead(error) => {
506                write!(f, "[{}] Committed read: {error}", self.code())
507            }
508            Self::Storage(msg) => {
509                write!(f, "[{}] Storage error: {msg}", self.code())
510            }
511            Self::Config(msg) => {
512                write!(f, "[{}] Config error: {msg}", self.code())
513            }
514            Self::Unsupported(msg) => {
515                write!(f, "[{}] Unsupported: {msg}", self.code())
516            }
517        }
518    }
519}
520
521impl DbError {
522    fn fmt_subscription_start_error(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
523        match self {
524            Self::SubscriptionReplayPruned {
525                name,
526                requested,
527                earliest_retained,
528            } => write!(
529                f,
530                "[{}] Epoch {requested} for stream '{name}' is no longer retained (earliest retained is {earliest_retained})",
531                self.code()
532            ),
533            Self::SubscriptionEpochNotCommitted {
534                name,
535                requested,
536                latest_committed,
537            } => match latest_committed {
538                Some(latest) => write!(
539                    f,
540                    "[{}] Epoch {requested} for stream '{name}' is not committed (latest committed is {latest})",
541                    self.code()
542                ),
543                None => write!(
544                    f,
545                    "[{}] Epoch {requested} for stream '{name}' is not committed (no committed epoch is available)",
546                    self.code()
547                ),
548            },
549            Self::SubscriptionSequencePruned {
550                name,
551                requested_sequence,
552                earliest_retained_sequence,
553            } => match earliest_retained_sequence {
554                Some(earliest) => write!(
555                    f,
556                    "[{}] Sequence {requested_sequence} for stream '{name}' is no longer retained (earliest retained sequence is {earliest})",
557                    self.code()
558                ),
559                None => write!(
560                    f,
561                    "[{}] Sequence {requested_sequence} for stream '{name}' is no longer retained (no replay history is retained)",
562                    self.code()
563                ),
564            },
565            Self::SubscriptionSequenceNotPublished {
566                name,
567                requested_sequence,
568                next_sequence,
569            } => write!(
570                f,
571                "[{}] Sequence {requested_sequence} for stream '{name}' has not been published (next sequence is {next_sequence})",
572                self.code()
573            ),
574            _ => unreachable!("subscription-start formatting is dispatched only for start errors"),
575        }
576    }
577
578    fn fmt_execution_error(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
579        match self {
580            Self::Pipeline(msg) => {
581                write!(f, "[{}] Pipeline error: {msg}", self.code())
582            }
583            Self::PipelineTerminal(msg) => {
584                write!(f, "[{}] Terminal pipeline error: {msg}", self.code())
585            }
586            Self::BackpressureFail(msg) => {
587                write!(f, "[{}] Backpressure fail: {msg}", self.code())
588            }
589            Self::GraphBufferBudgetExceeded {
590                node, port, batches, bytes, max_batches, max_bytes,
591            } => write!(
592                f,
593                "[{}] Graph input budget exceeded at '{node}' port {port}: projected={batches} batches/{bytes} bytes, limits={max_batches} batches/{max_bytes:?} bytes; reduce batch size or increase graph input limits; terminal fault resolution is required before restarting",
594                self.code()
595            ),
596            Self::ShuffleNotReady(msg) => {
597                write!(f, "[{}] Shuffle target not ready: {msg}", self.code())
598            }
599            Self::ShuffleTerminal(msg) => {
600                write!(f, "[{}] Terminal shuffle routing error: {msg}", self.code())
601            }
602            Self::ShufflePartialSend(msg) => {
603                write!(f, "[{}] Shuffle partial send: {msg}", self.code())
604            }
605            Self::ShuffleSendStalled(msg) => {
606                write!(f, "[{}] Shuffle send stalled: {msg}", self.code())
607            }
608            Self::StatefulOperatorPartialApply(msg) => {
609                write!(
610                    f,
611                    "[{}] Stateful operator partial apply: {msg}",
612                    self.code()
613                )
614            }
615            Self::ManagedStateBudgetExceeded {
616                context,
617                accounted_bytes,
618                limit_bytes,
619            } => write!(
620                f,
621                "[{}] Managed state budget exceeded during {context}: accounted={accounted_bytes} bytes, limit={limit_bytes} bytes",
622                self.code()
623            ),
624            Self::GroupLimitExceeded {
625                context,
626                groups,
627                limit,
628            } => write!(
629                f,
630                "[{}] Group limit exceeded during {context}: groups={groups}, limit={limit}",
631                self.code()
632            ),
633            Self::MaterializedViewQuotaExceeded { view, rows, bytes, max_rows, max_bytes } => write!(
634                f, "[{}] Materialized-view '{view}' quota exceeded: projected={rows} rows/{bytes} bytes, limits={max_rows} rows/{max_bytes} bytes", self.code()
635            ),
636            Self::ReferenceTableQuotaExceeded { table, rows, bytes, max_rows, max_bytes } => write!(
637                f, "[{}] Reference-table '{table}' quota exceeded: projected={rows} rows/{bytes} bytes, limits={max_rows} rows/{max_bytes} bytes", self.code()
638            ),
639            _ => unreachable!("execution formatting is dispatched only for execution errors"),
640        }
641    }
642}