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