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