Skip to main content

mz_sql/plan/
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::BTreeSet;
11use std::error::Error;
12use std::num::{ParseIntError, TryFromIntError};
13use std::sync::Arc;
14use std::time::Duration;
15use std::{fmt, io};
16
17use itertools::Itertools;
18use mz_expr::EvalError;
19use mz_mysql_util::MySqlError;
20use mz_ore::error::ErrorExt;
21use mz_ore::stack::RecursionLimitError;
22use mz_ore::str::{StrExt, separated};
23use mz_postgres_util::PostgresError;
24use mz_repr::adt::char::InvalidCharLengthError;
25use mz_repr::adt::mz_acl_item::AclMode;
26use mz_repr::adt::numeric::InvalidNumericMaxScaleError;
27use mz_repr::adt::timestamp::InvalidTimestampPrecisionError;
28use mz_repr::adt::varchar::InvalidVarCharMaxLengthError;
29use mz_repr::{CatalogItemId, ColumnName, strconv};
30use mz_sql_parser::ast::display::AstDisplay;
31use mz_sql_parser::ast::{IdentError, UnresolvedItemName};
32use mz_sql_parser::parser::{ParserError, ParserStatementError};
33use mz_sql_server_util::SqlServerError;
34use mz_storage_types::sources::ExternalReferenceResolutionError;
35
36use crate::catalog::{
37    CatalogError, CatalogItemType, ErrorMessageObjectDescription, SystemObjectType,
38};
39use crate::names::{PartialItemName, ResolvedItemName};
40use crate::plan::ObjectType;
41use crate::plan::plan_utils::JoinSide;
42use crate::plan::scope::ScopeItem;
43use crate::plan::typeconv::CastContext;
44use crate::pure::error::{
45    CsrPurificationError, GluePurificationError, IcebergSinkPurificationError,
46    KafkaSinkPurificationError, KafkaSourcePurificationError, LoadGeneratorSourcePurificationError,
47    MySqlSourcePurificationError, PgSourcePurificationError, SqlServerSourcePurificationError,
48};
49use crate::session::vars::VarError;
50
51#[derive(Debug)]
52pub enum PlanError {
53    /// This feature is not yet supported, but may be supported at some point in the future.
54    Unsupported {
55        feature: String,
56        discussion_no: Option<usize>,
57    },
58    /// A burst `HYDRATION SIZE` equal to the cluster `SIZE` (a no-op burst).
59    HydrationSizeEqualsClusterSize {
60        size: String,
61    },
62    /// This feature is not supported, and will likely never be supported.
63    NeverSupported {
64        feature: String,
65        documentation_link: Option<String>,
66        details: Option<String>,
67    },
68    UnknownColumn {
69        table: Option<PartialItemName>,
70        column: ColumnName,
71        similar: Box<[ColumnName]>,
72    },
73    UngroupedColumn {
74        table: Option<PartialItemName>,
75        column: ColumnName,
76    },
77    ItemWithoutColumns {
78        name: String,
79        item_type: CatalogItemType,
80    },
81    WrongJoinTypeForLateralColumn {
82        table: Option<PartialItemName>,
83        column: ColumnName,
84    },
85    AmbiguousColumn(ColumnName),
86    TooManyColumns {
87        max_num_columns: usize,
88        req_num_columns: usize,
89    },
90    ColumnAlreadyExists {
91        column_name: ColumnName,
92        object_name: String,
93    },
94    AmbiguousTable(PartialItemName),
95    UnknownColumnInUsingClause {
96        column: ColumnName,
97        join_side: JoinSide,
98    },
99    AmbiguousColumnInUsingClause {
100        column: ColumnName,
101        join_side: JoinSide,
102    },
103    MisqualifiedName(String),
104    OverqualifiedDatabaseName(String),
105    OverqualifiedSchemaName(String),
106    UnderqualifiedColumnName(String),
107    SubqueriesDisallowed {
108        context: String,
109    },
110    UnknownParameter(usize),
111    ParameterNotAllowed(String),
112    WrongParameterType(usize, String, String),
113    RecursionLimit(RecursionLimitError),
114    StrconvParse(strconv::ParseError),
115    Catalog(CatalogError),
116    UpsertSinkWithoutKey,
117    UpsertSinkWithInvalidKey {
118        name: String,
119        desired_key: Vec<String>,
120        valid_keys: Vec<Vec<String>>,
121    },
122    IcebergSinkUnsupportedKeyType {
123        column: String,
124        column_type: String,
125    },
126    InvalidWmrRecursionLimit(String),
127    InvalidNumericMaxScale(InvalidNumericMaxScaleError),
128    InvalidCharLength(InvalidCharLengthError),
129    InvalidId(CatalogItemId),
130    InvalidIdent(IdentError),
131    InvalidObject(Box<ResolvedItemName>),
132    InvalidObjectType {
133        expected_type: SystemObjectType,
134        actual_type: SystemObjectType,
135        object_name: String,
136    },
137    InvalidPrivilegeTypes {
138        invalid_privileges: AclMode,
139        object_description: ErrorMessageObjectDescription,
140    },
141    InvalidVarCharMaxLength(InvalidVarCharMaxLengthError),
142    InvalidTimestampPrecision(InvalidTimestampPrecisionError),
143    InvalidSecret(Box<ResolvedItemName>),
144    InvalidTemporarySchema,
145    InvalidCast {
146        name: String,
147        ccx: CastContext,
148        from: String,
149        to: String,
150    },
151    /// Range type with an element type that is not supported (e.g. float, uint).
152    UnsupportedRangeElementType {
153        element_type_name: String,
154    },
155    InvalidTable {
156        name: String,
157    },
158    InvalidVersion {
159        name: String,
160        version: String,
161    },
162    InvalidSinkFrom {
163        name: String,
164        item_type: String,
165    },
166    InvalidDependency {
167        name: String,
168        item_type: String,
169    },
170    MangedReplicaName(String),
171    ParserStatement(ParserStatementError),
172    Parser(ParserError),
173    DropViewOnMaterializedView(String),
174    DependentObjectsStillExist {
175        object_type: String,
176        object_name: String,
177        // (dependent type, name)
178        dependents: Vec<(String, String)>,
179    },
180    AlterViewOnMaterializedView(String),
181    ShowCreateViewOnMaterializedView(String),
182    ExplainViewOnMaterializedView(String),
183    UnacceptableTimelineName(String),
184    FetchingCsrSchemaFailed {
185        schema_lookup: String,
186        cause: Arc<dyn Error + Send + Sync>,
187    },
188    PostgresConnectionErr {
189        cause: Arc<mz_postgres_util::PostgresError>,
190    },
191    MySqlConnectionErr {
192        cause: Arc<MySqlError>,
193    },
194    SqlServerConnectionErr {
195        cause: Arc<SqlServerError>,
196    },
197    SubsourceNameConflict {
198        name: UnresolvedItemName,
199        upstream_references: Vec<UnresolvedItemName>,
200    },
201    SubsourceDuplicateReference {
202        name: UnresolvedItemName,
203        target_names: Vec<UnresolvedItemName>,
204    },
205    NoTablesFoundForSchemas(Vec<String>),
206    InvalidProtobufSchema {
207        cause: protobuf_native::OperationFailedError,
208    },
209    InvalidOptionValue {
210        // Expected to be generated from the `to_ast_string` value on the option
211        // name.
212        option_name: String,
213        err: Box<PlanError>,
214    },
215    UnexpectedDuplicateReference {
216        name: UnresolvedItemName,
217    },
218    /// Declaration of a recursive type did not match the inferred type.
219    RecursiveTypeMismatch(String, Vec<String>, Vec<String>),
220    UnknownFunction {
221        name: String,
222        arg_types: Vec<String>,
223    },
224    IndistinctFunction {
225        name: String,
226        arg_types: Vec<String>,
227    },
228    UnknownOperator {
229        name: String,
230        arg_types: Vec<String>,
231    },
232    IndistinctOperator {
233        name: String,
234        arg_types: Vec<String>,
235    },
236    InvalidPrivatelinkAvailabilityZone {
237        name: String,
238        supported_azs: BTreeSet<String>,
239    },
240    DuplicatePrivatelinkAvailabilityZone {
241        duplicate_azs: BTreeSet<String>,
242    },
243    InvalidSchemaName,
244    ItemAlreadyExists {
245        name: String,
246        item_type: CatalogItemType,
247    },
248    ManagedCluster {
249        cluster_name: String,
250    },
251    InvalidKeysInSubscribeEnvelopeUpsert,
252    InvalidKeysInSubscribeEnvelopeDebezium,
253    DuplicateKeyColumnInSubscribeEnvelope {
254        column_name: String,
255    },
256    InvalidPartitionByEnvelopeDebezium {
257        column_name: String,
258    },
259    InvalidOrderByInSubscribeWithinTimestampOrderBy,
260    FromValueRequiresParen,
261    VarError(VarError),
262    UnsolvablePolymorphicFunctionInput,
263    ShowCommandInView,
264    WebhookValidationDoesNotUseColumns,
265    WebhookValidationNonDeterministic,
266    InternalFunctionCall,
267    CommentTooLong {
268        length: usize,
269        max_size: usize,
270    },
271    InvalidTimestampInterval {
272        min: Duration,
273        max: Duration,
274        requested: Duration,
275    },
276    InvalidGroupSizeHints,
277    PgSourcePurification(PgSourcePurificationError),
278    KafkaSourcePurification(KafkaSourcePurificationError),
279    KafkaSinkPurification(KafkaSinkPurificationError),
280    IcebergSinkPurification(IcebergSinkPurificationError),
281    LoadGeneratorSourcePurification(LoadGeneratorSourcePurificationError),
282    CsrPurification(CsrPurificationError),
283    GluePurification(GluePurificationError),
284    MySqlSourcePurification(MySqlSourcePurificationError),
285    SqlServerSourcePurificationError(SqlServerSourcePurificationError),
286    UseTablesForSources(String),
287    MissingName(CatalogItemType),
288    InvalidRefreshAt,
289    InvalidRefreshEveryAlignedTo,
290    MismatchedObjectType {
291        name: PartialItemName,
292        is_type: ObjectType,
293        expected_type: ObjectType,
294    },
295    /// MZ failed to generate cast for the data type.
296    TableContainsUningestableTypes {
297        name: String,
298        type_: String,
299        column: String,
300    },
301    RetainHistoryLow {
302        limit: Duration,
303    },
304    RetainHistoryRequired,
305    UntilReadyTimeoutRequired,
306    SubsourceResolutionError(ExternalReferenceResolutionError),
307    Replan(String),
308    Internal(String),
309    NetworkPolicyLockoutError,
310    NetworkPolicyInUse,
311    /// Expected a constant expression that evaluates without an error to a non-null value.
312    ConstantExpressionSimplificationFailed(String),
313    InvalidLimit(String),
314    InvalidOffset(String),
315    /// The named cursor does not exist.
316    UnknownCursor(String),
317    CopyFromTargetTableDropped {
318        target_name: String,
319    },
320    /// AS OF or UP TO should be an expression that is castable and simplifiable to a non-null mz_timestamp value.
321    InvalidAsOfUpTo,
322    InvalidReplacement {
323        item_type: CatalogItemType,
324        item_name: PartialItemName,
325        replacement_type: CatalogItemType,
326        replacement_name: PartialItemName,
327    },
328    // TODO(benesch): eventually all errors should be structured.
329    Unstructured(String),
330}
331
332impl PlanError {
333    pub(crate) fn ungrouped_column(item: &ScopeItem) -> PlanError {
334        PlanError::UngroupedColumn {
335            table: item.table_name.clone(),
336            column: item.column_name.clone(),
337        }
338    }
339
340    pub fn detail(&self) -> Option<String> {
341        match self {
342            Self::HydrationSizeEqualsClusterSize { .. } => Some(
343                "A burst replica at the same size as the steady replicas would not \
344                 accelerate hydration."
345                    .into(),
346            ),
347            Self::NeverSupported { details, .. } => details.clone(),
348            Self::FetchingCsrSchemaFailed { cause, .. } => Some(cause.to_string_with_causes()),
349            Self::PostgresConnectionErr { cause } => Some(cause.to_string_with_causes()),
350            Self::InvalidProtobufSchema { cause } => Some(cause.to_string_with_causes()),
351            Self::InvalidOptionValue { err, .. } => err.detail(),
352            Self::UpsertSinkWithInvalidKey {
353                name,
354                desired_key,
355                valid_keys,
356            } => {
357                let valid_keys = if valid_keys.is_empty() {
358                    "There are no known valid unique keys for the underlying relation.".into()
359                } else {
360                    format!(
361                        "The following keys are known to be unique for the underlying relation:\n{}",
362                        valid_keys
363                            .iter()
364                            .map(|k|
365                                format!("  ({})", k.iter().map(|c| c.as_str().quoted()).join(", "))
366                            )
367                            .join("\n"),
368                    )
369                };
370                Some(format!(
371                    "Materialize could not prove that the specified upsert envelope key ({}) \
372                    was a unique key of the underlying relation {}. {valid_keys}",
373                    separated(", ", desired_key.iter().map(|c| c.as_str().quoted())),
374                    name.quoted()
375                ))
376            }
377            Self::VarError(e) => e.detail(),
378            Self::InternalFunctionCall => Some("This function is for the internal use of the database system and cannot be called directly.".into()),
379            Self::PgSourcePurification(e) => e.detail(),
380            Self::MySqlSourcePurification(e) => e.detail(),
381            Self::SqlServerSourcePurificationError(e) => e.detail(),
382            Self::KafkaSourcePurification(e) => e.detail(),
383            Self::LoadGeneratorSourcePurification(e) => e.detail(),
384            Self::CsrPurification(e) => e.detail(),
385            Self::GluePurification(e) => e.detail(),
386            Self::KafkaSinkPurification(e) => e.detail(),
387            Self::IcebergSinkPurification(e) => e.detail(),
388            Self::SubsourceNameConflict {
389                name: _,
390                upstream_references,
391            } => Some(format!(
392                "referenced tables with duplicate name: {}",
393                itertools::join(upstream_references, ", ")
394            )),
395            Self::SubsourceDuplicateReference {
396                name: _,
397                target_names,
398            } => Some(format!(
399                "subsources referencing table: {}",
400                itertools::join(target_names, ", ")
401            )),
402            Self::InvalidPartitionByEnvelopeDebezium { .. } => Some(
403                "When using ENVELOPE DEBEZIUM, only columns in the key can be referenced in the PARTITION BY expression.".to_string()
404            ),
405            Self::NoTablesFoundForSchemas(schemas) => Some(format!(
406                "missing schemas: {}",
407                separated(", ", schemas.iter().map(|c| c.quoted()))
408            )),
409            _ => None,
410        }
411    }
412
413    pub fn hint(&self) -> Option<String> {
414        match self {
415            Self::DropViewOnMaterializedView(_) => {
416                Some("Use DROP MATERIALIZED VIEW to remove a materialized view.".into())
417            }
418            Self::DependentObjectsStillExist {..} => Some("Use DROP ... CASCADE to drop the dependent objects too.".into()),
419            Self::AlterViewOnMaterializedView(_) => {
420                Some("Use ALTER MATERIALIZED VIEW to rename a materialized view.".into())
421            }
422            Self::ShowCreateViewOnMaterializedView(_) => {
423                Some("Use SHOW CREATE MATERIALIZED VIEW to show a materialized view.".into())
424            }
425            Self::ExplainViewOnMaterializedView(_) => {
426                Some("Use EXPLAIN [...] MATERIALIZED VIEW to explain a materialized view.".into())
427            }
428            Self::UnacceptableTimelineName(_) => {
429                Some("The prefix \"mz_\" is reserved for system timelines.".into())
430            }
431            Self::PostgresConnectionErr { cause } => {
432                if let Some(cause) = cause.source() {
433                    if let Some(cause) = cause.downcast_ref::<io::Error>() {
434                        if cause.kind() == io::ErrorKind::TimedOut {
435                            return Some(
436                                "Do you have a firewall or security group that is \
437                                preventing Materialize from connecting to your PostgreSQL server?"
438                                    .into(),
439                            );
440                        }
441                    }
442                }
443                None
444            }
445            Self::InvalidOptionValue { err, .. } => err.hint(),
446            Self::UnknownFunction { ..} => Some("No function matches the given name and argument types.  You might need to add explicit type casts.".into()),
447            Self::IndistinctFunction {..} => {
448                Some("Could not choose a best candidate function.  You might need to add explicit type casts.".into())
449            }
450            Self::UnknownOperator {..} => {
451                Some("No operator matches the given name and argument types.  You might need to add explicit type casts.".into())
452            }
453            Self::IndistinctOperator {..} => {
454                Some("Could not choose a best candidate operator.  You might need to add explicit type casts.".into())
455            },
456            Self::InvalidPrivatelinkAvailabilityZone { supported_azs, ..} => {
457                let supported_azs_str = supported_azs.iter().join("\n  ");
458                Some(format!("Did you supply an availability zone name instead of an ID? Known availability zone IDs:\n  {}", supported_azs_str))
459            }
460            Self::DuplicatePrivatelinkAvailabilityZone { duplicate_azs, ..} => {
461                let duplicate_azs  = duplicate_azs.iter().join("\n  ");
462                Some(format!("Duplicated availability zones:\n  {}", duplicate_azs))
463            }
464            Self::InvalidKeysInSubscribeEnvelopeUpsert => {
465                Some("All keys must be columns on the underlying relation.".into())
466            }
467            Self::InvalidKeysInSubscribeEnvelopeDebezium => {
468                Some("All keys must be columns on the underlying relation.".into())
469            }
470            Self::DuplicateKeyColumnInSubscribeEnvelope { .. } => {
471                Some("Each KEY column must be listed at most once.".into())
472            }
473            Self::InvalidOrderByInSubscribeWithinTimestampOrderBy => {
474                Some("All order bys must be output columns.".into())
475            }
476            Self::UpsertSinkWithInvalidKey { .. } | Self::UpsertSinkWithoutKey => {
477                Some("See: https://materialize.com/s/sink-key-selection".into())
478            }
479            Self::IcebergSinkUnsupportedKeyType { .. } => {
480                Some("Iceberg equality delete keys must be primitive, non-floating-point columns.".into())
481            }
482            Self::Catalog(e) => e.hint(),
483            Self::VarError(e) => e.hint(),
484            Self::PgSourcePurification(e) => e.hint(),
485            Self::MySqlSourcePurification(e) => e.hint(),
486            Self::SqlServerSourcePurificationError(e) => e.hint(),
487            Self::KafkaSourcePurification(e) => e.hint(),
488            Self::LoadGeneratorSourcePurification(e) => e.hint(),
489            Self::CsrPurification(e) => e.hint(),
490            Self::GluePurification(e) => e.hint(),
491            Self::KafkaSinkPurification(e) => e.hint(),
492            Self::UnknownColumn { table, similar, .. } => {
493                let suffix = "Make sure to surround case sensitive names in double quotes.";
494                match &similar[..] {
495                    [] => None,
496                    [column] => Some(format!("The similarly named column {} does exist. {suffix}", ColumnDisplay { table, column })),
497                    names => {
498                        let similar = names.into_iter().map(|column| ColumnDisplay { table, column }).join(", ");
499                        Some(format!("There are similarly named columns that do exist: {similar}. {suffix}"))
500                    }
501                }
502            }
503            Self::RecursiveTypeMismatch(..) => {
504                Some("You will need to rewrite or cast the query's expressions.".into())
505            },
506            Self::InvalidRefreshAt
507            | Self::InvalidRefreshEveryAlignedTo => {
508                Some("Calling `mz_now()` is allowed.".into())
509            },
510            Self::TableContainsUningestableTypes { column,.. } => {
511                Some(format!("Remove the table or use TEXT COLUMNS ({column}, ..) to ingest this column as text"))
512            }
513            Self::RetainHistoryLow { .. } | Self::RetainHistoryRequired => {
514                Some("Use ALTER ... RESET (RETAIN HISTORY) to set the retain history to its default and lowest value.".into())
515            }
516            Self::NetworkPolicyInUse => {
517                Some("Use ALTER SYSTEM SET 'network_policy' to change the default network policy.".into())
518            }
519            Self::WrongParameterType(_, _, _) => {
520                Some("EXECUTE automatically inserts only such casts that are allowed in an assignment cast context.  Try adding an explicit cast.".into())
521            }
522            Self::InvalidSchemaName => {
523                Some("Use SET schema = name to select a schema.  Use SHOW SCHEMAS to list available schemas.  Use SHOW search_path to show the schema names that we looked for, but none of them existed.".into())
524            }
525            _ => None,
526        }
527    }
528}
529
530impl fmt::Display for PlanError {
531    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
532        match self {
533            Self::Unsupported { feature, discussion_no } => {
534                write!(f, "{} not yet supported", feature)?;
535                if let Some(discussion_no) = discussion_no {
536                    write!(f, ", see https://github.com/MaterializeInc/materialize/discussions/{} for more details", discussion_no)?;
537                }
538                Ok(())
539            }
540            Self::HydrationSizeEqualsClusterSize { size } => {
541                write!(f, "HYDRATION SIZE must differ from the cluster SIZE ('{size}')")
542            }
543            Self::NeverSupported { feature, documentation_link: documentation_path,.. } => {
544                write!(f, "{feature} is not supported",)?;
545                if let Some(documentation_path) = documentation_path {
546                    write!(f, ", for more information consult the documentation at https://materialize.com/docs/{documentation_path}")?;
547                }
548                Ok(())
549            }
550            Self::UnknownColumn { table, column, similar: _ } => write!(
551                f,
552                "column {} does not exist",
553                ColumnDisplay { table, column }
554            ),
555            Self::UngroupedColumn { table, column } => write!(
556                f,
557                "column {} must appear in the GROUP BY clause or be used in an aggregate function",
558                ColumnDisplay { table, column },
559            ),
560            Self::ItemWithoutColumns { name, item_type } => {
561                let name = name.quoted();
562                write!(f, "{item_type} {name} does not have columns")
563            }
564            Self::WrongJoinTypeForLateralColumn { table, column } => write!(
565                f,
566                "column {} cannot be referenced from this part of the query: \
567                the combining JOIN type must be INNER or LEFT for a LATERAL reference",
568                ColumnDisplay { table, column },
569            ),
570            Self::AmbiguousColumn(column) => write!(
571                f,
572                "column reference {} is ambiguous",
573                column.quoted()
574            ),
575            Self::TooManyColumns { max_num_columns, req_num_columns } => write!(
576                f,
577                "attempt to create relation with too many columns, {} max: {}",
578                req_num_columns, max_num_columns
579            ),
580            Self::ColumnAlreadyExists { column_name, object_name } => write!(
581                f,
582                "column {} of relation {} already exists",
583                column_name.quoted(), object_name.quoted(),
584            ),
585            Self::AmbiguousTable(table) => write!(
586                f,
587                "table reference {} is ambiguous",
588                table.item.as_str().quoted()
589            ),
590            Self::UnknownColumnInUsingClause { column, join_side } => write!(
591                f,
592                "column {} specified in USING clause does not exist in {} table",
593                column.quoted(),
594                join_side,
595            ),
596            Self::AmbiguousColumnInUsingClause { column, join_side } => write!(
597                f,
598                "common column name {} appears more than once in {} table",
599                column.quoted(),
600                join_side,
601            ),
602            Self::MisqualifiedName(name) => write!(
603                f,
604                "qualified name did not have between 1 and 3 components: {}",
605                name
606            ),
607            Self::OverqualifiedDatabaseName(name) => write!(
608                f,
609                "database name '{}' does not have exactly one component",
610                name
611            ),
612            Self::OverqualifiedSchemaName(name) => write!(
613                f,
614                "schema name '{}' cannot have more than two components",
615                name
616            ),
617            Self::UnderqualifiedColumnName(name) => write!(
618                f,
619                "column name '{}' must have at least a table qualification",
620                name
621            ),
622            Self::UnacceptableTimelineName(name) => {
623                write!(f, "unacceptable timeline name {}", name.quoted(),)
624            }
625            Self::SubqueriesDisallowed { context } => {
626                write!(f, "{} does not allow subqueries", context)
627            }
628            Self::UnknownParameter(n) => write!(f, "there is no parameter ${}", n),
629            Self::ParameterNotAllowed(object_type) => write!(f, "{} cannot have parameters", object_type),
630            Self::WrongParameterType(i, expected_ty, actual_ty) => write!(f, "unable to cast given parameter ${}: expected {}, got {}", i, expected_ty, actual_ty),
631            Self::RecursionLimit(e) => write!(f, "{}", e),
632            Self::StrconvParse(e) => write!(f, "{}", e),
633            Self::Catalog(e) => write!(f, "{}", e),
634            Self::UpsertSinkWithoutKey => write!(f, "upsert sinks must specify a key"),
635            Self::UpsertSinkWithInvalidKey { .. } => {
636                write!(f, "upsert key could not be validated as unique")
637            }
638            Self::IcebergSinkUnsupportedKeyType { column, column_type } => {
639                write!(f, "column {column} has type {column_type} which cannot be used as an Iceberg equality delete key")
640            }
641            Self::InvalidWmrRecursionLimit(msg) => write!(f, "Invalid WITH MUTUALLY RECURSIVE recursion limit. {}", msg),
642            Self::InvalidNumericMaxScale(e) => e.fmt(f),
643            Self::InvalidCharLength(e) => e.fmt(f),
644            Self::InvalidVarCharMaxLength(e) => e.fmt(f),
645            Self::InvalidTimestampPrecision(e) => e.fmt(f),
646            Self::Parser(e) => e.fmt(f),
647            Self::ParserStatement(e) => e.fmt(f),
648            Self::Unstructured(e) => write!(f, "{}", e),
649            Self::InvalidId(id) => write!(f, "invalid id {}", id),
650            Self::InvalidIdent(err) => write!(f, "invalid identifier, {err}"),
651            Self::InvalidObject(i) => write!(f, "{} is not a database object", i.full_name_str()),
652            Self::InvalidObjectType{expected_type, actual_type, object_name} => write!(f, "{actual_type} {object_name} is not a {expected_type}"),
653            Self::InvalidPrivilegeTypes{ invalid_privileges, object_description, } => {
654                write!(f, "invalid privilege types {} for {}", invalid_privileges.to_error_string(), object_description)
655            },
656            Self::InvalidSecret(i) => write!(f, "{} is not a secret", i.full_name_str()),
657            Self::InvalidTemporarySchema => {
658                write!(f, "cannot create temporary item in non-temporary schema")
659            }
660            Self::InvalidCast { name, ccx, from, to } =>{
661                write!(
662                    f,
663                    "{name} does not support {ccx}casting from {from} to {to}",
664                    ccx = if matches!(ccx, CastContext::Implicit) {
665                        "implicitly "
666                    } else {
667                        ""
668                    },
669                )
670            }
671            Self::UnsupportedRangeElementType { element_type_name } => {
672                write!(f, "range type over {} is not supported", element_type_name)
673            }
674            Self::InvalidTable { name } => {
675                write!(f, "invalid table definition for {}", name.quoted())
676            },
677            Self::InvalidVersion { name, version } => {
678                write!(f, "invalid version {} for {}", version.quoted(), name.quoted())
679            },
680            Self::InvalidSinkFrom { name, item_type } => {
681                write!(f, "{item_type} {name} cannot be exported as a sink")
682            },
683            Self::InvalidDependency { name, item_type } => {
684                write!(f, "{item_type} {name} cannot be depended upon")
685            },
686            Self::DropViewOnMaterializedView(name)
687            | Self::AlterViewOnMaterializedView(name)
688            | Self::ShowCreateViewOnMaterializedView(name)
689            | Self::ExplainViewOnMaterializedView(name) => write!(f, "{name} is not a view"),
690            Self::FetchingCsrSchemaFailed { schema_lookup, .. } => {
691                write!(f, "failed to fetch schema {schema_lookup} from schema registry")
692            }
693            Self::PostgresConnectionErr { .. } => {
694                write!(f, "failed to connect to PostgreSQL database")
695            }
696            Self::MySqlConnectionErr { cause } => {
697                write!(f, "failed to connect to MySQL database: {}", cause)
698            }
699            Self::SqlServerConnectionErr { cause } => {
700                write!(f, "failed to connect to SQL Server database: {}", cause)
701            }
702            Self::SubsourceNameConflict {
703                name , upstream_references: _,
704            } => {
705                write!(f, "multiple subsources would be named {}", name)
706            },
707            Self::SubsourceDuplicateReference {
708                name,
709                target_names: _,
710            } => {
711                write!(f, "multiple subsources refer to table {}", name)
712            },
713            Self::NoTablesFoundForSchemas(schemas) => {
714                write!(f, "no tables found in referenced schemas: {}",
715                    separated(", ", schemas.iter().map(|c| c.quoted()))
716                )
717            },
718            Self::InvalidProtobufSchema { .. } => {
719                write!(f, "invalid protobuf schema")
720            }
721            Self::DependentObjectsStillExist {object_type, object_name, dependents} => {
722                let reason = match &dependents[..] {
723                    [] => " because other objects depend on it".to_string(),
724                    dependents => {
725                        let dependents = dependents.iter().map(|(dependent_type, dependent_name)| format!("{} {}", dependent_type, dependent_name.quoted())).join(", ");
726                        format!(": still depended upon by {dependents}")
727                    },
728                };
729                let object_name = object_name.quoted();
730                write!(f, "cannot drop {object_type} {object_name}{reason}")
731            }
732            Self::InvalidOptionValue { option_name, err } => write!(f, "invalid {} option value: {}", option_name, err),
733            Self::UnexpectedDuplicateReference { name } => write!(f, "unexpected multiple references to {}", name.to_ast_string_simple()),
734            Self::RecursiveTypeMismatch(name, declared, inferred) => {
735                let declared = separated(", ", declared);
736                let inferred = separated(", ", inferred);
737                let name = name.quoted();
738                write!(f, "WITH MUTUALLY RECURSIVE query {name} declared types ({declared}), but query returns types ({inferred})")
739            },
740            Self::UnknownFunction {name, arg_types, ..} => {
741                write!(f, "function {}({}) does not exist", name, arg_types.join(", "))
742            },
743            Self::IndistinctFunction {name, arg_types, ..} => {
744                write!(f, "function {}({}) is not unique", name, arg_types.join(", "))
745            },
746            Self::UnknownOperator {name, arg_types, ..} => {
747                write!(f, "operator does not exist: {}", match arg_types.as_slice(){
748                    [typ] => format!("{} {}", name, typ),
749                    [ltyp, rtyp] => {
750                        format!("{} {} {}", ltyp, name, rtyp)
751                    }
752                    _ => unreachable!("non-unary non-binary operator"),
753                })
754            },
755            Self::IndistinctOperator {name, arg_types, ..} => {
756                write!(f, "operator is not unique: {}", match arg_types.as_slice(){
757                    [typ] => format!("{} {}", name, typ),
758                    [ltyp, rtyp] => {
759                        format!("{} {} {}", ltyp, name, rtyp)
760                    }
761                    _ => unreachable!("non-unary non-binary operator"),
762                })
763            },
764            Self::InvalidPrivatelinkAvailabilityZone { name, ..} => write!(f, "invalid AWS PrivateLink availability zone {}", name.quoted()),
765            Self::DuplicatePrivatelinkAvailabilityZone {..} =>   write!(f, "connection cannot contain duplicate availability zones"),
766            Self::InvalidSchemaName => write!(f, "no valid schema selected"),
767            Self::ItemAlreadyExists { name, item_type } => write!(f, "{item_type} {} already exists", name.quoted()),
768            Self::ManagedCluster {cluster_name} => write!(f, "cannot modify managed cluster {cluster_name}"),
769            Self::InvalidKeysInSubscribeEnvelopeUpsert => {
770                write!(f, "invalid keys in SUBSCRIBE ENVELOPE UPSERT (KEY (..))")
771            }
772            Self::InvalidKeysInSubscribeEnvelopeDebezium => {
773                write!(f, "invalid keys in SUBSCRIBE ENVELOPE DEBEZIUM (KEY (..))")
774            }
775            Self::DuplicateKeyColumnInSubscribeEnvelope { column_name } => {
776                write!(
777                    f,
778                    "column {} appears more than once in SUBSCRIBE ENVELOPE KEY clause",
779                    column_name.quoted(),
780                )
781            }
782            Self::InvalidPartitionByEnvelopeDebezium { column_name } => {
783                write!(
784                    f,
785                    "PARTITION BY expression cannot refer to non-key column {}",
786                    column_name.quoted(),
787                )
788            }
789            Self::InvalidOrderByInSubscribeWithinTimestampOrderBy => {
790                write!(f, "invalid ORDER BY in SUBSCRIBE WITHIN TIMESTAMP ORDER BY")
791            }
792            Self::FromValueRequiresParen => f.write_str(
793                "VALUES expression in FROM clause must be surrounded by parentheses"
794            ),
795            Self::VarError(e) => e.fmt(f),
796            Self::UnsolvablePolymorphicFunctionInput => f.write_str(
797                "could not determine polymorphic type because input has type unknown"
798            ),
799            Self::ShowCommandInView => f.write_str("SHOW commands are not allowed in views"),
800            Self::WebhookValidationDoesNotUseColumns => f.write_str(
801                "expression provided in CHECK does not reference any columns"
802            ),
803            Self::WebhookValidationNonDeterministic => f.write_str(
804                "expression provided in CHECK is not deterministic"
805            ),
806            Self::InternalFunctionCall => f.write_str("cannot call function with arguments of type internal"),
807            Self::CommentTooLong { length, max_size } => {
808                write!(f, "provided comment was {length} bytes long, max size is {max_size} bytes")
809            }
810            Self::InvalidTimestampInterval { min, max, requested } => {
811                write!(f, "invalid timestamp interval of {}ms, must be in the range [{}ms, {}ms]", requested.as_millis(), min.as_millis(), max.as_millis())
812            }
813            Self::InvalidGroupSizeHints => f.write_str("EXPECTED GROUP SIZE cannot be provided \
814                simultaneously with any of AGGREGATE INPUT GROUP SIZE, DISTINCT ON INPUT GROUP SIZE, \
815                or LIMIT INPUT GROUP SIZE"),
816            Self::PgSourcePurification(e) => write!(f, "POSTGRES source validation: {}", e),
817            Self::KafkaSourcePurification(e) => write!(f, "KAFKA source validation: {}", e),
818            Self::LoadGeneratorSourcePurification(e) => write!(f, "LOAD GENERATOR source validation: {}", e),
819            Self::KafkaSinkPurification(e) => write!(f, "KAFKA sink validation: {}", e),
820            Self::IcebergSinkPurification(e) => write!(f, "ICEBERG sink validation: {}", e),
821            Self::CsrPurification(e) => write!(f, "CONFLUENT SCHEMA REGISTRY validation: {}", e),
822            Self::GluePurification(e) => write!(f, "AWS GLUE SCHEMA REGISTRY validation: {}", e),
823            Self::MySqlSourcePurification(e) => write!(f, "MYSQL source validation: {}", e),
824            Self::SqlServerSourcePurificationError(e) => write!(f, "SQL SERVER source validation: {}", e),
825            Self::UseTablesForSources(command) => write!(f, "{command} not supported; use CREATE TABLE .. FROM SOURCE instead"),
826            Self::MangedReplicaName(name) => {
827                write!(f, "{name} is reserved for replicas of managed clusters")
828            }
829            Self::MissingName(item_type) => {
830                write!(f, "unspecified name for {item_type}")
831            }
832            Self::InvalidRefreshAt => {
833                write!(f, "REFRESH AT argument must be an expression that can be simplified \
834                           and/or cast to a constant whose type is mz_timestamp")
835            }
836            Self::InvalidRefreshEveryAlignedTo => {
837                write!(f, "REFRESH EVERY ... ALIGNED TO argument must be an expression that can be simplified \
838                           and/or cast to a constant whose type is mz_timestamp")
839            }
840            Self::MismatchedObjectType {
841                name,
842                is_type,
843                expected_type,
844            } => {
845                write!(
846                    f,
847                    "{name} is {} {} not {} {}",
848                    if *is_type == ObjectType::Index {
849                        "an"
850                    } else {
851                        "a"
852                    },
853                    is_type.to_string().to_lowercase(),
854                    if *expected_type == ObjectType::Index {
855                        "an"
856                    } else {
857                        "a"
858                    },
859                    expected_type.to_string().to_lowercase()
860                )
861            }
862            Self::TableContainsUningestableTypes { name, type_, column } => {
863                write!(f, "table {name} contains column {column} of type {type_} which Materialize cannot currently ingest")
864            },
865            Self::RetainHistoryLow { limit } => {
866                write!(f, "RETAIN HISTORY cannot be set lower than {}ms", limit.as_millis())
867            },
868            Self::RetainHistoryRequired => {
869                write!(f, "RETAIN HISTORY cannot be disabled or set to 0")
870            },
871            Self::SubsourceResolutionError(e) => write!(f, "{}", e),
872            Self::Replan(msg) => write!(f, "internal error while replanning, please contact support: {msg}"),
873            Self::Internal(msg) => write!(f, "internal error: {msg}"),
874            Self::NetworkPolicyLockoutError => write!(f, "policy would block current session IP"),
875            Self::NetworkPolicyInUse => write!(f, "network policy is currently in use"),
876            Self::UntilReadyTimeoutRequired => {
877                write!(f, "TIMEOUT=<duration> option is required for ALTER CLUSTER ... WITH (WAIT UNTIL READY ( ... ))")
878            },
879            Self::ConstantExpressionSimplificationFailed(e) => write!(f, "{}", e),
880            Self::InvalidLimit(e) => write!(f, "Invalid LIMIT clause: {}", e),
881            Self::InvalidOffset(e) => write!(f, "Invalid OFFSET clause: {}", e),
882            Self::UnknownCursor(name) => {
883                write!(f, "cursor {} does not exist", name.quoted())
884            }
885            Self::CopyFromTargetTableDropped { target_name: name } => {
886                write!(f, "COPY FROM's target table {} was dropped", name.quoted())
887            }
888            Self::InvalidAsOfUpTo => write!(
889                f,
890                "AS OF or UP TO should be castable to a (non-null) mz_timestamp value",
891            ),
892            Self::InvalidReplacement {
893                item_type, item_name, replacement_type, replacement_name,
894            } => {
895                write!(
896                    f,
897                    "cannot replace {item_type} {item_name} \
898                     with {replacement_type} {replacement_name}",
899                )
900            }
901        }
902    }
903}
904
905impl Error for PlanError {}
906
907impl From<CatalogError> for PlanError {
908    fn from(e: CatalogError) -> PlanError {
909        PlanError::Catalog(e)
910    }
911}
912
913impl From<strconv::ParseError> for PlanError {
914    fn from(e: strconv::ParseError) -> PlanError {
915        PlanError::StrconvParse(e)
916    }
917}
918
919impl From<RecursionLimitError> for PlanError {
920    fn from(e: RecursionLimitError) -> PlanError {
921        PlanError::RecursionLimit(e)
922    }
923}
924
925impl From<InvalidNumericMaxScaleError> for PlanError {
926    fn from(e: InvalidNumericMaxScaleError) -> PlanError {
927        PlanError::InvalidNumericMaxScale(e)
928    }
929}
930
931impl From<InvalidCharLengthError> for PlanError {
932    fn from(e: InvalidCharLengthError) -> PlanError {
933        PlanError::InvalidCharLength(e)
934    }
935}
936
937impl From<InvalidVarCharMaxLengthError> for PlanError {
938    fn from(e: InvalidVarCharMaxLengthError) -> PlanError {
939        PlanError::InvalidVarCharMaxLength(e)
940    }
941}
942
943impl From<InvalidTimestampPrecisionError> for PlanError {
944    fn from(e: InvalidTimestampPrecisionError) -> PlanError {
945        PlanError::InvalidTimestampPrecision(e)
946    }
947}
948
949impl From<anyhow::Error> for PlanError {
950    fn from(e: anyhow::Error) -> PlanError {
951        // WIP: Do we maybe want to keep the alternate selector for these?
952        sql_err!("{}", e.display_with_causes())
953    }
954}
955
956impl From<TryFromIntError> for PlanError {
957    fn from(e: TryFromIntError) -> PlanError {
958        sql_err!("{}", e.display_with_causes())
959    }
960}
961
962impl From<ParseIntError> for PlanError {
963    fn from(e: ParseIntError) -> PlanError {
964        sql_err!("{}", e.display_with_causes())
965    }
966}
967
968impl From<EvalError> for PlanError {
969    fn from(e: EvalError) -> PlanError {
970        sql_err!("{}", e.display_with_causes())
971    }
972}
973
974impl From<ParserError> for PlanError {
975    fn from(e: ParserError) -> PlanError {
976        PlanError::Parser(e)
977    }
978}
979
980impl From<ParserStatementError> for PlanError {
981    fn from(e: ParserStatementError) -> PlanError {
982        PlanError::ParserStatement(e)
983    }
984}
985
986impl From<PostgresError> for PlanError {
987    fn from(e: PostgresError) -> PlanError {
988        PlanError::PostgresConnectionErr { cause: Arc::new(e) }
989    }
990}
991
992impl From<MySqlError> for PlanError {
993    fn from(e: MySqlError) -> PlanError {
994        PlanError::MySqlConnectionErr { cause: Arc::new(e) }
995    }
996}
997
998impl From<SqlServerError> for PlanError {
999    fn from(e: SqlServerError) -> PlanError {
1000        PlanError::SqlServerConnectionErr { cause: Arc::new(e) }
1001    }
1002}
1003
1004impl From<VarError> for PlanError {
1005    fn from(e: VarError) -> Self {
1006        PlanError::VarError(e)
1007    }
1008}
1009
1010impl From<PgSourcePurificationError> for PlanError {
1011    fn from(e: PgSourcePurificationError) -> Self {
1012        PlanError::PgSourcePurification(e)
1013    }
1014}
1015
1016impl From<KafkaSourcePurificationError> for PlanError {
1017    fn from(e: KafkaSourcePurificationError) -> Self {
1018        PlanError::KafkaSourcePurification(e)
1019    }
1020}
1021
1022impl From<KafkaSinkPurificationError> for PlanError {
1023    fn from(e: KafkaSinkPurificationError) -> Self {
1024        PlanError::KafkaSinkPurification(e)
1025    }
1026}
1027
1028impl From<IcebergSinkPurificationError> for PlanError {
1029    fn from(e: IcebergSinkPurificationError) -> Self {
1030        PlanError::IcebergSinkPurification(e)
1031    }
1032}
1033
1034impl From<GluePurificationError> for PlanError {
1035    fn from(e: GluePurificationError) -> Self {
1036        PlanError::GluePurification(e)
1037    }
1038}
1039
1040impl From<CsrPurificationError> for PlanError {
1041    fn from(e: CsrPurificationError) -> Self {
1042        PlanError::CsrPurification(e)
1043    }
1044}
1045
1046impl From<LoadGeneratorSourcePurificationError> for PlanError {
1047    fn from(e: LoadGeneratorSourcePurificationError) -> Self {
1048        PlanError::LoadGeneratorSourcePurification(e)
1049    }
1050}
1051
1052impl From<MySqlSourcePurificationError> for PlanError {
1053    fn from(e: MySqlSourcePurificationError) -> Self {
1054        PlanError::MySqlSourcePurification(e)
1055    }
1056}
1057
1058impl From<SqlServerSourcePurificationError> for PlanError {
1059    fn from(e: SqlServerSourcePurificationError) -> Self {
1060        PlanError::SqlServerSourcePurificationError(e)
1061    }
1062}
1063
1064impl From<IdentError> for PlanError {
1065    fn from(e: IdentError) -> Self {
1066        PlanError::InvalidIdent(e)
1067    }
1068}
1069
1070impl From<ExternalReferenceResolutionError> for PlanError {
1071    fn from(e: ExternalReferenceResolutionError) -> Self {
1072        PlanError::SubsourceResolutionError(e)
1073    }
1074}
1075
1076struct ColumnDisplay<'a> {
1077    table: &'a Option<PartialItemName>,
1078    column: &'a ColumnName,
1079}
1080
1081impl<'a> fmt::Display for ColumnDisplay<'a> {
1082    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1083        if let Some(table) = &self.table {
1084            format!("{}.{}", table.item, self.column).quoted().fmt(f)
1085        } else {
1086            self.column.quoted().fmt(f)
1087        }
1088    }
1089}