Skip to main content

mz_adapter/
error.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10use std::collections::{BTreeMap, BTreeSet};
11use std::error::Error;
12use std::fmt;
13use std::num::TryFromIntError;
14
15use dec::TryFromDecimalError;
16use itertools::Itertools;
17use mz_catalog::builtin::MZ_CATALOG_SERVER_CLUSTER;
18use mz_compute_client::controller::error as compute_error;
19use mz_compute_client::controller::error::InstanceMissing;
20
21use mz_compute_types::ComputeInstanceId;
22use mz_controller_types::ClusterId;
23use mz_expr::EvalError;
24use mz_ore::cast::CastFrom;
25use mz_ore::error::ErrorExt;
26use mz_ore::stack::RecursionLimitError;
27use mz_ore::str::StrExt;
28use mz_pgwire_common::{ErrorResponse, Severity};
29use mz_repr::adt::array::InvalidArrayError;
30use mz_repr::adt::range::InvalidRangeError;
31use mz_repr::adt::timestamp::TimestampError;
32use mz_repr::explain::ExplainError;
33use mz_repr::strconv::{ParseError, ParseErrorKind};
34use mz_repr::{ColumnDiff, ColumnName, KeyDiff, NotNullViolation, RelationDescDiff, Timestamp};
35use mz_sql::plan::PlanError;
36use mz_sql::rbac;
37use mz_sql::session::vars::VarError;
38use mz_storage_types::connections::ConnectionValidationError;
39use mz_storage_types::controller::StorageError;
40use mz_storage_types::errors::CollectionMissing;
41use smallvec::SmallVec;
42use timely::progress::Antichain;
43use tokio::sync::oneshot;
44use tokio_postgres::error::SqlState;
45
46use crate::coord::NetworkPolicyError;
47use crate::optimize::OptimizerError;
48use crate::peek_client::CollectionLookupError;
49
50/// Errors that can occur in the coordinator.
51#[derive(Debug)]
52pub enum AdapterError {
53    /// A `SUBSCRIBE` was requested whose `UP TO` bound precedes its `as_of` timestamp
54    AbsurdSubscribeBounds {
55        as_of: mz_repr::Timestamp,
56        up_to: mz_repr::Timestamp,
57    },
58    /// Attempted to use a potentially ambiguous column reference expression with a system table.
59    // We don't allow this until https://github.com/MaterializeInc/database-issues/issues/4824 is
60    // resolved because it prevents us from adding columns to system tables.
61    AmbiguousSystemColumnReference,
62    /// An error occurred in a catalog operation.
63    Catalog(mz_catalog::memory::error::Error),
64    /// 1. The cached plan or descriptor changed,
65    /// 2. or some dependency of a statement disappeared during sequencing.
66    /// TODO(ggevay): we should refactor 2. usages to use `ConcurrentDependencyDrop` instead
67    /// (e.g., in MV sequencing)
68    ChangedPlan(String),
69    /// The cursor already exists.
70    DuplicateCursor(String),
71    /// An error while evaluating an expression.
72    Eval(EvalError),
73    /// An error occurred while planning the statement.
74    Explain(ExplainError),
75    /// The ID allocator exhausted all valid IDs.
76    IdExhaustionError,
77    /// Unexpected internal state was encountered.
78    Internal(String),
79    /// Attempted to read from log sources of a replica with disabled introspection.
80    IntrospectionDisabled {
81        log_names: Vec<String>,
82    },
83    /// Attempted to create an object dependent on log sources that doesn't support
84    /// log dependencies.
85    InvalidLogDependency {
86        object_type: String,
87        log_names: Vec<String>,
88    },
89    /// No such cluster replica size has been configured.
90    InvalidClusterReplicaAz {
91        az: String,
92        expected: Vec<String>,
93    },
94    /// SET TRANSACTION ISOLATION LEVEL was called in the middle of a transaction.
95    InvalidSetIsolationLevel,
96    /// SET cluster was called in the middle of a transaction.
97    InvalidSetCluster,
98    /// No such storage instance size has been configured.
99    InvalidStorageClusterSize {
100        size: String,
101        expected: Vec<String>,
102    },
103    /// Creating a source or sink without specifying its size is forbidden.
104    SourceOrSinkSizeRequired {
105        expected: Vec<String>,
106    },
107    /// The selection value for a table mutation operation refers to an invalid object.
108    InvalidTableMutationSelection {
109        /// The full name of the problematic object (e.g. a source or source-export table).
110        object_name: String,
111        /// Human-readable type of the object (e.g. "source", "source-export table").
112        object_type: String,
113    },
114    /// A read-then-write statement's read set has more transitive dependencies
115    /// than validation is willing to walk.
116    ReadThenWriteDependencyLimitExceeded {
117        /// The configured maximum number of dependencies.
118        max_rw_dependencies: usize,
119    },
120    /// Expression violated a column's constraint
121    ConstraintViolation(NotNullViolation),
122    /// An error occurred while decoding COPY data.
123    CopyFormatError(String),
124    /// Transaction cluster was dropped in the middle of a transaction.
125    ConcurrentClusterDrop,
126    /// A dependency was dropped while sequencing a statement.
127    ConcurrentDependencyDrop {
128        dependency_kind: &'static str,
129        dependency_id: String,
130    },
131    /// A dependency's definition changed while a statement was being sequenced.
132    /// Raised by `PlanValidity::check` when a dependency's `create_sql` hash no
133    /// longer matches what we captured at plan time, and by
134    /// `Coordinator::stage_group_commit` when a table's `RelationDesc` no longer
135    /// matches the rows a staged write packed against it.
136    ConcurrentDependencyMutation {
137        dependency_id: String,
138    },
139    /// A frontend read-then-write exhausted its OCC retry budget.
140    ///
141    /// The statement is retryable: every attempt was refused before anything
142    /// was appended, so nothing it intended has been committed.
143    ReadThenWriteContention,
144    /// The write timestamp ran past what the write timeline may be advanced to,
145    /// so nothing was appended. See `coord::timeline::write_ts_upper_bound` for
146    /// the bound and why exceeding it is not recoverable.
147    ///
148    /// The timestamp comes from the timeline's oracle, so reaching this means the
149    /// oracle has run away from the wall clock rather than that the statement
150    /// asked for anything unusual.
151    ReadThenWriteTimestampTooFarAhead {
152        target_timestamp: mz_repr::Timestamp,
153        limit: mz_repr::Timestamp,
154    },
155    CollectionUnreadable {
156        id: String,
157    },
158    /// Target cluster has no replicas to service query.
159    NoClusterReplicasAvailable {
160        name: String,
161        is_managed: bool,
162    },
163    /// The named operation cannot be run in a transaction.
164    OperationProhibitsTransaction(String),
165    /// The named operation requires an active transaction.
166    OperationRequiresTransaction(String),
167    /// An error occurred while planning the statement.
168    PlanError(PlanError),
169    /// The named prepared statement already exists.
170    PreparedStatementExists(String),
171    /// Wrapper around parsing error
172    ParseError(mz_sql_parser::parser::ParserStatementError),
173    /// The transaction is in read-only mode.
174    ReadOnlyTransaction,
175    /// The transaction in in read-only mode and a read already occurred.
176    ReadWriteUnavailable,
177    /// The recursion limit of some operation was exceeded.
178    RecursionLimit(RecursionLimitError),
179    /// A query in a transaction referenced a relation outside the first query's
180    /// time domain.
181    RelationOutsideTimeDomain {
182        relations: Vec<String>,
183        names: Vec<String>,
184    },
185    /// A query tried to create more resources than is allowed in the system configuration.
186    ResourceExhaustion {
187        resource_type: String,
188        limit_name: String,
189        desired: String,
190        limit: String,
191        current: String,
192    },
193    /// Result size of a query is too large.
194    ResultSize(String),
195    /// A `SUBSCRIBE` (or `COPY (SUBSCRIBE ...) TO STDOUT`) buffered more bytes in
196    /// environmentd than its budget allows because the client was not reading
197    /// fast enough. The subscribe is retired rather than let one slow client
198    /// grow the shared process's memory without bound.
199    SubscribeFellBehind {
200        /// Bytes buffered when the budget was exceeded.
201        buffered_bytes: usize,
202        /// The `subscribe_max_buffered_bytes` budget that was exceeded.
203        max_buffered_bytes: usize,
204    },
205    /// The specified feature is not permitted in safe mode.
206    SafeModeViolation(String),
207    /// The current transaction had the wrong set of write locks.
208    WrongSetOfLocks,
209    /// Waiting on a query timed out.
210    ///
211    /// Note this differs slightly from PG's implementation/semantics.
212    StatementTimeout,
213    /// The user canceled the query
214    Canceled,
215    /// An idle session in a transaction has timed out.
216    IdleInTransactionSessionTimeout,
217    /// The transaction is in single-subscribe mode.
218    SubscribeOnlyTransaction,
219    /// An error occurred in the optimizer.
220    Optimizer(OptimizerError),
221    /// A query depends on items which are not allowed to be referenced from the current cluster.
222    UnallowedOnCluster {
223        depends_on: SmallVec<[String; 2]>,
224        cluster: String,
225    },
226    /// A user tried to perform an action that they were unauthorized to do.
227    Unauthorized(rbac::UnauthorizedError),
228    /// The named cursor does not exist.
229    UnknownCursor(String),
230    /// The named role does not exist.
231    UnknownLoginRole(String),
232    UnknownPreparedStatement(String),
233    /// The named cluster replica does not exist.
234    UnknownClusterReplica {
235        cluster_name: String,
236        replica_name: String,
237    },
238    /// The named setting does not exist.
239    UnrecognizedConfigurationParam(String),
240    /// A generic error occurred.
241    //
242    // TODO(benesch): convert all those errors to structured errors.
243    Unstructured(anyhow::Error),
244    /// The named feature is not supported and will (probably) not be.
245    Unsupported(&'static str),
246    /// Some feature isn't available for a (potentially opaque) reason.
247    /// For example, in cloud Self-Managed auth features aren't available,
248    /// but we don't want to mention self managed auth.
249    UnavailableFeature {
250        feature: String,
251        docs: Option<String>,
252    },
253    /// Attempted to read from log sources without selecting a target replica.
254    UntargetedLogRead {
255        log_names: Vec<String>,
256    },
257    /// The transaction is in write-only mode.
258    WriteOnlyTransaction,
259    /// The transaction can only execute a single statement.
260    SingleStatementTransaction,
261    /// The transaction can only execute simple DDL.
262    DDLOnlyTransaction,
263    /// Another session modified the Catalog while this transaction was open.
264    DDLTransactionRace,
265    /// A conditional cluster-config write failed its precondition: the cluster's
266    /// managed config changed between when the write was conditioned and when it
267    /// was applied. Produced by `Op::CheckClusterState`, never by SQL DDL, so it
268    /// does not reach a SQL client.
269    ClusterStateChanged {
270        cluster_id: ClusterId,
271    },
272    /// An error occurred in the storage layer
273    Storage(mz_storage_types::controller::StorageError),
274    /// An error occurred in the compute layer
275    Compute(anyhow::Error),
276    /// An error in the orchestrator layer
277    Orchestrator(anyhow::Error),
278    /// A statement tried to drop a role that had dependent objects.
279    ///
280    /// The map keys are role names and values are detailed error messages.
281    DependentObject(BTreeMap<String, Vec<String>>),
282    /// When performing an `ALTER` of some variety, re-planning the statement
283    /// errored.
284    InvalidAlter(&'static str, PlanError),
285    /// An error occurred while validating a connection.
286    ConnectionValidation(ConnectionValidationError),
287    /// We refuse to create the materialized view, because it would never be refreshed, so it would
288    /// never be queryable. This can happen when the only specified refreshes are further back in
289    /// the past than the initial compaction window of the materialized view.
290    MaterializedViewWouldNeverRefresh(Timestamp, Timestamp),
291    /// A CREATE MATERIALIZED VIEW statement tried to acquire a read hold at a REFRESH AT time,
292    /// but was unable to get a precise read hold.
293    InputNotReadableAtRefreshAtTime(Timestamp, Antichain<Timestamp>),
294    /// A humanized version of [`StorageError::RtrTimeout`].
295    RtrTimeout(String),
296    /// A humanized version of [`StorageError::RtrDropFailure`].
297    RtrDropFailure(String),
298    /// The collection requested to be sinked cannot be read at any timestamp
299    UnreadableSinkCollection,
300    /// User sessions have been blocked.
301    UserSessionsDisallowed,
302    /// This use session has been denied by a NetworkPolicy.
303    NetworkPolicyDenied(NetworkPolicyError),
304    /// Something attempted a write (to catalog, storage, tables, etc.) while in
305    /// read-only mode.
306    ReadOnly,
307    AlterClusterTimeout,
308    AlterClusterWhilePendingReplicas,
309    /// Attempt to convert a cluster to unmanaged while a graceful
310    /// reconfiguration is in progress.
311    AlterClusterUnmanagedWhileReconfiguring,
312    /// Attempt to convert a cluster to unmanaged while a hydration burst is in
313    /// flight.
314    AlterClusterUnmanagedWhileBursting,
315    /// Attempt to change a cluster's replication factor while a graceful
316    /// reconfiguration is in progress.
317    AlterClusterReplicationFactorWhileReconfiguring,
318    /// Attempt to change a cluster's schedule while a graceful reconfiguration
319    /// is in progress.
320    AlterClusterScheduleWhileReconfiguring,
321    /// Attempt to use `WAIT` on an `ALTER` of a scheduled (non-`MANUAL`)
322    /// cluster, whose replica set the controller reshapes in the background.
323    AlterClusterWaitOnScheduledCluster,
324    AuthenticationError(AuthenticationError),
325    /// Schema of a replacement is incompatible with the target.
326    ReplacementSchemaMismatch(RelationDescDiff),
327    /// Attempt to apply a replacement to a sealed materialized view.
328    ReplaceMaterializedViewSealed {
329        name: String,
330    },
331    /// Could not find a valid timestamp satisfying all constraints.
332    ImpossibleTimestampConstraints {
333        constraints: String,
334    },
335    /// OIDC group-to-role sync failed and strict mode is enabled.
336    OidcGroupSyncFailed(String),
337    /// Returned when bounded staleness was selected but the input frontiers lag
338    /// further than the bound permits, so no timestamp in the no-wait window is
339    /// at most `bound` stale.
340    BoundedStalenessExceeded {
341        bound: std::time::Duration,
342        gap_ms: u64,
343        slowest_input: Option<mz_repr::GlobalId>,
344    },
345    /// A write was attempted in a session whose isolation level is bounded
346    /// staleness. Bounded staleness is read-only.
347    BoundedStalenessReadOnly,
348    /// `real_time_recency = on` and bounded staleness were both requested in
349    /// the same session; they are mutually exclusive.
350    BoundedStalenessRealTimeRecencyConflict,
351    /// A bounded-staleness query touched a timeline whose timestamps are not
352    /// the `EpochMilliseconds` wall-clock timeline.
353    BoundedStalenessTimelineUnsupported,
354}
355
356#[derive(Debug, thiserror::Error)]
357pub enum AuthenticationError {
358    #[error("invalid credentials")]
359    InvalidCredentials,
360    #[error("role is not allowed to login")]
361    NonLogin,
362    #[error("role does not exist")]
363    RoleNotFound,
364    #[error("password is required")]
365    PasswordRequired,
366}
367
368/// Maps a parse/cast [`ParseError`] to the appropriate `DATA_EXCEPTION` (class
369/// 22) SQLSTATE. Out-of-range values map to overflow codes and malformed input
370/// maps to invalid-representation codes, in both cases distinguishing datetime
371/// types from everything else, matching PostgreSQL.
372fn parse_error_code(err: &ParseError) -> SqlState {
373    let is_datetime = matches!(
374        &*err.type_name,
375        "date" | "time" | "timestamp" | "timestamp with time zone" | "interval"
376    );
377    match (err.kind, is_datetime) {
378        (ParseErrorKind::OutOfRange, true) => SqlState::DATETIME_FIELD_OVERFLOW,
379        (ParseErrorKind::OutOfRange, false) => SqlState::NUMERIC_VALUE_OUT_OF_RANGE,
380        (ParseErrorKind::InvalidInputSyntax, true) => SqlState::INVALID_DATETIME_FORMAT,
381        (ParseErrorKind::InvalidInputSyntax, false) => SqlState::INVALID_TEXT_REPRESENTATION,
382    }
383}
384
385/// Maps an [`EvalError`] to the appropriate SQLSTATE.
386///
387/// Historically every `EvalError` fell through to `INTERNAL_ERROR` (`XX000`),
388/// but the overwhelming majority are user-facing data exceptions (class 22) or
389/// other well-defined conditions, not internal errors. This match is
390/// deliberately exhaustive — with no wildcard — so that adding a new
391/// `EvalError` variant is a compile error until it is assigned a code, and we
392/// never silently regress to `XX000`. Codes are chosen to match PostgreSQL
393/// where an equivalent error exists.
394fn eval_error_code(err: &EvalError) -> SqlState {
395    match err {
396        // Division and mathematical domain errors.
397        EvalError::DivisionByZero => SqlState::DIVISION_BY_ZERO,
398        EvalError::NegSqrt | EvalError::ComplexOutOfRange(_) => {
399            SqlState::INVALID_ARGUMENT_FOR_POWER_FUNCTION
400        }
401        EvalError::InfinityOutOfDomain(_)
402        | EvalError::NegativeOutOfDomain(_)
403        | EvalError::ZeroOutOfDomain(_)
404        | EvalError::OutOfDomain(..)
405        | EvalError::Undefined(_) => SqlState::INVALID_PARAMETER_VALUE,
406
407        // Out-of-range numeric, integer, and float values.
408        EvalError::FloatOverflow
409        | EvalError::FloatUnderflow
410        | EvalError::NumericFieldOverflow
411        | EvalError::Float32OutOfRange(_)
412        | EvalError::Float64OutOfRange(_)
413        | EvalError::Int16OutOfRange(_)
414        | EvalError::Int32OutOfRange(_)
415        | EvalError::Int64OutOfRange(_)
416        | EvalError::UInt16OutOfRange(_)
417        | EvalError::UInt32OutOfRange(_)
418        | EvalError::UInt64OutOfRange(_)
419        | EvalError::OidOutOfRange(_)
420        | EvalError::MzTimestampOutOfRange(_)
421        | EvalError::MzTimestampStepOverflow
422        | EvalError::CharOutOfRange => SqlState::NUMERIC_VALUE_OUT_OF_RANGE,
423
424        // Out-of-range datetime and interval values.
425        EvalError::DateOutOfRange
426        | EvalError::TimestampOutOfRange
427        | EvalError::TimestampCannotBeNan
428        | EvalError::DateBinOutOfRange(_)
429        | EvalError::DateDiffOverflow { .. } => SqlState::DATETIME_FIELD_OVERFLOW,
430        EvalError::IntervalOutOfRange(_) => SqlState::INTERVAL_FIELD_OVERFLOW,
431
432        // Parse/cast failures, plus other malformed textual/encoded input.
433        EvalError::Parse(e) => parse_error_code(e),
434        EvalError::ParseHex(_)
435        | EvalError::InvalidBase64Equals
436        | EvalError::InvalidBase64Symbol(_)
437        | EvalError::InvalidBase64EndSequence => SqlState::INVALID_TEXT_REPRESENTATION,
438        EvalError::InvalidByteSequence { .. } => SqlState::CHARACTER_NOT_IN_REPERTOIRE,
439        EvalError::CharacterNotValidForEncoding(_) | EvalError::CharacterTooLargeForEncoding(_) => {
440            SqlState::PROGRAM_LIMIT_EXCEEDED
441        }
442
443        // Invalid argument values.
444        EvalError::InvalidTimezone(_)
445        | EvalError::InvalidTimezoneInterval
446        | EvalError::InvalidTimezoneConversion
447        | EvalError::InvalidIanaTimezoneId(_)
448        | EvalError::InvalidLayer { .. }
449        | EvalError::InvalidEncodingName(_)
450        | EvalError::InvalidHashAlgorithm(_)
451        | EvalError::InvalidDatePart(_)
452        | EvalError::InvalidParameterValue(_)
453        | EvalError::InvalidJsonbCast { .. }
454        | EvalError::UnknownUnits(_)
455        | EvalError::UnsupportedUnits(..)
456        | EvalError::InvalidIdentifier { .. }
457        | EvalError::InvalidRoleId(_)
458        | EvalError::InvalidPrivileges(_)
459        | EvalError::NegLimit => SqlState::INVALID_PARAMETER_VALUE,
460
461        // Regular expressions.
462        EvalError::InvalidRegex(_) | EvalError::InvalidRegexFlag(_) => {
463            SqlState::INVALID_REGULAR_EXPRESSION
464        }
465
466        // LIKE escape sequences.
467        EvalError::UnterminatedLikeEscapeSequence | EvalError::LikeEscapeTooLong => {
468            SqlState::INVALID_ESCAPE_SEQUENCE
469        }
470
471        // NULL values where they are not permitted.
472        EvalError::KeyCannotBeNull
473        | EvalError::MustNotBeNull(_)
474        | EvalError::AclArrayNullElement
475        | EvalError::MzAclArrayNullElement => SqlState::NULL_VALUE_NOT_ALLOWED,
476
477        // Array subscript/dimension errors.
478        EvalError::IndexOutOfRange { .. }
479        | EvalError::ArrayFillWrongArraySubscripts
480        | EvalError::IncompatibleArrayDimensions { .. } => SqlState::ARRAY_SUBSCRIPT_ERROR,
481        EvalError::InvalidArray(e) => match e {
482            InvalidArrayError::TooManyDimensions(_) => SqlState::PROGRAM_LIMIT_EXCEEDED,
483            InvalidArrayError::WrongCardinality { .. } => SqlState::ARRAY_SUBSCRIPT_ERROR,
484        },
485
486        // Range errors.
487        EvalError::InvalidRange(e) => match e {
488            InvalidRangeError::CanonicalizationOverflow(_) => SqlState::NUMERIC_VALUE_OUT_OF_RANGE,
489            InvalidRangeError::MisorderedRangeBounds
490            | InvalidRangeError::InvalidRangeBoundFlags
491            | InvalidRangeError::NullRangeBoundFlags
492            | InvalidRangeError::DiscontiguousUnion
493            | InvalidRangeError::DiscontiguousDifference
494            | InvalidRangeError::InvalidRangeData => SqlState::DATA_EXCEPTION,
495        },
496
497        // Cardinality violations from scalar subqueries.
498        EvalError::MultipleRowsFromSubquery | EvalError::NegativeRowsFromSubquery => {
499            SqlState::CARDINALITY_VIOLATION
500        }
501
502        // Length, size, and resource limits.
503        EvalError::StringValueTooLong { .. } => SqlState::STRING_DATA_RIGHT_TRUNCATION,
504        EvalError::LikePatternTooLong
505        | EvalError::LengthTooLarge
506        | EvalError::TempStorageBudgetExceeded
507        | EvalError::NullCharacterNotPermitted
508        | EvalError::MaxArraySizeExceeded(_)
509        | EvalError::LetRecLimitExceeded(_) => SqlState::PROGRAM_LIMIT_EXCEEDED,
510
511        // Unsupported features.
512        EvalError::Unsupported { .. }
513        | EvalError::MultidimensionalArrayRemovalNotSupported
514        | EvalError::MultiDimensionalArraySearch => SqlState::FEATURE_NOT_SUPPORTED,
515
516        // User-raised errors (e.g. `error_if_null`).
517        EvalError::IfNullError(_) => SqlState::DATA_EXCEPTION,
518
519        // Genuinely internal errors.
520        EvalError::Internal(_)
521        | EvalError::InvalidCatalogJson(_)
522        | EvalError::TypeFromOid(_)
523        | EvalError::PrettyError(_)
524        | EvalError::RedactError(_) => SqlState::INTERNAL_ERROR,
525    }
526}
527
528impl AdapterError {
529    pub fn into_response(self, severity: Severity) -> ErrorResponse {
530        ErrorResponse {
531            severity,
532            code: self.code(),
533            message: self.to_string(),
534            detail: self.detail(),
535            hint: self.hint(),
536            position: self.position(),
537        }
538    }
539
540    pub fn position(&self) -> Option<usize> {
541        match self {
542            AdapterError::ParseError(err) => Some(err.error.pos),
543            _ => None,
544        }
545    }
546
547    /// Reports additional details about the error, if any are available.
548    pub fn detail(&self) -> Option<String> {
549        match self {
550            AdapterError::AmbiguousSystemColumnReference => {
551                Some("This is a current limitation in Materialize".into())
552            }
553            AdapterError::Catalog(c) => c.detail(),
554            AdapterError::Eval(e) => e.detail(),
555            AdapterError::RelationOutsideTimeDomain { relations, names } => Some(format!(
556                "The following relations in the query are outside the transaction's time domain:\n{}\n{}",
557                relations
558                    .iter()
559                    .map(|r| r.quoted().to_string())
560                    .collect::<Vec<_>>()
561                    .join("\n"),
562                match names.is_empty() {
563                    true => "No relations are available.".to_string(),
564                    false => format!(
565                        "Only the following relations are available:\n{}",
566                        names
567                            .iter()
568                            .map(|name| name.quoted().to_string())
569                            .collect::<Vec<_>>()
570                            .join("\n")
571                    ),
572                }
573            )),
574            AdapterError::SourceOrSinkSizeRequired { .. } => Some(
575                "Either specify the cluster that will maintain this object via IN CLUSTER or \
576                specify size via SIZE option."
577                    .into(),
578            ),
579            AdapterError::InvalidTableMutationSelection {
580                object_name,
581                object_type,
582            } => Some(format!(
583                "{object_type} '{}' may not be used in this operation; \
584                     the selection may refer to views and materialized views, but transitive \
585                     dependencies must not include sources or source-export tables",
586                object_name.quoted()
587            )),
588            AdapterError::ReadThenWriteDependencyLimitExceeded {
589                max_rw_dependencies,
590            } => Some(format!(
591                "The read set transitively depends on more than {max_rw_dependencies} \
592                     objects. Reduce the number of dependencies, or raise the \
593                     read_then_write_max_dependencies system parameter."
594            )),
595            AdapterError::SafeModeViolation(_) => Some(
596                "The Materialize server you are connected to is running in \
597                 safe mode, which limits the features that are available."
598                    .into(),
599            ),
600            AdapterError::IntrospectionDisabled { log_names }
601            | AdapterError::UntargetedLogRead { log_names } => Some(format!(
602                "The query references the following log sources:\n    {}",
603                log_names.join("\n    "),
604            )),
605            AdapterError::InvalidLogDependency { log_names, .. } => Some(format!(
606                "The object depends on the following log sources:\n    {}",
607                log_names.join("\n    "),
608            )),
609            AdapterError::PlanError(e) => e.detail(),
610            AdapterError::Unauthorized(unauthorized) => unauthorized.detail(),
611            AdapterError::DependentObject(dependent_objects) => Some(
612                dependent_objects
613                    .iter()
614                    .map(|(role_name, err_msgs)| {
615                        err_msgs
616                            .iter()
617                            .map(|err_msg| format!("{role_name}: {err_msg}"))
618                            .join("\n")
619                    })
620                    .join("\n"),
621            ),
622            AdapterError::Storage(storage_error) => storage_error
623                .source()
624                .map(|source_error| source_error.to_string_with_causes()),
625            AdapterError::ReadOnlyTransaction => Some(
626                "SELECT queries cannot be combined with other query types, including SUBSCRIBE."
627                    .into(),
628            ),
629            AdapterError::InvalidAlter(_, e) => e.detail(),
630            AdapterError::Optimizer(e) => e.detail(),
631            AdapterError::ConnectionValidation(e) => e.detail(),
632            AdapterError::MaterializedViewWouldNeverRefresh(last_refresh, earliest_possible) => {
633                Some(format!(
634                    "The specified last refresh is at {}, while the earliest possible time to compute the materialized \
635                    view is {}.",
636                    last_refresh, earliest_possible,
637                ))
638            }
639            AdapterError::UnallowedOnCluster { cluster, .. } => {
640                (cluster == MZ_CATALOG_SERVER_CLUSTER.name).then(|| {
641                    format!(
642                        "The transaction is executing on the \
643                        {cluster} cluster, maybe having been routed \
644                        there by the first statement in the transaction."
645                    )
646                })
647            }
648            AdapterError::InputNotReadableAtRefreshAtTime(oracle_read_ts, least_valid_read) => {
649                Some(format!(
650                    "The requested REFRESH AT time is {}, \
651                    but not all input collections are readable earlier than [{}].",
652                    oracle_read_ts,
653                    if least_valid_read.len() == 1 {
654                        format!(
655                            "{}",
656                            least_valid_read
657                                .as_option()
658                                .expect("antichain contains exactly 1 timestamp")
659                        )
660                    } else {
661                        // This can't occur currently
662                        format!("{:?}", least_valid_read)
663                    }
664                ))
665            }
666            AdapterError::RtrTimeout(name) => Some(format!(
667                "{name} failed to ingest data up to the real-time recency point"
668            )),
669            AdapterError::RtrDropFailure(name) => Some(format!(
670                "{name} dropped before ingesting data to the real-time recency point"
671            )),
672            AdapterError::UserSessionsDisallowed => {
673                Some("Your organization has been blocked. Please contact support.".to_string())
674            }
675            AdapterError::NetworkPolicyDenied(reason) => Some(format!("{reason}.")),
676            AdapterError::ReplacementSchemaMismatch(diff) => {
677                let mut lines: Vec<_> = diff.column_diffs.iter().map(|(idx, diff)| {
678                    let pos = idx + 1;
679                    match diff {
680                        ColumnDiff::Missing { name } => {
681                            let name = name.as_str().quoted();
682                            format!("missing column {name} at position {pos}")
683                        }
684                        ColumnDiff::Extra { name } => {
685                            let name = name.as_str().quoted();
686                            format!("extra column {name} at position {pos}")
687                        }
688                        ColumnDiff::TypeMismatch { name, left, right } => {
689                            let name = name.as_str().quoted();
690                            format!("column {name} at position {pos}: type mismatch (target: {left:?}, replacement: {right:?})")
691                        }
692                        ColumnDiff::NullabilityMismatch { name, left, right } => {
693                            let name = name.as_str().quoted();
694                            let left = if *left { "NULL" } else { "NOT NULL" };
695                            let right = if *right { "NULL" } else { "NOT NULL" };
696                            format!("column {name} at position {pos}: nullability mismatch (target: {left}, replacement: {right})")
697                        }
698                        ColumnDiff::NameMismatch { left, right } => {
699                            let left = left.as_str().quoted();
700                            let right = right.as_str().quoted();
701                            format!("column at position {pos}: name mismatch (target: {left}, replacement: {right})")
702                        }
703                    }
704                }).collect();
705
706                if let Some(KeyDiff { left, right }) = &diff.key_diff {
707                    let format_keys = |keys: &BTreeSet<Vec<ColumnName>>| {
708                        if keys.is_empty() {
709                            "(none)".to_string()
710                        } else {
711                            keys.iter()
712                                .map(|key| {
713                                    let cols = key.iter().map(|c| c.as_str()).join(", ");
714                                    format!("{{{cols}}}")
715                                })
716                                .join(", ")
717                        }
718                    };
719                    lines.push(format!(
720                        "keys differ (target: {}, replacement: {})",
721                        format_keys(left),
722                        format_keys(right)
723                    ));
724                }
725                Some(lines.join("\n"))
726            }
727            AdapterError::ReplaceMaterializedViewSealed { .. } => Some(
728                "The materialized view has already computed its output until the end of time, \
729                 so replacing its definition would have no effect."
730                    .into(),
731            ),
732            AdapterError::ImpossibleTimestampConstraints { constraints } => {
733                Some(format!("Constraints:\n{}", constraints))
734            }
735            AdapterError::BoundedStalenessExceeded {
736                gap_ms,
737                slowest_input,
738                ..
739            } => {
740                let mut detail = format!(
741                    "Freshest available timestamp is {}ms older than the bound.",
742                    gap_ms,
743                );
744                if let Some(id) = slowest_input {
745                    detail.push_str(&format!(" Slowest input: {}.", id));
746                }
747                Some(detail)
748            }
749            AdapterError::BoundedStalenessTimelineUnsupported => Some(
750                "This query touches a timeline other than the EpochMilliseconds wall-clock \
751                 timeline."
752                    .into(),
753            ),
754            _ => None,
755        }
756    }
757
758    /// Reports a hint for the user about how the error could be fixed.
759    pub fn hint(&self) -> Option<String> {
760        match self {
761            AdapterError::AmbiguousSystemColumnReference => Some(
762                "Rewrite the view to refer to all columns by name. Expand all wildcards and \
763                convert all NATURAL JOINs to USING joins."
764                    .to_string(),
765            ),
766            AdapterError::Catalog(c) => c.hint(),
767            AdapterError::Eval(e) => e.hint(),
768            AdapterError::SubscribeFellBehind { .. } => Some(
769                "The client is not reading results fast enough. Use a client that reads output \
770                without buffering, or raise the subscribe_max_buffered_bytes system variable."
771                    .to_string(),
772            ),
773            AdapterError::AlterClusterUnmanagedWhileReconfiguring => Some(
774                "Cancel the reconfiguration by altering the cluster back to its current \
775                configuration, or wait for it to settle, then convert."
776                    .to_string(),
777            ),
778            AdapterError::AlterClusterUnmanagedWhileBursting => Some(
779                "Remove the strategy with ALTER CLUSTER ... RESET (AUTO SCALING STRATEGY), \
780                or wait for the burst to wind down, then convert."
781                    .to_string(),
782            ),
783            AdapterError::AlterClusterReplicationFactorWhileReconfiguring => Some(
784                "Cancel the reconfiguration by altering the cluster back to its current \
785                configuration, or wait for it to settle, then change the replication factor."
786                    .to_string(),
787            ),
788            AdapterError::AlterClusterScheduleWhileReconfiguring => Some(
789                "Cancel the reconfiguration by altering the cluster back to its current \
790                configuration, or wait for it to settle, then change the schedule."
791                    .to_string(),
792            ),
793            AdapterError::AlterClusterWaitOnScheduledCluster => Some(
794                "The scheduler reshapes a scheduled cluster's replicas in the background. \
795                Run the ALTER without a WAIT option, or set SCHEDULE = MANUAL first."
796                    .to_string(),
797            ),
798            AdapterError::InvalidClusterReplicaAz { expected, az: _ } => {
799                Some(if expected.is_empty() {
800                    "No availability zones configured; do not specify AVAILABILITY ZONE".into()
801                } else {
802                    format!("Valid availability zones are: {}", expected.join(", "))
803                })
804            }
805            AdapterError::InvalidStorageClusterSize { expected, .. } => {
806                Some(format!("Valid sizes are: {}", expected.join(", ")))
807            }
808            AdapterError::SourceOrSinkSizeRequired { expected } => Some(format!(
809                "Try choosing one of the smaller sizes to start. Available sizes: {}",
810                expected.join(", ")
811            )),
812            AdapterError::NoClusterReplicasAvailable { is_managed, .. } => {
813                Some(if *is_managed {
814                    "Use ALTER CLUSTER to adjust the replication factor of the cluster. \
815                    Example:`ALTER CLUSTER <cluster-name> SET (REPLICATION FACTOR 1)`".into()
816                } else {
817                    "Use CREATE CLUSTER REPLICA to attach cluster replicas to the cluster".into()
818                })
819            }
820            AdapterError::UntargetedLogRead { .. } => Some(
821                "Use `SET cluster_replica = <replica-name>` to target a specific replica in the \
822                 active cluster. Note that subsequent queries will only be answered by \
823                 the selected replica, which might reduce availability. To undo the replica \
824                 selection, use `RESET cluster_replica`."
825                    .into(),
826            ),
827            AdapterError::ResourceExhaustion { resource_type, .. } => Some(format!(
828                "Drop an existing {resource_type} or contact support to request a limit increase."
829            )),
830            AdapterError::StatementTimeout => Some(
831                "Consider increasing the maximum allowed statement duration for this session by \
832                 setting the statement_timeout session variable. For example, `SET \
833                 statement_timeout = '120s'`."
834                    .into(),
835            ),
836            AdapterError::PlanError(e) => e.hint(),
837            AdapterError::UnallowedOnCluster { cluster, .. } => {
838                (cluster != MZ_CATALOG_SERVER_CLUSTER.name).then(||
839                    "Use `SET CLUSTER = <cluster-name>` to change your cluster and re-run the query."
840                    .to_string()
841                )
842            }
843            AdapterError::InvalidAlter(_, e) => e.hint(),
844            AdapterError::Optimizer(e) => e.hint(),
845            AdapterError::ConnectionValidation(e) => e.hint(),
846            AdapterError::InputNotReadableAtRefreshAtTime(_, _) => Some(
847                "You can use `REFRESH AT greatest(mz_now(), <explicit timestamp>)` to refresh \
848                 either at the explicitly specified timestamp, or now if the given timestamp would \
849                 be in the past.".to_string()
850            ),
851            AdapterError::AlterClusterTimeout => Some(
852                "Consider increasing the timeout duration in the alter cluster statement.".into(),
853            ),
854            AdapterError::DDLTransactionRace => Some(
855                "Currently, DDL transactions fail when any other DDL happens concurrently, \
856                 even on unrelated schemas/clusters.".into()
857            ),
858            AdapterError::ConcurrentDependencyMutation { .. } => Some(
859                "Another session modified one of this statement's dependencies before \
860                 it could commit. Retry the statement.".into()
861            ),
862            AdapterError::ReadThenWriteContention => Some(
863                "Concurrent writes to the target table kept this statement from \
864                 committing. Retry the statement, or lower the write concurrency.".into()
865            ),
866            AdapterError::ReadThenWriteTimestampTooFarAhead { .. } => Some(
867                "The write timestamp this statement would have committed at is far ahead of \
868                 the wall clock. Committing there would keep writes and strict-serializable \
869                 reads on this timeline from proceeding until the clock caught up.".into()
870            ),
871            AdapterError::CollectionUnreadable { .. } => Some(
872                "This could be because the collection has recently been dropped.".into()
873            ),
874            _ => None,
875        }
876    }
877
878    pub fn code(&self) -> SqlState {
879        // We define this up here to make sure `AdapterError::` and `OptimizerError::` act the same way.
880        const RECURSION_LIMIT_ERROR_CODE: SqlState = SqlState::INTERNAL_ERROR;
881
882        // TODO(benesch): we should only use `SqlState::INTERNAL_ERROR` for
883        // those errors that are truly internal errors. At the moment we have
884        // a various classes of uncategorized errors that use this error code
885        // inappropriately.
886        match self {
887            // DATA_EXCEPTION to match what Postgres returns for degenerate
888            // range bounds
889            AdapterError::AbsurdSubscribeBounds { .. } => SqlState::DATA_EXCEPTION,
890            AdapterError::AmbiguousSystemColumnReference => SqlState::FEATURE_NOT_SUPPORTED,
891            AdapterError::Catalog(e) => match &e.kind {
892                mz_catalog::memory::error::ErrorKind::VarError(e) => match e {
893                    VarError::ConstrainedParameter { .. } => SqlState::INVALID_PARAMETER_VALUE,
894                    VarError::FixedValueParameter { .. } => SqlState::INVALID_PARAMETER_VALUE,
895                    VarError::InvalidParameterType { .. } => SqlState::INVALID_PARAMETER_VALUE,
896                    VarError::InvalidParameterValue { .. } => SqlState::INVALID_PARAMETER_VALUE,
897                    VarError::ReadOnlyParameter(_) => SqlState::CANT_CHANGE_RUNTIME_PARAM,
898                    VarError::UnknownParameter(_) => SqlState::UNDEFINED_OBJECT,
899                    VarError::RequiresUnsafeMode { .. } => SqlState::CANT_CHANGE_RUNTIME_PARAM,
900                    VarError::RequiresFeatureFlag { .. } => SqlState::CANT_CHANGE_RUNTIME_PARAM,
901                },
902                _ => SqlState::INTERNAL_ERROR,
903            },
904            AdapterError::ChangedPlan(_) => SqlState::FEATURE_NOT_SUPPORTED,
905            AdapterError::DuplicateCursor(_) => SqlState::DUPLICATE_CURSOR,
906            // Evaluation errors are almost all user-facing data exceptions, not
907            // internal errors. `eval_error_code` matches every variant
908            // exhaustively so the catch-all `INTERNAL_ERROR` no longer applies
909            // to errors that are really the user's fault. See SQL-326.
910            AdapterError::Eval(e) => eval_error_code(e),
911            AdapterError::Explain(_) => SqlState::INTERNAL_ERROR,
912            AdapterError::IdExhaustionError => SqlState::INTERNAL_ERROR,
913            AdapterError::Internal(_) => SqlState::INTERNAL_ERROR,
914            AdapterError::IntrospectionDisabled { .. } => SqlState::FEATURE_NOT_SUPPORTED,
915            AdapterError::InvalidLogDependency { .. } => SqlState::FEATURE_NOT_SUPPORTED,
916            AdapterError::InvalidClusterReplicaAz { .. } => SqlState::FEATURE_NOT_SUPPORTED,
917            AdapterError::InvalidSetIsolationLevel => SqlState::ACTIVE_SQL_TRANSACTION,
918            AdapterError::InvalidSetCluster => SqlState::ACTIVE_SQL_TRANSACTION,
919            AdapterError::InvalidStorageClusterSize { .. } => SqlState::FEATURE_NOT_SUPPORTED,
920            AdapterError::SourceOrSinkSizeRequired { .. } => SqlState::FEATURE_NOT_SUPPORTED,
921            AdapterError::InvalidTableMutationSelection { .. } => {
922                SqlState::INVALID_TRANSACTION_STATE
923            }
924            AdapterError::ReadThenWriteDependencyLimitExceeded { .. } => {
925                SqlState::PROGRAM_LIMIT_EXCEEDED
926            }
927            AdapterError::ConstraintViolation(NotNullViolation(_)) => SqlState::NOT_NULL_VIOLATION,
928            AdapterError::CopyFormatError(_) => SqlState::BAD_COPY_FILE_FORMAT,
929            AdapterError::ConcurrentClusterDrop => SqlState::INVALID_TRANSACTION_STATE,
930            AdapterError::ConcurrentDependencyDrop { .. } => SqlState::UNDEFINED_OBJECT,
931            AdapterError::ConcurrentDependencyMutation { .. } => {
932                SqlState::T_R_SERIALIZATION_FAILURE
933            }
934            AdapterError::ReadThenWriteContention => SqlState::T_R_SERIALIZATION_FAILURE,
935            AdapterError::ReadThenWriteTimestampTooFarAhead { .. } => {
936                // An invariant violation in the environment rather than a property of
937                // the statement: the write timeline has run away from the wall clock.
938                // Nothing the client sends can produce or avoid it.
939                SqlState::INTERNAL_ERROR
940            }
941            AdapterError::CollectionUnreadable { .. } => SqlState::NO_DATA_FOUND,
942            AdapterError::NoClusterReplicasAvailable { .. } => SqlState::FEATURE_NOT_SUPPORTED,
943            AdapterError::OperationProhibitsTransaction(_) => SqlState::ACTIVE_SQL_TRANSACTION,
944            AdapterError::OperationRequiresTransaction(_) => SqlState::NO_ACTIVE_SQL_TRANSACTION,
945            AdapterError::ParseError(_) => SqlState::SYNTAX_ERROR,
946            AdapterError::PlanError(PlanError::InvalidSchemaName) => SqlState::INVALID_SCHEMA_NAME,
947            AdapterError::PlanError(PlanError::ColumnAlreadyExists { .. }) => {
948                SqlState::DUPLICATE_COLUMN
949            }
950            AdapterError::PlanError(PlanError::UnknownParameter(_)) => {
951                SqlState::UNDEFINED_PARAMETER
952            }
953            AdapterError::PlanError(PlanError::ParameterNotAllowed(_)) => {
954                SqlState::UNDEFINED_PARAMETER
955            }
956            // `PlanError::Unsupported` is raised (via `bail_unsupported!`) only for
957            // genuinely unsupported features, so it maps to PostgreSQL's
958            // feature-not-supported code rather than internal-error. See SQL-326.
959            AdapterError::PlanError(PlanError::Unsupported { .. }) => {
960                SqlState::FEATURE_NOT_SUPPORTED
961            }
962            AdapterError::PlanError(_) => SqlState::INTERNAL_ERROR,
963            AdapterError::PreparedStatementExists(_) => SqlState::DUPLICATE_PSTATEMENT,
964            AdapterError::ReadOnlyTransaction => SqlState::READ_ONLY_SQL_TRANSACTION,
965            AdapterError::ReadWriteUnavailable => SqlState::INVALID_TRANSACTION_STATE,
966            AdapterError::SingleStatementTransaction => SqlState::INVALID_TRANSACTION_STATE,
967            AdapterError::WrongSetOfLocks => SqlState::LOCK_NOT_AVAILABLE,
968            AdapterError::StatementTimeout => SqlState::QUERY_CANCELED,
969            AdapterError::Canceled => SqlState::QUERY_CANCELED,
970            AdapterError::IdleInTransactionSessionTimeout => {
971                SqlState::IDLE_IN_TRANSACTION_SESSION_TIMEOUT
972            }
973            AdapterError::RecursionLimit(_) => RECURSION_LIMIT_ERROR_CODE,
974            AdapterError::RelationOutsideTimeDomain { .. } => SqlState::INVALID_TRANSACTION_STATE,
975            AdapterError::ResourceExhaustion { .. } => SqlState::INSUFFICIENT_RESOURCES,
976            AdapterError::ResultSize(_) => SqlState::OUT_OF_MEMORY,
977            AdapterError::SubscribeFellBehind { .. } => SqlState::OUT_OF_MEMORY,
978            AdapterError::SafeModeViolation(_) => SqlState::INTERNAL_ERROR,
979            AdapterError::SubscribeOnlyTransaction => SqlState::INVALID_TRANSACTION_STATE,
980            AdapterError::Optimizer(e) => match e {
981                OptimizerError::PlanError(PlanError::InvalidSchemaName) => {
982                    SqlState::INVALID_SCHEMA_NAME
983                }
984                OptimizerError::PlanError(PlanError::ColumnAlreadyExists { .. }) => {
985                    SqlState::DUPLICATE_COLUMN
986                }
987                OptimizerError::PlanError(PlanError::UnknownParameter(_)) => {
988                    SqlState::UNDEFINED_PARAMETER
989                }
990                OptimizerError::PlanError(PlanError::ParameterNotAllowed(_)) => {
991                    SqlState::UNDEFINED_PARAMETER
992                }
993                OptimizerError::PlanError(PlanError::Unsupported { .. }) => {
994                    SqlState::FEATURE_NOT_SUPPORTED
995                }
996                OptimizerError::PlanError(_) => SqlState::INTERNAL_ERROR,
997                OptimizerError::RecursionLimitError(_) => RECURSION_LIMIT_ERROR_CODE,
998                OptimizerError::Internal(s) => {
999                    AdapterError::Internal(s.clone()).code() // Delegate to outer
1000                }
1001                OptimizerError::EvalError(e) => {
1002                    AdapterError::Eval(e.clone()).code() // Delegate to outer
1003                }
1004                OptimizerError::TransformError(_) => SqlState::INTERNAL_ERROR,
1005                OptimizerError::UnmaterializableFunction(_) => SqlState::FEATURE_NOT_SUPPORTED,
1006                OptimizerError::UncallableFunction { .. } => SqlState::FEATURE_NOT_SUPPORTED,
1007                OptimizerError::UnsupportedTemporalExpression(_) => SqlState::FEATURE_NOT_SUPPORTED,
1008                OptimizerError::RestrictedFunction(_) => SqlState::INSUFFICIENT_PRIVILEGE,
1009                // This should be handled by peek optimization, so it's an internal error if it
1010                // reaches the user.
1011                OptimizerError::InternalUnsafeMfpPlan(_) => SqlState::INTERNAL_ERROR,
1012            },
1013            AdapterError::UnallowedOnCluster { .. } => {
1014                SqlState::S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED
1015            }
1016            AdapterError::Unauthorized(_) => SqlState::INSUFFICIENT_PRIVILEGE,
1017            AdapterError::UnknownCursor(_) => SqlState::INVALID_CURSOR_NAME,
1018            AdapterError::UnknownPreparedStatement(_) => SqlState::UNDEFINED_PSTATEMENT,
1019            AdapterError::UnknownLoginRole(_) => SqlState::INVALID_AUTHORIZATION_SPECIFICATION,
1020            AdapterError::UnknownClusterReplica { .. } => SqlState::UNDEFINED_OBJECT,
1021            AdapterError::UnrecognizedConfigurationParam(_) => SqlState::UNDEFINED_OBJECT,
1022            AdapterError::Unsupported(..) => SqlState::FEATURE_NOT_SUPPORTED,
1023            AdapterError::UnavailableFeature { .. } => SqlState::FEATURE_NOT_SUPPORTED,
1024            AdapterError::Unstructured(_) => SqlState::INTERNAL_ERROR,
1025            AdapterError::UntargetedLogRead { .. } => SqlState::FEATURE_NOT_SUPPORTED,
1026            AdapterError::DDLTransactionRace => SqlState::T_R_SERIALIZATION_FAILURE,
1027            AdapterError::ClusterStateChanged { .. } => SqlState::T_R_SERIALIZATION_FAILURE,
1028            // It's not immediately clear which error code to use here because a
1029            // "write-only transaction", "single table write transaction", or "ddl only
1030            // transaction" are not things in Postgres. This error code is the generic "bad txn
1031            // thing" code, so it's probably the best choice.
1032            AdapterError::WriteOnlyTransaction => SqlState::INVALID_TRANSACTION_STATE,
1033            AdapterError::DDLOnlyTransaction => SqlState::INVALID_TRANSACTION_STATE,
1034            AdapterError::Storage(_) | AdapterError::Compute(_) | AdapterError::Orchestrator(_) => {
1035                SqlState::INTERNAL_ERROR
1036            }
1037            AdapterError::DependentObject(_) => SqlState::DEPENDENT_OBJECTS_STILL_EXIST,
1038            AdapterError::InvalidAlter(_, _) => SqlState::FEATURE_NOT_SUPPORTED,
1039            AdapterError::ConnectionValidation(_) => SqlState::SYSTEM_ERROR,
1040            // `DATA_EXCEPTION`, similarly to `AbsurdSubscribeBounds`.
1041            AdapterError::MaterializedViewWouldNeverRefresh(_, _) => SqlState::DATA_EXCEPTION,
1042            AdapterError::InputNotReadableAtRefreshAtTime(_, _) => SqlState::DATA_EXCEPTION,
1043            AdapterError::RtrTimeout(_) => SqlState::QUERY_CANCELED,
1044            AdapterError::RtrDropFailure(_) => SqlState::UNDEFINED_OBJECT,
1045            AdapterError::UnreadableSinkCollection => SqlState::from_code("MZ009"),
1046            AdapterError::UserSessionsDisallowed => SqlState::from_code("MZ010"),
1047            AdapterError::NetworkPolicyDenied(_) => SqlState::from_code("MZ011"),
1048            // In read-only mode all transactions are implicitly read-only
1049            // transactions.
1050            AdapterError::ReadOnly => SqlState::READ_ONLY_SQL_TRANSACTION,
1051            AdapterError::AlterClusterTimeout => SqlState::QUERY_CANCELED,
1052            AdapterError::AlterClusterWhilePendingReplicas => SqlState::OBJECT_IN_USE,
1053            AdapterError::AlterClusterUnmanagedWhileReconfiguring => SqlState::OBJECT_IN_USE,
1054            AdapterError::AlterClusterUnmanagedWhileBursting => SqlState::OBJECT_IN_USE,
1055            AdapterError::AlterClusterReplicationFactorWhileReconfiguring => {
1056                SqlState::OBJECT_IN_USE
1057            }
1058            AdapterError::AlterClusterScheduleWhileReconfiguring => SqlState::OBJECT_IN_USE,
1059            AdapterError::AlterClusterWaitOnScheduledCluster => SqlState::FEATURE_NOT_SUPPORTED,
1060            AdapterError::ReplacementSchemaMismatch(_) => SqlState::FEATURE_NOT_SUPPORTED,
1061            AdapterError::AuthenticationError(AuthenticationError::InvalidCredentials) => {
1062                SqlState::INVALID_PASSWORD
1063            }
1064            AdapterError::AuthenticationError(_) => SqlState::INVALID_AUTHORIZATION_SPECIFICATION,
1065            AdapterError::ReplaceMaterializedViewSealed { .. } => {
1066                SqlState::OBJECT_NOT_IN_PREREQUISITE_STATE
1067            }
1068            // similar to AbsurdSubscribeBounds
1069            AdapterError::ImpossibleTimestampConstraints { .. } => SqlState::DATA_EXCEPTION,
1070            AdapterError::OidcGroupSyncFailed(_) => SqlState::INTERNAL_ERROR,
1071            AdapterError::BoundedStalenessExceeded { .. } => SqlState::T_R_SERIALIZATION_FAILURE,
1072            // Matches ReadOnlyTransaction/ReadOnly: a write was rejected
1073            // because the session is effectively read-only.
1074            AdapterError::BoundedStalenessReadOnly => SqlState::READ_ONLY_SQL_TRANSACTION,
1075            AdapterError::BoundedStalenessRealTimeRecencyConflict => {
1076                SqlState::FEATURE_NOT_SUPPORTED
1077            }
1078            AdapterError::BoundedStalenessTimelineUnsupported => SqlState::FEATURE_NOT_SUPPORTED,
1079        }
1080    }
1081
1082    pub fn internal<E: std::fmt::Display>(context: &str, e: E) -> AdapterError {
1083        AdapterError::Internal(format!("{context}: {e}"))
1084    }
1085
1086    // We don't want the following error conversions to `ConcurrentDependencyDrop` to happen
1087    // automatically, because it might depend on the context whether `ConcurrentDependencyDrop`
1088    // is appropriate, so we want to make the conversion target explicit at the call site.
1089    // For example, maybe we get an `InstanceMissing` if the user specifies a non-existing cluster,
1090    // in which case `ConcurrentDependencyDrop` would not be appropriate.
1091
1092    pub fn concurrent_dependency_drop_from_instance_missing(e: InstanceMissing) -> Self {
1093        AdapterError::ConcurrentDependencyDrop {
1094            dependency_kind: "cluster",
1095            dependency_id: e.0.to_string(),
1096        }
1097    }
1098
1099    pub fn concurrent_dependency_drop_from_collection_missing(e: CollectionMissing) -> Self {
1100        AdapterError::ConcurrentDependencyDrop {
1101            dependency_kind: "collection",
1102            dependency_id: e.0.to_string(),
1103        }
1104    }
1105
1106    pub fn concurrent_dependency_drop_from_collection_lookup_error(
1107        e: CollectionLookupError,
1108        compute_instance: ComputeInstanceId,
1109    ) -> Self {
1110        match e {
1111            CollectionLookupError::InstanceMissing(id) => AdapterError::ConcurrentDependencyDrop {
1112                dependency_kind: "cluster",
1113                dependency_id: id.to_string(),
1114            },
1115            CollectionLookupError::CollectionMissing(id) => {
1116                AdapterError::ConcurrentDependencyDrop {
1117                    dependency_kind: "collection",
1118                    dependency_id: id.to_string(),
1119                }
1120            }
1121            CollectionLookupError::InstanceShutDown => AdapterError::ConcurrentDependencyDrop {
1122                dependency_kind: "cluster",
1123                dependency_id: compute_instance.to_string(),
1124            },
1125        }
1126    }
1127
1128    pub fn concurrent_dependency_drop_from_watch_set_install_error(
1129        e: compute_error::CollectionLookupError,
1130    ) -> Self {
1131        match e {
1132            compute_error::CollectionLookupError::InstanceMissing(id) => {
1133                AdapterError::ConcurrentDependencyDrop {
1134                    dependency_kind: "cluster",
1135                    dependency_id: id.to_string(),
1136                }
1137            }
1138            compute_error::CollectionLookupError::CollectionMissing(id) => {
1139                AdapterError::ConcurrentDependencyDrop {
1140                    dependency_kind: "collection",
1141                    dependency_id: id.to_string(),
1142                }
1143            }
1144        }
1145    }
1146
1147    pub fn concurrent_dependency_drop_from_instance_peek_error(
1148        e: mz_compute_client::controller::instance_client::PeekError,
1149        compute_instance: ComputeInstanceId,
1150    ) -> AdapterError {
1151        use mz_compute_client::controller::instance_client::PeekError::*;
1152        match e {
1153            ReplicaMissing(id) => AdapterError::ConcurrentDependencyDrop {
1154                dependency_kind: "replica",
1155                dependency_id: id.to_string(),
1156            },
1157            InstanceShutDown => AdapterError::ConcurrentDependencyDrop {
1158                dependency_kind: "cluster",
1159                dependency_id: compute_instance.to_string(),
1160            },
1161            e @ ReadHoldIdMismatch(_) => AdapterError::internal("instance peek error", e),
1162            e @ ReadHoldInsufficient(_) => AdapterError::internal("instance peek error", e),
1163        }
1164    }
1165
1166    pub fn concurrent_dependency_drop_from_collection_update_error(
1167        e: compute_error::CollectionUpdateError,
1168    ) -> Self {
1169        use compute_error::CollectionUpdateError::*;
1170        match e {
1171            InstanceMissing(id) => AdapterError::ConcurrentDependencyDrop {
1172                dependency_kind: "cluster",
1173                dependency_id: id.to_string(),
1174            },
1175            CollectionMissing(id) => AdapterError::ConcurrentDependencyDrop {
1176                dependency_kind: "collection",
1177                dependency_id: id.to_string(),
1178            },
1179        }
1180    }
1181
1182    pub fn concurrent_dependency_drop_from_peek_error(
1183        e: mz_compute_client::controller::error::PeekError,
1184    ) -> AdapterError {
1185        use mz_compute_client::controller::error::PeekError::*;
1186        match e {
1187            InstanceMissing(id) => AdapterError::ConcurrentDependencyDrop {
1188                dependency_kind: "cluster",
1189                dependency_id: id.to_string(),
1190            },
1191            CollectionMissing(id) => AdapterError::ConcurrentDependencyDrop {
1192                dependency_kind: "collection",
1193                dependency_id: id.to_string(),
1194            },
1195            ReplicaMissing(id) => AdapterError::ConcurrentDependencyDrop {
1196                dependency_kind: "replica",
1197                dependency_id: id.to_string(),
1198            },
1199            e @ (ReadHoldIdMismatch(_) | SinceViolation(_)) => {
1200                AdapterError::internal("peek error", e)
1201            }
1202        }
1203    }
1204
1205    pub fn concurrent_dependency_drop_from_dataflow_creation_error(
1206        e: compute_error::DataflowCreationError,
1207    ) -> Self {
1208        use compute_error::DataflowCreationError::*;
1209        match e {
1210            InstanceMissing(id) => AdapterError::ConcurrentDependencyDrop {
1211                dependency_kind: "cluster",
1212                dependency_id: id.to_string(),
1213            },
1214            CollectionMissing(id) => AdapterError::ConcurrentDependencyDrop {
1215                dependency_kind: "collection",
1216                dependency_id: id.to_string(),
1217            },
1218            ReplicaMissing(id) => AdapterError::ConcurrentDependencyDrop {
1219                dependency_kind: "replica",
1220                dependency_id: id.to_string(),
1221            },
1222            MissingAsOf | SinceViolation(..) | EmptyAsOfForSubscribe | EmptyAsOfForCopyTo => {
1223                AdapterError::internal("dataflow creation error", e)
1224            }
1225        }
1226    }
1227}
1228
1229impl fmt::Display for AdapterError {
1230    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1231        match self {
1232            AdapterError::AbsurdSubscribeBounds { as_of, up_to } => {
1233                write!(
1234                    f,
1235                    "subscription lower bound (`AS OF`) is greater than its upper bound (`UP TO`): \
1236                     {as_of} > {up_to}",
1237                )
1238            }
1239            AdapterError::AmbiguousSystemColumnReference => {
1240                write!(
1241                    f,
1242                    "cannot use wildcard expansions or NATURAL JOINs in a view that depends on \
1243                    system objects"
1244                )
1245            }
1246            AdapterError::ChangedPlan(e) => write!(f, "{}", e),
1247            AdapterError::Catalog(e) => e.fmt(f),
1248            AdapterError::DuplicateCursor(name) => {
1249                write!(f, "cursor {} already exists", name.quoted())
1250            }
1251            AdapterError::Eval(e) => e.fmt(f),
1252            AdapterError::Explain(e) => e.fmt(f),
1253            AdapterError::IdExhaustionError => f.write_str("ID allocator exhausted all valid IDs"),
1254            AdapterError::Internal(e) => write!(f, "internal error: {}", e),
1255            AdapterError::IntrospectionDisabled { .. } => write!(
1256                f,
1257                "cannot read log sources of replica with disabled introspection"
1258            ),
1259            AdapterError::InvalidLogDependency { object_type, .. } => {
1260                write!(f, "{object_type} objects cannot depend on log sources")
1261            }
1262            AdapterError::InvalidClusterReplicaAz { az, expected: _ } => {
1263                write!(f, "unknown cluster replica availability zone {az}",)
1264            }
1265            AdapterError::InvalidSetIsolationLevel => write!(
1266                f,
1267                "SET TRANSACTION ISOLATION LEVEL must be called before any query"
1268            ),
1269            AdapterError::InvalidSetCluster => {
1270                write!(f, "SET cluster cannot be called in an active transaction")
1271            }
1272            AdapterError::InvalidStorageClusterSize { size, .. } => {
1273                write!(f, "unknown source size {size}")
1274            }
1275            AdapterError::SourceOrSinkSizeRequired { .. } => {
1276                write!(f, "must specify either cluster or size option")
1277            }
1278            AdapterError::InvalidTableMutationSelection { .. } => {
1279                write!(
1280                    f,
1281                    "invalid selection: operation may only (transitively) refer to non-source, non-system tables"
1282                )
1283            }
1284            AdapterError::ReadThenWriteDependencyLimitExceeded {
1285                max_rw_dependencies,
1286            } => {
1287                write!(
1288                    f,
1289                    "selection has too many transitive dependencies to validate (limit {max_rw_dependencies})"
1290                )
1291            }
1292            AdapterError::ReplaceMaterializedViewSealed { name } => {
1293                write!(
1294                    f,
1295                    "materialized view {name} is sealed and thus cannot be replaced"
1296                )
1297            }
1298            AdapterError::ConstraintViolation(not_null_violation) => {
1299                write!(f, "{}", not_null_violation)
1300            }
1301            AdapterError::CopyFormatError(e) => write!(f, "{e}"),
1302            AdapterError::ConcurrentClusterDrop => {
1303                write!(f, "the transaction's active cluster has been dropped")
1304            }
1305            AdapterError::ConcurrentDependencyDrop {
1306                dependency_kind,
1307                dependency_id,
1308            } => {
1309                write!(f, "{dependency_kind} '{dependency_id}' was dropped")
1310            }
1311            AdapterError::ConcurrentDependencyMutation { dependency_id } => {
1312                write!(
1313                    f,
1314                    "catalog item '{dependency_id}' was concurrently modified"
1315                )
1316            }
1317            AdapterError::ReadThenWriteContention => {
1318                write!(
1319                    f,
1320                    "read-then-write exceeded maximum retry attempts under contention"
1321                )
1322            }
1323            AdapterError::ReadThenWriteTimestampTooFarAhead {
1324                target_timestamp,
1325                limit,
1326            } => {
1327                write!(
1328                    f,
1329                    "read-then-write would have to commit at {target_timestamp}, past the \
1330                     highest timestamp the write timeline may be advanced to ({limit})"
1331                )
1332            }
1333            AdapterError::CollectionUnreadable { id } => {
1334                write!(f, "collection '{id}' is not readable at any timestamp")
1335            }
1336            AdapterError::NoClusterReplicasAvailable { name, .. } => {
1337                write!(
1338                    f,
1339                    "CLUSTER {} has no replicas available to service request",
1340                    name.quoted()
1341                )
1342            }
1343            AdapterError::OperationProhibitsTransaction(op) => {
1344                write!(f, "{} cannot be run inside a transaction block", op)
1345            }
1346            AdapterError::OperationRequiresTransaction(op) => {
1347                write!(f, "{} can only be used in transaction blocks", op)
1348            }
1349            AdapterError::ParseError(e) => e.fmt(f),
1350            AdapterError::PlanError(e) => e.fmt(f),
1351            AdapterError::PreparedStatementExists(name) => {
1352                write!(f, "prepared statement {} already exists", name.quoted())
1353            }
1354            AdapterError::ReadOnlyTransaction => f.write_str("transaction in read-only mode"),
1355            AdapterError::SingleStatementTransaction => {
1356                f.write_str("this transaction can only execute a single statement")
1357            }
1358            AdapterError::ReadWriteUnavailable => {
1359                f.write_str("transaction read-write mode must be set before any query")
1360            }
1361            AdapterError::WrongSetOfLocks => {
1362                write!(f, "internal error, wrong set of locks acquired")
1363            }
1364            AdapterError::StatementTimeout => {
1365                write!(f, "canceling statement due to statement timeout")
1366            }
1367            AdapterError::Canceled => {
1368                write!(f, "canceling statement due to user request")
1369            }
1370            AdapterError::IdleInTransactionSessionTimeout => {
1371                write!(
1372                    f,
1373                    "terminating connection due to idle-in-transaction timeout"
1374                )
1375            }
1376            AdapterError::RecursionLimit(e) => e.fmt(f),
1377            AdapterError::RelationOutsideTimeDomain { .. } => {
1378                write!(
1379                    f,
1380                    "Transactions can only reference objects in the same timedomain. \
1381                     See https://materialize.com/docs/sql/begin/#same-timedomain-error",
1382                )
1383            }
1384            AdapterError::ResourceExhaustion {
1385                resource_type,
1386                limit_name,
1387                desired,
1388                limit,
1389                current,
1390            } => {
1391                write!(
1392                    f,
1393                    "creating {resource_type} would violate {limit_name} limit (desired: {desired}, limit: {limit}, current: {current})"
1394                )
1395            }
1396            AdapterError::ResultSize(e) => write!(f, "{e}"),
1397            AdapterError::SubscribeFellBehind {
1398                buffered_bytes,
1399                max_buffered_bytes,
1400            } => {
1401                use bytesize::ByteSize;
1402                let buffered = u64::cast_from(*buffered_bytes);
1403                let max = u64::cast_from(*max_buffered_bytes);
1404                write!(
1405                    f,
1406                    "SUBSCRIBE fell behind: the client did not read results fast enough, so its backlog reached {}, exceeding the {} budget",
1407                    ByteSize::b(buffered),
1408                    ByteSize::b(max),
1409                )
1410            }
1411            AdapterError::SafeModeViolation(feature) => {
1412                write!(f, "cannot create {} in safe mode", feature)
1413            }
1414            AdapterError::SubscribeOnlyTransaction => {
1415                f.write_str("SUBSCRIBE in transactions must be the only read statement")
1416            }
1417            AdapterError::Optimizer(e) => e.fmt(f),
1418            AdapterError::UnallowedOnCluster {
1419                depends_on,
1420                cluster,
1421            } => {
1422                let items = depends_on.into_iter().map(|item| item.quoted()).join(", ");
1423                write!(
1424                    f,
1425                    "querying the following items {items} is not allowed from the {} cluster",
1426                    cluster.quoted()
1427                )
1428            }
1429            AdapterError::Unauthorized(unauthorized) => {
1430                write!(f, "{unauthorized}")
1431            }
1432            AdapterError::UnknownCursor(name) => {
1433                write!(f, "cursor {} does not exist", name.quoted())
1434            }
1435            AdapterError::UnknownLoginRole(name) => {
1436                write!(f, "role {} does not exist", name.quoted())
1437            }
1438            AdapterError::Unsupported(features) => write!(f, "{} are not supported", features),
1439            AdapterError::Unstructured(e) => write!(f, "{}", e.display_with_causes()),
1440            AdapterError::WriteOnlyTransaction => f.write_str("transaction in write-only mode"),
1441            AdapterError::UnknownPreparedStatement(name) => {
1442                write!(f, "prepared statement {} does not exist", name.quoted())
1443            }
1444            AdapterError::UnknownClusterReplica {
1445                cluster_name,
1446                replica_name,
1447            } => write!(
1448                f,
1449                "cluster replica '{cluster_name}.{replica_name}' does not exist"
1450            ),
1451            AdapterError::UnrecognizedConfigurationParam(setting_name) => write!(
1452                f,
1453                "unrecognized configuration parameter {}",
1454                setting_name.quoted()
1455            ),
1456            AdapterError::UntargetedLogRead { .. } => {
1457                f.write_str("log source reads must target a replica")
1458            }
1459            AdapterError::DDLOnlyTransaction => f.write_str(
1460                "transactions which modify objects are restricted to just modifying objects",
1461            ),
1462            AdapterError::DDLTransactionRace => f.write_str(
1463                "another session modified the catalog while this DDL transaction was open",
1464            ),
1465            AdapterError::ClusterStateChanged { cluster_id } => {
1466                write!(f, "cluster {cluster_id} was concurrently modified")
1467            }
1468            AdapterError::Storage(e) => e.fmt(f),
1469            AdapterError::Compute(e) => e.fmt(f),
1470            AdapterError::Orchestrator(e) => e.fmt(f),
1471            AdapterError::DependentObject(dependent_objects) => {
1472                let role_str = if dependent_objects.keys().count() == 1 {
1473                    "role"
1474                } else {
1475                    "roles"
1476                };
1477                write!(
1478                    f,
1479                    "{role_str} \"{}\" cannot be dropped because some objects depend on it",
1480                    dependent_objects.keys().join(", ")
1481                )
1482            }
1483            AdapterError::InvalidAlter(t, e) => {
1484                write!(f, "invalid ALTER {t}: {e}")
1485            }
1486            AdapterError::ConnectionValidation(e) => e.fmt(f),
1487            AdapterError::MaterializedViewWouldNeverRefresh(_, _) => {
1488                write!(
1489                    f,
1490                    "all the specified refreshes of the materialized view would be too far in the past, and thus they \
1491                    would never happen"
1492                )
1493            }
1494            AdapterError::InputNotReadableAtRefreshAtTime(_, _) => {
1495                write!(
1496                    f,
1497                    "REFRESH AT requested for a time where not all the inputs are readable"
1498                )
1499            }
1500            AdapterError::RtrTimeout(_) => {
1501                write!(
1502                    f,
1503                    "timed out before ingesting the source's visible frontier when real-time-recency query issued"
1504                )
1505            }
1506            AdapterError::RtrDropFailure(_) => write!(
1507                f,
1508                "real-time source dropped before ingesting the upstream system's visible frontier"
1509            ),
1510            AdapterError::UnreadableSinkCollection => {
1511                write!(f, "collection is not readable at any time")
1512            }
1513            AdapterError::UserSessionsDisallowed => write!(f, "login blocked"),
1514            AdapterError::NetworkPolicyDenied(_) => write!(f, "session denied"),
1515            AdapterError::ReadOnly => write!(f, "cannot write in read-only mode"),
1516            AdapterError::AlterClusterTimeout => {
1517                write!(f, "canceling statement, provided timeout lapsed")
1518            }
1519            AdapterError::AuthenticationError(e) => {
1520                write!(f, "authentication error {e}")
1521            }
1522            AdapterError::UnavailableFeature { feature, docs } => {
1523                write!(f, "{} is not supported in this environment.", feature)?;
1524                if let Some(docs) = docs {
1525                    write!(
1526                        f,
1527                        " For more information consult the documentation at {docs}"
1528                    )?;
1529                }
1530                Ok(())
1531            }
1532            AdapterError::AlterClusterWhilePendingReplicas => {
1533                write!(f, "cannot alter clusters with pending updates")
1534            }
1535            AdapterError::AlterClusterUnmanagedWhileReconfiguring => {
1536                write!(
1537                    f,
1538                    "cannot convert cluster to unmanaged while a reconfiguration is in progress"
1539                )
1540            }
1541            AdapterError::AlterClusterUnmanagedWhileBursting => {
1542                write!(
1543                    f,
1544                    "cannot convert cluster to unmanaged while a hydration burst is in progress"
1545                )
1546            }
1547            AdapterError::AlterClusterReplicationFactorWhileReconfiguring => {
1548                write!(
1549                    f,
1550                    "cannot change replication factor while a reconfiguration is in progress"
1551                )
1552            }
1553            AdapterError::AlterClusterScheduleWhileReconfiguring => {
1554                write!(
1555                    f,
1556                    "cannot change the cluster schedule while a reconfiguration is in progress"
1557                )
1558            }
1559            AdapterError::AlterClusterWaitOnScheduledCluster => {
1560                write!(
1561                    f,
1562                    "WAIT is not supported when the cluster SCHEDULE is not MANUAL"
1563                )
1564            }
1565            AdapterError::ReplacementSchemaMismatch(_) => {
1566                write!(f, "replacement schema differs from target schema")
1567            }
1568            AdapterError::ImpossibleTimestampConstraints { .. } => {
1569                write!(f, "could not find a valid timestamp for the query")
1570            }
1571            AdapterError::OidcGroupSyncFailed(msg) => {
1572                write!(f, "OIDC group-to-role sync failed: {msg}")
1573            }
1574            AdapterError::BoundedStalenessExceeded { bound, .. } => {
1575                write!(
1576                    f,
1577                    "cannot serve query under bounded staleness {}",
1578                    humantime::format_duration(*bound),
1579                )
1580            }
1581            AdapterError::BoundedStalenessReadOnly => {
1582                f.write_str("writes are not permitted under bounded staleness isolation")
1583            }
1584            AdapterError::BoundedStalenessRealTimeRecencyConflict => {
1585                f.write_str("real_time_recency cannot be combined with bounded staleness isolation")
1586            }
1587            AdapterError::BoundedStalenessTimelineUnsupported => {
1588                f.write_str("bounded staleness isolation requires the EpochMilliseconds timeline")
1589            }
1590        }
1591    }
1592}
1593
1594impl From<anyhow::Error> for AdapterError {
1595    fn from(e: anyhow::Error) -> AdapterError {
1596        match e.downcast::<PlanError>() {
1597            Ok(plan_error) => AdapterError::PlanError(plan_error),
1598            Err(e) => AdapterError::Unstructured(e),
1599        }
1600    }
1601}
1602
1603impl From<TryFromIntError> for AdapterError {
1604    fn from(e: TryFromIntError) -> AdapterError {
1605        AdapterError::Unstructured(e.into())
1606    }
1607}
1608
1609impl From<TryFromDecimalError> for AdapterError {
1610    fn from(e: TryFromDecimalError) -> AdapterError {
1611        AdapterError::Unstructured(e.into())
1612    }
1613}
1614
1615impl From<mz_catalog::memory::error::Error> for AdapterError {
1616    fn from(e: mz_catalog::memory::error::Error) -> AdapterError {
1617        AdapterError::Catalog(e)
1618    }
1619}
1620
1621impl From<mz_catalog::durable::CatalogError> for AdapterError {
1622    fn from(e: mz_catalog::durable::CatalogError) -> Self {
1623        mz_catalog::memory::error::Error::from(e).into()
1624    }
1625}
1626
1627impl From<mz_catalog::durable::DurableCatalogError> for AdapterError {
1628    fn from(e: mz_catalog::durable::DurableCatalogError) -> Self {
1629        mz_catalog::durable::CatalogError::from(e).into()
1630    }
1631}
1632
1633impl From<EvalError> for AdapterError {
1634    fn from(e: EvalError) -> AdapterError {
1635        AdapterError::Eval(e)
1636    }
1637}
1638
1639impl From<ExplainError> for AdapterError {
1640    fn from(e: ExplainError) -> AdapterError {
1641        match e {
1642            ExplainError::RecursionLimitError(e) => AdapterError::RecursionLimit(e),
1643            e => AdapterError::Explain(e),
1644        }
1645    }
1646}
1647
1648impl From<mz_sql::catalog::CatalogError> for AdapterError {
1649    fn from(e: mz_sql::catalog::CatalogError) -> AdapterError {
1650        AdapterError::Catalog(mz_catalog::memory::error::Error::from(e))
1651    }
1652}
1653
1654impl From<PlanError> for AdapterError {
1655    fn from(e: PlanError) -> AdapterError {
1656        match e {
1657            PlanError::UnknownCursor(name) => AdapterError::UnknownCursor(name),
1658            _ => AdapterError::PlanError(e),
1659        }
1660    }
1661}
1662
1663impl From<OptimizerError> for AdapterError {
1664    fn from(e: OptimizerError) -> AdapterError {
1665        use OptimizerError::*;
1666        match e {
1667            PlanError(e) => Self::PlanError(e),
1668            RecursionLimitError(e) => Self::RecursionLimit(e),
1669            EvalError(e) => Self::Eval(e),
1670            InternalUnsafeMfpPlan(e) => Self::Internal(e),
1671            Internal(e) => Self::Internal(e),
1672            RestrictedFunction(func) => {
1673                Self::Unauthorized(mz_sql::rbac::UnauthorizedError::RestrictedSystemObject {
1674                    object_name: format!("function {func}"),
1675                })
1676            }
1677            e => Self::Optimizer(e),
1678        }
1679    }
1680}
1681
1682impl From<NotNullViolation> for AdapterError {
1683    fn from(e: NotNullViolation) -> AdapterError {
1684        AdapterError::ConstraintViolation(e)
1685    }
1686}
1687
1688impl From<RecursionLimitError> for AdapterError {
1689    fn from(e: RecursionLimitError) -> AdapterError {
1690        AdapterError::RecursionLimit(e)
1691    }
1692}
1693
1694impl From<oneshot::error::RecvError> for AdapterError {
1695    fn from(e: oneshot::error::RecvError) -> AdapterError {
1696        AdapterError::Unstructured(e.into())
1697    }
1698}
1699
1700impl From<StorageError> for AdapterError {
1701    fn from(e: StorageError) -> Self {
1702        AdapterError::Storage(e)
1703    }
1704}
1705
1706impl From<compute_error::InstanceExists> for AdapterError {
1707    fn from(e: compute_error::InstanceExists) -> Self {
1708        AdapterError::Compute(e.into())
1709    }
1710}
1711
1712impl From<TimestampError> for AdapterError {
1713    fn from(e: TimestampError) -> Self {
1714        let e: EvalError = e.into();
1715        e.into()
1716    }
1717}
1718
1719impl From<mz_sql_parser::parser::ParserStatementError> for AdapterError {
1720    fn from(e: mz_sql_parser::parser::ParserStatementError) -> Self {
1721        AdapterError::ParseError(e)
1722    }
1723}
1724
1725impl From<VarError> for AdapterError {
1726    fn from(e: VarError) -> Self {
1727        let e: mz_catalog::memory::error::Error = e.into();
1728        e.into()
1729    }
1730}
1731
1732impl From<rbac::UnauthorizedError> for AdapterError {
1733    fn from(e: rbac::UnauthorizedError) -> Self {
1734        AdapterError::Unauthorized(e)
1735    }
1736}
1737
1738impl From<mz_sql_parser::ast::IdentError> for AdapterError {
1739    fn from(value: mz_sql_parser::ast::IdentError) -> Self {
1740        AdapterError::PlanError(PlanError::InvalidIdent(value))
1741    }
1742}
1743
1744impl From<mz_pgwire_common::ConnectionError> for AdapterError {
1745    fn from(value: mz_pgwire_common::ConnectionError) -> Self {
1746        match value {
1747            mz_pgwire_common::ConnectionError::TooManyConnections { current, limit } => {
1748                AdapterError::ResourceExhaustion {
1749                    resource_type: "connection".into(),
1750                    limit_name: "max_connections".into(),
1751                    desired: (current + 1).to_string(),
1752                    limit: limit.to_string(),
1753                    current: current.to_string(),
1754                }
1755            }
1756        }
1757    }
1758}
1759
1760impl From<NetworkPolicyError> for AdapterError {
1761    fn from(value: NetworkPolicyError) -> Self {
1762        AdapterError::NetworkPolicyDenied(value)
1763    }
1764}
1765
1766impl From<ConnectionValidationError> for AdapterError {
1767    fn from(e: ConnectionValidationError) -> AdapterError {
1768        AdapterError::ConnectionValidation(e)
1769    }
1770}
1771
1772impl Error for AdapterError {}