1use 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;
19
20use mz_compute_types::ComputeInstanceId;
21use mz_controller_types::ClusterId;
22use mz_expr::EvalError;
23use mz_ore::cast::CastFrom;
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, DataflowError};
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#[derive(Debug)]
51pub enum AdapterError {
52 AbsurdSubscribeBounds {
54 as_of: mz_repr::Timestamp,
55 up_to: mz_repr::Timestamp,
56 },
57 AmbiguousSystemColumnReference,
61 Catalog(mz_catalog::memory::error::Error),
63 ChangedPlan(String),
68 DuplicateCursor(String),
70 Eval(EvalError),
72 Dataflow(Box<DataflowError>),
74 Explain(ExplainError),
76 IdExhaustionError,
78 Internal(String),
80 IntrospectionDisabled {
82 log_names: Vec<String>,
83 },
84 InvalidLogDependency {
87 object_type: String,
88 log_names: Vec<String>,
89 },
90 InvalidClusterReplicaAz {
92 az: String,
93 expected: Vec<String>,
94 },
95 InvalidSetIsolationLevel,
97 InvalidSetCluster,
99 InvalidStorageClusterSize {
101 size: String,
102 expected: Vec<String>,
103 },
104 SourceOrSinkSizeRequired {
106 expected: Vec<String>,
107 },
108 InvalidTableMutationSelection {
110 object_name: String,
112 object_type: String,
114 },
115 ReadThenWriteDependencyLimitExceeded {
118 max_rw_dependencies: usize,
120 },
121 ConstraintViolation(NotNullViolation),
123 CopyFormatError(String),
125 ConcurrentClusterDrop,
127 ConcurrentDependencyDrop {
129 dependency_kind: &'static str,
130 dependency_id: String,
131 },
132 ConcurrentDependencyMutation {
138 dependency_id: String,
139 },
140 ReadThenWriteContention,
145 ReadThenWriteTimestampTooFarAhead {
153 target_timestamp: mz_repr::Timestamp,
154 limit: mz_repr::Timestamp,
155 },
156 CollectionUnreadable {
157 id: String,
158 },
159 NoClusterReplicasAvailable {
161 name: String,
162 is_managed: bool,
163 },
164 OperationProhibitsTransaction(String),
166 OperationRequiresTransaction(String),
168 PlanError(PlanError),
170 PreparedStatementExists(String),
172 ParseError(mz_sql_parser::parser::ParserStatementError),
174 ReadOnlyTransaction,
176 ReadWriteUnavailable,
178 RecursionLimit(RecursionLimitError),
180 RelationOutsideTimeDomain {
183 relations: Vec<String>,
184 names: Vec<String>,
185 },
186 ResourceExhaustion {
188 resource_type: String,
189 limit_name: String,
190 desired: String,
191 limit: String,
192 current: String,
193 },
194 ResultSize(String),
196 SubscribeFellBehind {
201 buffered_bytes: usize,
203 max_buffered_bytes: usize,
205 },
206 PeekRowIterationLimitExceeded {
208 limit: usize,
210 },
211 SafeModeViolation(String),
213 StatementTimeout,
217 Canceled,
219 IdleInTransactionSessionTimeout,
221 SubscribeOnlyTransaction,
223 Optimizer(OptimizerError),
225 UnallowedOnCluster {
227 depends_on: SmallVec<[String; 2]>,
228 cluster: String,
229 },
230 Unauthorized(rbac::UnauthorizedError),
232 UnknownCursor(String),
234 UnknownLoginRole(String),
236 UnknownPreparedStatement(String),
237 UnknownClusterReplica {
239 cluster_name: String,
240 replica_name: String,
241 },
242 UnrecognizedConfigurationParam(String),
244 Unstructured(anyhow::Error),
248 Unsupported(&'static str),
250 UnavailableFeature {
254 feature: String,
255 docs: Option<String>,
256 },
257 UntargetedLogRead {
259 log_names: Vec<String>,
260 },
261 WriteOnlyTransaction,
263 SingleStatementTransaction,
265 DDLOnlyTransaction,
267 DDLTransactionOutcomeChanged,
269 ClusterStateChanged {
274 cluster_id: ClusterId,
275 },
276 Storage(mz_storage_types::controller::StorageError),
278 Compute(anyhow::Error),
280 Orchestrator(anyhow::Error),
282 DependentObject(BTreeMap<String, Vec<String>>),
286 IndexInUse {
292 blocked: Vec<(String, Vec<(String, String)>)>,
293 },
294 InvalidAlter(&'static str, PlanError),
297 ConnectionValidation(ConnectionValidationError),
299 MaterializedViewWouldNeverRefresh(Timestamp, Timestamp),
303 InputNotReadableAtRefreshAtTime(Timestamp, Antichain<Timestamp>),
306 RtrTimeout(String),
308 RtrDropFailure(String),
310 UnreadableSinkCollection,
312 UserSessionsDisallowed,
314 NetworkPolicyDenied(NetworkPolicyError),
316 ReadOnly,
319 AlterClusterTimeout,
320 AlterClusterSuperseded,
323 AlterClusterResourceExhausted,
326 AlterClusterUnmanagedWhileReconfiguring,
329 AlterClusterUnmanagedWhileBursting,
332 AlterClusterScheduleWhileReconfiguring,
335 AlterClusterWaitOnScheduledCluster,
338 AuthenticationError(AuthenticationError),
339 ReplacementSchemaMismatch(RelationDescDiff),
341 ReplaceMaterializedViewSealed {
343 name: String,
344 },
345 ImpossibleTimestampConstraints {
347 constraints: String,
348 },
349 OidcGroupSyncFailed(String),
351 BoundedStalenessExceeded {
355 bound: std::time::Duration,
356 gap_ms: u64,
357 slowest_input: Option<mz_repr::GlobalId>,
358 },
359 BoundedStalenessReadOnly,
362 BoundedStalenessRealTimeRecencyConflict,
365 BoundedStalenessTimelineUnsupported,
368}
369
370#[derive(Debug, thiserror::Error)]
371pub enum AuthenticationError {
372 #[error("invalid credentials")]
373 InvalidCredentials,
374 #[error("role is not allowed to login")]
375 NonLogin,
376 #[error("role does not exist")]
377 RoleNotFound,
378 #[error("password is required")]
379 PasswordRequired,
380}
381
382fn parse_error_code(err: &ParseError) -> SqlState {
387 let is_datetime = matches!(
388 &*err.type_name,
389 "date" | "time" | "timestamp" | "timestamp with time zone" | "interval"
390 );
391 match (err.kind, is_datetime) {
392 (ParseErrorKind::OutOfRange, true) => SqlState::DATETIME_FIELD_OVERFLOW,
393 (ParseErrorKind::OutOfRange, false) => SqlState::NUMERIC_VALUE_OUT_OF_RANGE,
394 (ParseErrorKind::InvalidInputSyntax, true) => SqlState::INVALID_DATETIME_FORMAT,
395 (ParseErrorKind::InvalidInputSyntax, false) => SqlState::INVALID_TEXT_REPRESENTATION,
396 }
397}
398
399fn eval_error_code(err: &EvalError) -> SqlState {
409 match err {
410 EvalError::DivisionByZero => SqlState::DIVISION_BY_ZERO,
412 EvalError::NegSqrt | EvalError::ComplexOutOfRange(_) => {
413 SqlState::INVALID_ARGUMENT_FOR_POWER_FUNCTION
414 }
415 EvalError::InfinityOutOfDomain(_)
416 | EvalError::NegativeOutOfDomain(_)
417 | EvalError::ZeroOutOfDomain(_)
418 | EvalError::OutOfDomain(..)
419 | EvalError::Undefined(_) => SqlState::INVALID_PARAMETER_VALUE,
420
421 EvalError::FloatOverflow
423 | EvalError::FloatUnderflow
424 | EvalError::NumericFieldOverflow
425 | EvalError::Float32OutOfRange(_)
426 | EvalError::Float64OutOfRange(_)
427 | EvalError::Int16OutOfRange(_)
428 | EvalError::Int32OutOfRange(_)
429 | EvalError::Int64OutOfRange(_)
430 | EvalError::UInt16OutOfRange(_)
431 | EvalError::UInt32OutOfRange(_)
432 | EvalError::UInt64OutOfRange(_)
433 | EvalError::OidOutOfRange(_)
434 | EvalError::MzTimestampOutOfRange(_)
435 | EvalError::MzTimestampStepOverflow
436 | EvalError::CharOutOfRange => SqlState::NUMERIC_VALUE_OUT_OF_RANGE,
437
438 EvalError::DateOutOfRange
440 | EvalError::TimestampOutOfRange
441 | EvalError::TimestampCannotBeNan
442 | EvalError::DateBinOutOfRange(_)
443 | EvalError::DateDiffOverflow { .. } => SqlState::DATETIME_FIELD_OVERFLOW,
444 EvalError::IntervalOutOfRange(_) => SqlState::INTERVAL_FIELD_OVERFLOW,
445
446 EvalError::Parse(e) => parse_error_code(e),
448 EvalError::ParseHex(_)
449 | EvalError::InvalidBase64Equals
450 | EvalError::InvalidBase64Symbol(_)
451 | EvalError::InvalidBase64EndSequence => SqlState::INVALID_TEXT_REPRESENTATION,
452 EvalError::InvalidByteSequence { .. } => SqlState::CHARACTER_NOT_IN_REPERTOIRE,
453 EvalError::CharacterNotValidForEncoding(_) | EvalError::CharacterTooLargeForEncoding(_) => {
454 SqlState::PROGRAM_LIMIT_EXCEEDED
455 }
456
457 EvalError::InvalidTimezone(_)
459 | EvalError::InvalidTimezoneInterval
460 | EvalError::InvalidTimezoneConversion
461 | EvalError::InvalidIanaTimezoneId(_)
462 | EvalError::InvalidLayer { .. }
463 | EvalError::InvalidEncodingName(_)
464 | EvalError::InvalidHashAlgorithm(_)
465 | EvalError::InvalidDatePart(_)
466 | EvalError::InvalidParameterValue(_)
467 | EvalError::InvalidJsonbCast { .. }
468 | EvalError::UnknownUnits(_)
469 | EvalError::UnsupportedUnits(..)
470 | EvalError::InvalidIdentifier { .. }
471 | EvalError::InvalidRoleId(_)
472 | EvalError::InvalidPrivileges(_)
473 | EvalError::NegLimit => SqlState::INVALID_PARAMETER_VALUE,
474
475 EvalError::InvalidRegex(_) | EvalError::InvalidRegexFlag(_) => {
477 SqlState::INVALID_REGULAR_EXPRESSION
478 }
479
480 EvalError::UnterminatedLikeEscapeSequence | EvalError::LikeEscapeTooLong => {
482 SqlState::INVALID_ESCAPE_SEQUENCE
483 }
484
485 EvalError::KeyCannotBeNull
487 | EvalError::MustNotBeNull(_)
488 | EvalError::AclArrayNullElement
489 | EvalError::MzAclArrayNullElement => SqlState::NULL_VALUE_NOT_ALLOWED,
490
491 EvalError::IndexOutOfRange { .. }
493 | EvalError::ArrayFillWrongArraySubscripts
494 | EvalError::IncompatibleArrayDimensions { .. } => SqlState::ARRAY_SUBSCRIPT_ERROR,
495 EvalError::InvalidArray(e) => match e {
496 InvalidArrayError::TooManyDimensions(_) => SqlState::PROGRAM_LIMIT_EXCEEDED,
497 InvalidArrayError::WrongCardinality { .. } => SqlState::ARRAY_SUBSCRIPT_ERROR,
498 },
499
500 EvalError::InvalidRange(e) => match e {
502 InvalidRangeError::CanonicalizationOverflow(_) => SqlState::NUMERIC_VALUE_OUT_OF_RANGE,
503 InvalidRangeError::MisorderedRangeBounds
504 | InvalidRangeError::InvalidRangeBoundFlags
505 | InvalidRangeError::NullRangeBoundFlags
506 | InvalidRangeError::DiscontiguousUnion
507 | InvalidRangeError::DiscontiguousDifference
508 | InvalidRangeError::InvalidRangeData => SqlState::DATA_EXCEPTION,
509 },
510
511 EvalError::MultipleRowsFromSubquery | EvalError::NegativeRowsFromSubquery => {
513 SqlState::CARDINALITY_VIOLATION
514 }
515
516 EvalError::StringValueTooLong { .. } => SqlState::STRING_DATA_RIGHT_TRUNCATION,
518 EvalError::LikePatternTooLong
519 | EvalError::LengthTooLarge
520 | EvalError::TempStorageBudgetExceeded
521 | EvalError::NullCharacterNotPermitted
522 | EvalError::MaxArraySizeExceeded(_)
523 | EvalError::LetRecLimitExceeded(_) => SqlState::PROGRAM_LIMIT_EXCEEDED,
524
525 EvalError::Unsupported { .. }
527 | EvalError::MultidimensionalArrayRemovalNotSupported
528 | EvalError::MultiDimensionalArraySearch => SqlState::FEATURE_NOT_SUPPORTED,
529
530 EvalError::IfNullError(_) => SqlState::DATA_EXCEPTION,
532
533 EvalError::Internal(_)
535 | EvalError::InvalidCatalogJson(_)
536 | EvalError::TypeFromOid(_)
537 | EvalError::PrettyError(_)
538 | EvalError::RedactError(_) => SqlState::INTERNAL_ERROR,
539 }
540}
541
542fn dataflow_error_code(error: &DataflowError) -> SqlState {
543 match error {
544 DataflowError::EvalError(error) => eval_error_code(error),
545 DataflowError::DecodeError(_)
546 | DataflowError::SourceError(_)
547 | DataflowError::EnvelopeError(_) => SqlState::INTERNAL_ERROR,
548 }
549}
550
551impl AdapterError {
552 pub fn into_response(self, severity: Severity) -> ErrorResponse {
553 ErrorResponse {
554 severity,
555 code: self.code(),
556 message: self.to_string(),
557 detail: self.detail(),
558 hint: self.hint(),
559 position: self.position(),
560 }
561 }
562
563 pub fn position(&self) -> Option<usize> {
564 match self {
565 AdapterError::ParseError(err) => Some(err.error.pos),
566 _ => None,
567 }
568 }
569
570 pub fn detail(&self) -> Option<String> {
572 match self {
573 AdapterError::AmbiguousSystemColumnReference => {
574 Some("This is a current limitation in Materialize".into())
575 }
576 AdapterError::Catalog(c) => c.detail(),
577 AdapterError::Eval(e) => e.detail(),
578 AdapterError::Dataflow(e) => match &**e {
579 DataflowError::EvalError(e) => e.detail(),
580 _ => None,
581 },
582 AdapterError::RelationOutsideTimeDomain { relations, names } => Some(format!(
583 "The following relations in the query are outside the transaction's time domain:\n{}\n{}",
584 relations
585 .iter()
586 .map(|r| r.quoted().to_string())
587 .collect::<Vec<_>>()
588 .join("\n"),
589 match names.is_empty() {
590 true => "No relations are available.".to_string(),
591 false => format!(
592 "Only the following relations are available:\n{}",
593 names
594 .iter()
595 .map(|name| name.quoted().to_string())
596 .collect::<Vec<_>>()
597 .join("\n")
598 ),
599 }
600 )),
601 AdapterError::SourceOrSinkSizeRequired { .. } => Some(
602 "Either specify the cluster that will maintain this object via IN CLUSTER or \
603 specify size via SIZE option."
604 .into(),
605 ),
606 AdapterError::InvalidTableMutationSelection {
607 object_name,
608 object_type,
609 } => Some(format!(
610 "{object_type} '{}' may not be used in this operation; \
611 every relation leaf in the selection must be a writable user table",
612 object_name.quoted()
613 )),
614 AdapterError::ReadThenWriteDependencyLimitExceeded {
615 max_rw_dependencies,
616 } => Some(format!(
617 "The read set transitively depends on more than {max_rw_dependencies} \
618 objects. Reduce the number of dependencies, or raise the \
619 read_then_write_max_dependencies system parameter."
620 )),
621 AdapterError::PeekRowIterationLimitExceeded { limit } => Some(format!(
622 "The query attempted to examine more than {limit} rows on a single compute \
623 worker. This limit prevents long-running SELECT queries from delaying other \
624 work on the cluster."
625 )),
626 AdapterError::SafeModeViolation(_) => Some(
627 "The Materialize server you are connected to is running in \
628 safe mode, which limits the features that are available."
629 .into(),
630 ),
631 AdapterError::IntrospectionDisabled { log_names }
632 | AdapterError::UntargetedLogRead { log_names } => Some(format!(
633 "The query references the following log sources:\n {}",
634 log_names.join("\n "),
635 )),
636 AdapterError::InvalidLogDependency { log_names, .. } => Some(format!(
637 "The object depends on the following log sources:\n {}",
638 log_names.join("\n "),
639 )),
640 AdapterError::PlanError(e) => e.detail(),
641 AdapterError::Unauthorized(unauthorized) => unauthorized.detail(),
642 AdapterError::IndexInUse { blocked } => match &blocked[..] {
643 [(index_name, _)] => Some(format!(
644 "The dependent objects are live dataflows that read from index {}, so the \
645 index cannot be dropped while they exist.",
646 index_name.quoted()
647 )),
648 blocked => {
649 let per_index = blocked
650 .iter()
651 .map(|(index_name, dependents)| {
652 format!(
653 "Index {} is read by {}.",
654 index_name.quoted(),
655 fmt_dependents(dependents.iter())
656 )
657 })
658 .join(" ");
659 Some(format!(
660 "{per_index} The dependent objects are live dataflows that read from \
661 these indexes, so the indexes cannot be dropped while they exist."
662 ))
663 }
664 },
665 AdapterError::DependentObject(dependent_objects) => Some(
666 dependent_objects
667 .iter()
668 .map(|(role_name, err_msgs)| {
669 err_msgs
670 .iter()
671 .map(|err_msg| format!("{role_name}: {err_msg}"))
672 .join("\n")
673 })
674 .join("\n"),
675 ),
676 AdapterError::Storage(storage_error) => storage_error
677 .source()
678 .map(|source_error| source_error.to_string_with_causes()),
679 AdapterError::ReadOnlyTransaction => Some(
680 "SELECT queries cannot be combined with other query types, including SUBSCRIBE."
681 .into(),
682 ),
683 AdapterError::InvalidAlter(_, e) => e.detail(),
684 AdapterError::Optimizer(e) => e.detail(),
685 AdapterError::ConnectionValidation(e) => e.detail(),
686 AdapterError::MaterializedViewWouldNeverRefresh(last_refresh, earliest_possible) => {
687 Some(format!(
688 "The specified last refresh is at {}, while the earliest possible time to compute the materialized \
689 view is {}.",
690 last_refresh, earliest_possible,
691 ))
692 }
693 AdapterError::UnallowedOnCluster { cluster, .. } => {
694 (cluster == MZ_CATALOG_SERVER_CLUSTER.name).then(|| {
695 format!(
696 "The transaction is executing on the \
697 {cluster} cluster, maybe having been routed \
698 there by the first statement in the transaction."
699 )
700 })
701 }
702 AdapterError::InputNotReadableAtRefreshAtTime(oracle_read_ts, least_valid_read) => {
703 Some(format!(
704 "The requested REFRESH AT time is {}, \
705 but not all input collections are readable earlier than [{}].",
706 oracle_read_ts,
707 if least_valid_read.len() == 1 {
708 format!(
709 "{}",
710 least_valid_read
711 .as_option()
712 .expect("antichain contains exactly 1 timestamp")
713 )
714 } else {
715 format!("{:?}", least_valid_read)
717 }
718 ))
719 }
720 AdapterError::RtrTimeout(name) => Some(format!(
721 "{name} failed to ingest data up to the real-time recency point"
722 )),
723 AdapterError::RtrDropFailure(name) => Some(format!(
724 "{name} dropped before ingesting data to the real-time recency point"
725 )),
726 AdapterError::UserSessionsDisallowed => {
727 Some("Your organization has been blocked. Please contact support.".to_string())
728 }
729 AdapterError::NetworkPolicyDenied(reason) => Some(format!("{reason}.")),
730 AdapterError::ReplacementSchemaMismatch(diff) => {
731 let mut lines: Vec<_> = diff.column_diffs.iter().map(|(idx, diff)| {
732 let pos = idx + 1;
733 match diff {
734 ColumnDiff::Missing { name } => {
735 let name = name.as_str().quoted();
736 format!("missing column {name} at position {pos}")
737 }
738 ColumnDiff::Extra { name } => {
739 let name = name.as_str().quoted();
740 format!("extra column {name} at position {pos}")
741 }
742 ColumnDiff::TypeMismatch { name, left, right } => {
743 let name = name.as_str().quoted();
744 format!("column {name} at position {pos}: type mismatch (target: {left:?}, replacement: {right:?})")
745 }
746 ColumnDiff::NullabilityMismatch { name, left, right } => {
747 let name = name.as_str().quoted();
748 let left = if *left { "NULL" } else { "NOT NULL" };
749 let right = if *right { "NULL" } else { "NOT NULL" };
750 format!("column {name} at position {pos}: nullability mismatch (target: {left}, replacement: {right})")
751 }
752 ColumnDiff::NameMismatch { left, right } => {
753 let left = left.as_str().quoted();
754 let right = right.as_str().quoted();
755 format!("column at position {pos}: name mismatch (target: {left}, replacement: {right})")
756 }
757 }
758 }).collect();
759
760 if let Some(KeyDiff { left, right }) = &diff.key_diff {
761 let format_keys = |keys: &BTreeSet<Vec<ColumnName>>| {
762 if keys.is_empty() {
763 "(none)".to_string()
764 } else {
765 keys.iter()
766 .map(|key| {
767 let cols = key.iter().map(|c| c.as_str()).join(", ");
768 format!("{{{cols}}}")
769 })
770 .join(", ")
771 }
772 };
773 lines.push(format!(
774 "keys differ (target: {}, replacement: {})",
775 format_keys(left),
776 format_keys(right)
777 ));
778 }
779 Some(lines.join("\n"))
780 }
781 AdapterError::ReplaceMaterializedViewSealed { .. } => Some(
782 "The materialized view's contents are final for all future times, so they can \
783 no longer be changed. See \
784 https://materialize.com/docs/sql/alter-materialized-view/#troubleshooting"
785 .into(),
786 ),
787 AdapterError::ImpossibleTimestampConstraints { constraints } => {
788 Some(format!("Constraints:\n{}", constraints))
789 }
790 AdapterError::BoundedStalenessExceeded {
791 gap_ms,
792 slowest_input,
793 ..
794 } => {
795 let mut detail = format!(
796 "Freshest available timestamp is {}ms older than the bound.",
797 gap_ms,
798 );
799 if let Some(id) = slowest_input {
800 detail.push_str(&format!(" Slowest input: {}.", id));
801 }
802 Some(detail)
803 }
804 AdapterError::BoundedStalenessTimelineUnsupported => Some(
805 "This query touches a timeline other than the EpochMilliseconds wall-clock \
806 timeline."
807 .into(),
808 ),
809 _ => None,
810 }
811 }
812
813 pub fn hint(&self) -> Option<String> {
815 match self {
816 AdapterError::AmbiguousSystemColumnReference => Some(
817 "Rewrite the view to refer to all columns by name. Expand all wildcards and \
818 convert all NATURAL JOINs to USING joins."
819 .to_string(),
820 ),
821 AdapterError::Catalog(c) => c.hint(),
822 AdapterError::Eval(e) => e.hint(),
823 AdapterError::IndexInUse { .. } => Some(
824 "Drop the dependent objects first, then drop the index and recreate the \
825 dependents. To replace them in production without downtime, use a blue/green \
826 deployment."
827 .to_string(),
828 ),
829 AdapterError::SubscribeFellBehind { .. } => Some(
830 "The client is not reading results fast enough. Use a client that reads output \
831 without buffering, or raise the subscribe_max_buffered_bytes system variable."
832 .to_string(),
833 ),
834 AdapterError::Dataflow(e) => match &**e {
835 DataflowError::EvalError(e) => e.hint(),
836 DataflowError::SourceError(e) => e.hint.as_ref().map(|hint| hint.to_string()),
837 _ => None,
838 },
839 AdapterError::AlterClusterUnmanagedWhileReconfiguring => Some(
840 "Cancel the reconfiguration by altering the cluster back to its current \
841 configuration, or wait for it to settle, then convert."
842 .to_string(),
843 ),
844 AdapterError::AlterClusterUnmanagedWhileBursting => Some(
845 "Remove the strategy with ALTER CLUSTER ... RESET (AUTO SCALING STRATEGY), \
846 or wait for the burst to wind down, then convert."
847 .to_string(),
848 ),
849 AdapterError::AlterClusterScheduleWhileReconfiguring => Some(
850 "Cancel the reconfiguration by altering the cluster back to its current \
851 configuration, or wait for it to settle, then change the schedule."
852 .to_string(),
853 ),
854 AdapterError::AlterClusterWaitOnScheduledCluster => Some(
855 "The scheduler reshapes a scheduled cluster's replicas in the background. \
856 Run the ALTER without a WAIT option, or set SCHEDULE = MANUAL first."
857 .to_string(),
858 ),
859 AdapterError::InvalidClusterReplicaAz { expected, az: _ } => {
860 Some(if expected.is_empty() {
861 "No availability zones configured; do not specify AVAILABILITY ZONE".into()
862 } else {
863 format!("Valid availability zones are: {}", expected.join(", "))
864 })
865 }
866 AdapterError::InvalidStorageClusterSize { expected, .. } => {
867 Some(format!("Valid sizes are: {}", expected.join(", ")))
868 }
869 AdapterError::SourceOrSinkSizeRequired { expected } => Some(format!(
870 "Try choosing one of the smaller sizes to start. Available sizes: {}",
871 expected.join(", ")
872 )),
873 AdapterError::NoClusterReplicasAvailable { is_managed, .. } => {
874 Some(if *is_managed {
875 "Use ALTER CLUSTER to adjust the replication factor of the cluster. \
876 Example:`ALTER CLUSTER <cluster-name> SET (REPLICATION FACTOR 1)`".into()
877 } else {
878 "Use CREATE CLUSTER REPLICA to attach cluster replicas to the cluster".into()
879 })
880 }
881 AdapterError::UntargetedLogRead { .. } => Some(
882 "Use `SET cluster_replica = <replica-name>` to target a specific replica in the \
883 active cluster. Note that subsequent queries will only be answered by \
884 the selected replica, which might reduce availability. To undo the replica \
885 selection, use `RESET cluster_replica`."
886 .into(),
887 ),
888 AdapterError::ResourceExhaustion { resource_type, .. } => Some(format!(
889 "Drop an existing {resource_type} or contact support to request a limit increase."
890 )),
891 AdapterError::StatementTimeout => Some(
892 "Consider increasing the maximum allowed statement duration for this session by \
893 setting the statement_timeout session variable. For example, `SET \
894 statement_timeout = '120s'`."
895 .into(),
896 ),
897 AdapterError::PeekRowIterationLimitExceeded { .. } => Some(
898 "Reduce the number of rows the query must examine, for example by querying an \
899 indexed, more selective result. Queries with `LIMIT` and no `ORDER BY` can also \
900 stop early. To permit this query, increase \
901 `compute_peek_row_iteration_limit`."
902 .into(),
903 ),
904 AdapterError::PlanError(e) => e.hint(),
905 AdapterError::UnallowedOnCluster { cluster, .. } => {
906 (cluster != MZ_CATALOG_SERVER_CLUSTER.name).then(||
907 "Use `SET CLUSTER = <cluster-name>` to change your cluster and re-run the query."
908 .to_string()
909 )
910 }
911 AdapterError::InvalidAlter(_, e) => e.hint(),
912 AdapterError::Optimizer(e) => e.hint(),
913 AdapterError::ConnectionValidation(e) => e.hint(),
914 AdapterError::InputNotReadableAtRefreshAtTime(_, _) => Some(
915 "You can use `REFRESH AT greatest(mz_now(), <explicit timestamp>)` to refresh \
916 either at the explicitly specified timestamp, or now if the given timestamp would \
917 be in the past.".to_string()
918 ),
919 AdapterError::AlterClusterTimeout => Some(
920 "Consider increasing the timeout duration in the alter cluster statement.".into(),
921 ),
922 AdapterError::AlterClusterSuperseded => Some(
923 "Retry the ALTER CLUSTER statement if its target is still desired.".into(),
924 ),
925 AdapterError::AlterClusterResourceExhausted => Some(
926 "Reduce the target resource use, or inspect \
927 mz_internal.mz_cluster_reconfigurations for the retained outcome."
928 .into(),
929 ),
930 AdapterError::DDLTransactionOutcomeChanged => Some(
931 "Retry the transaction to execute its statements against the current catalog.".into()
932 ),
933 AdapterError::ConcurrentDependencyMutation { .. } => Some(
934 "Another session modified one of this statement's dependencies before \
935 it could commit. Retry the statement.".into()
936 ),
937 AdapterError::ReadThenWriteContention => Some(
938 "Concurrent writes to the target table kept this statement from \
939 committing. Retry the statement, or lower the write concurrency.".into()
940 ),
941 AdapterError::ReadThenWriteTimestampTooFarAhead { .. } => Some(
942 "The write timestamp this statement would have committed at is far ahead of \
943 the wall clock. Committing there would keep writes and strict-serializable \
944 reads on this timeline from proceeding until the clock caught up.".into()
945 ),
946 AdapterError::CollectionUnreadable { .. } => Some(
947 "This could be because the collection has recently been dropped.".into()
948 ),
949 AdapterError::ReplaceMaterializedViewSealed { .. } => Some(
950 "Create a new materialized view with the new definition instead.".into()
951 ),
952 _ => None,
953 }
954 }
955
956 pub fn code(&self) -> SqlState {
957 const RECURSION_LIMIT_ERROR_CODE: SqlState = SqlState::INTERNAL_ERROR;
959
960 match self {
965 AdapterError::AbsurdSubscribeBounds { .. } => SqlState::DATA_EXCEPTION,
968 AdapterError::AmbiguousSystemColumnReference => SqlState::FEATURE_NOT_SUPPORTED,
969 AdapterError::Catalog(e) => match &e.kind {
970 mz_catalog::memory::error::ErrorKind::VarError(e) => match e {
971 VarError::ConstrainedParameter { .. } => SqlState::INVALID_PARAMETER_VALUE,
972 VarError::FixedValueParameter { .. } => SqlState::INVALID_PARAMETER_VALUE,
973 VarError::InvalidParameterType { .. } => SqlState::INVALID_PARAMETER_VALUE,
974 VarError::InvalidParameterValue { .. } => SqlState::INVALID_PARAMETER_VALUE,
975 VarError::ReadOnlyParameter(_) => SqlState::CANT_CHANGE_RUNTIME_PARAM,
976 VarError::UnknownParameter(_) => SqlState::UNDEFINED_OBJECT,
977 VarError::RequiresUnsafeMode { .. } => SqlState::CANT_CHANGE_RUNTIME_PARAM,
978 VarError::RequiresFeatureFlag { .. } => SqlState::CANT_CHANGE_RUNTIME_PARAM,
979 },
980 _ => SqlState::INTERNAL_ERROR,
981 },
982 AdapterError::ChangedPlan(_) => SqlState::FEATURE_NOT_SUPPORTED,
983 AdapterError::DuplicateCursor(_) => SqlState::DUPLICATE_CURSOR,
984 AdapterError::Eval(e) => eval_error_code(e),
989 AdapterError::Dataflow(e) => dataflow_error_code(e),
990 AdapterError::Explain(_) => SqlState::INTERNAL_ERROR,
991 AdapterError::IdExhaustionError => SqlState::INTERNAL_ERROR,
992 AdapterError::Internal(_) => SqlState::INTERNAL_ERROR,
993 AdapterError::IntrospectionDisabled { .. } => SqlState::FEATURE_NOT_SUPPORTED,
994 AdapterError::InvalidLogDependency { .. } => SqlState::FEATURE_NOT_SUPPORTED,
995 AdapterError::InvalidClusterReplicaAz { .. } => SqlState::FEATURE_NOT_SUPPORTED,
996 AdapterError::InvalidSetIsolationLevel => SqlState::ACTIVE_SQL_TRANSACTION,
997 AdapterError::InvalidSetCluster => SqlState::ACTIVE_SQL_TRANSACTION,
998 AdapterError::InvalidStorageClusterSize { .. } => SqlState::FEATURE_NOT_SUPPORTED,
999 AdapterError::SourceOrSinkSizeRequired { .. } => SqlState::FEATURE_NOT_SUPPORTED,
1000 AdapterError::InvalidTableMutationSelection { .. } => {
1001 SqlState::INVALID_TRANSACTION_STATE
1002 }
1003 AdapterError::ReadThenWriteDependencyLimitExceeded { .. } => {
1004 SqlState::PROGRAM_LIMIT_EXCEEDED
1005 }
1006 AdapterError::ConstraintViolation(NotNullViolation(_)) => SqlState::NOT_NULL_VIOLATION,
1007 AdapterError::CopyFormatError(_) => SqlState::BAD_COPY_FILE_FORMAT,
1008 AdapterError::ConcurrentClusterDrop => SqlState::INVALID_TRANSACTION_STATE,
1009 AdapterError::ConcurrentDependencyDrop { .. } => SqlState::UNDEFINED_OBJECT,
1010 AdapterError::ConcurrentDependencyMutation { .. } => {
1011 SqlState::T_R_SERIALIZATION_FAILURE
1012 }
1013 AdapterError::ReadThenWriteContention => SqlState::T_R_SERIALIZATION_FAILURE,
1014 AdapterError::ReadThenWriteTimestampTooFarAhead { .. } => {
1015 SqlState::INTERNAL_ERROR
1019 }
1020 AdapterError::CollectionUnreadable { .. } => SqlState::NO_DATA_FOUND,
1021 AdapterError::NoClusterReplicasAvailable { .. } => SqlState::FEATURE_NOT_SUPPORTED,
1022 AdapterError::OperationProhibitsTransaction(_) => SqlState::ACTIVE_SQL_TRANSACTION,
1023 AdapterError::OperationRequiresTransaction(_) => SqlState::NO_ACTIVE_SQL_TRANSACTION,
1024 AdapterError::ParseError(_) => SqlState::SYNTAX_ERROR,
1025 AdapterError::PlanError(PlanError::InvalidSchemaName) => SqlState::INVALID_SCHEMA_NAME,
1026 AdapterError::PlanError(PlanError::ColumnAlreadyExists { .. }) => {
1027 SqlState::DUPLICATE_COLUMN
1028 }
1029 AdapterError::PlanError(PlanError::UnknownParameter(_)) => {
1030 SqlState::UNDEFINED_PARAMETER
1031 }
1032 AdapterError::PlanError(PlanError::ParameterNotAllowed(_)) => {
1033 SqlState::UNDEFINED_PARAMETER
1034 }
1035 AdapterError::PlanError(PlanError::Unsupported { .. }) => {
1039 SqlState::FEATURE_NOT_SUPPORTED
1040 }
1041 AdapterError::PlanError(_) => SqlState::INTERNAL_ERROR,
1042 AdapterError::PreparedStatementExists(_) => SqlState::DUPLICATE_PSTATEMENT,
1043 AdapterError::ReadOnlyTransaction => SqlState::READ_ONLY_SQL_TRANSACTION,
1044 AdapterError::ReadWriteUnavailable => SqlState::INVALID_TRANSACTION_STATE,
1045 AdapterError::SingleStatementTransaction => SqlState::INVALID_TRANSACTION_STATE,
1046 AdapterError::StatementTimeout => SqlState::QUERY_CANCELED,
1047 AdapterError::Canceled => SqlState::QUERY_CANCELED,
1048 AdapterError::IdleInTransactionSessionTimeout => {
1049 SqlState::IDLE_IN_TRANSACTION_SESSION_TIMEOUT
1050 }
1051 AdapterError::RecursionLimit(_) => RECURSION_LIMIT_ERROR_CODE,
1052 AdapterError::RelationOutsideTimeDomain { .. } => SqlState::INVALID_TRANSACTION_STATE,
1053 AdapterError::ResourceExhaustion { .. } => SqlState::INSUFFICIENT_RESOURCES,
1054 AdapterError::ResultSize(_) => SqlState::OUT_OF_MEMORY,
1055 AdapterError::SubscribeFellBehind { .. } => SqlState::OUT_OF_MEMORY,
1056 AdapterError::PeekRowIterationLimitExceeded { .. } => SqlState::PROGRAM_LIMIT_EXCEEDED,
1057 AdapterError::SafeModeViolation(_) => SqlState::INTERNAL_ERROR,
1058 AdapterError::SubscribeOnlyTransaction => SqlState::INVALID_TRANSACTION_STATE,
1059 AdapterError::Optimizer(e) => match e {
1060 OptimizerError::PlanError(PlanError::InvalidSchemaName) => {
1061 SqlState::INVALID_SCHEMA_NAME
1062 }
1063 OptimizerError::PlanError(PlanError::ColumnAlreadyExists { .. }) => {
1064 SqlState::DUPLICATE_COLUMN
1065 }
1066 OptimizerError::PlanError(PlanError::UnknownParameter(_)) => {
1067 SqlState::UNDEFINED_PARAMETER
1068 }
1069 OptimizerError::PlanError(PlanError::ParameterNotAllowed(_)) => {
1070 SqlState::UNDEFINED_PARAMETER
1071 }
1072 OptimizerError::PlanError(PlanError::Unsupported { .. }) => {
1073 SqlState::FEATURE_NOT_SUPPORTED
1074 }
1075 OptimizerError::PlanError(_) => SqlState::INTERNAL_ERROR,
1076 OptimizerError::RecursionLimitError(_) => RECURSION_LIMIT_ERROR_CODE,
1077 OptimizerError::Internal(s) => {
1078 AdapterError::Internal(s.clone()).code() }
1080 OptimizerError::EvalError(e) => {
1081 AdapterError::Eval(e.clone()).code() }
1083 OptimizerError::TransformError(_) => SqlState::INTERNAL_ERROR,
1084 OptimizerError::UnmaterializableFunction(_) => SqlState::FEATURE_NOT_SUPPORTED,
1085 OptimizerError::UncallableFunction { .. } => SqlState::FEATURE_NOT_SUPPORTED,
1086 OptimizerError::UnsupportedTemporalExpression(_) => SqlState::FEATURE_NOT_SUPPORTED,
1087 OptimizerError::RestrictedFunction(_) => SqlState::INSUFFICIENT_PRIVILEGE,
1088 OptimizerError::InternalUnsafeMfpPlan(_) => SqlState::INTERNAL_ERROR,
1091 },
1092 AdapterError::UnallowedOnCluster { .. } => {
1093 SqlState::S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED
1094 }
1095 AdapterError::Unauthorized(_) => SqlState::INSUFFICIENT_PRIVILEGE,
1096 AdapterError::UnknownCursor(_) => SqlState::INVALID_CURSOR_NAME,
1097 AdapterError::UnknownPreparedStatement(_) => SqlState::UNDEFINED_PSTATEMENT,
1098 AdapterError::UnknownLoginRole(_) => SqlState::INVALID_AUTHORIZATION_SPECIFICATION,
1099 AdapterError::UnknownClusterReplica { .. } => SqlState::UNDEFINED_OBJECT,
1100 AdapterError::UnrecognizedConfigurationParam(_) => SqlState::UNDEFINED_OBJECT,
1101 AdapterError::Unsupported(..) => SqlState::FEATURE_NOT_SUPPORTED,
1102 AdapterError::UnavailableFeature { .. } => SqlState::FEATURE_NOT_SUPPORTED,
1103 AdapterError::Unstructured(_) => SqlState::INTERNAL_ERROR,
1104 AdapterError::UntargetedLogRead { .. } => SqlState::FEATURE_NOT_SUPPORTED,
1105 AdapterError::DDLTransactionOutcomeChanged => SqlState::T_R_SERIALIZATION_FAILURE,
1106 AdapterError::ClusterStateChanged { .. } => SqlState::T_R_SERIALIZATION_FAILURE,
1107 AdapterError::WriteOnlyTransaction => SqlState::INVALID_TRANSACTION_STATE,
1112 AdapterError::DDLOnlyTransaction => SqlState::INVALID_TRANSACTION_STATE,
1113 AdapterError::Storage(_) | AdapterError::Compute(_) | AdapterError::Orchestrator(_) => {
1114 SqlState::INTERNAL_ERROR
1115 }
1116 AdapterError::DependentObject(_) => SqlState::DEPENDENT_OBJECTS_STILL_EXIST,
1117 AdapterError::IndexInUse { .. } => SqlState::DEPENDENT_OBJECTS_STILL_EXIST,
1118 AdapterError::InvalidAlter(_, _) => SqlState::FEATURE_NOT_SUPPORTED,
1119 AdapterError::ConnectionValidation(_) => SqlState::SYSTEM_ERROR,
1120 AdapterError::MaterializedViewWouldNeverRefresh(_, _) => SqlState::DATA_EXCEPTION,
1122 AdapterError::InputNotReadableAtRefreshAtTime(_, _) => SqlState::DATA_EXCEPTION,
1123 AdapterError::RtrTimeout(_) => SqlState::QUERY_CANCELED,
1124 AdapterError::RtrDropFailure(_) => SqlState::UNDEFINED_OBJECT,
1125 AdapterError::UnreadableSinkCollection => SqlState::from_code("MZ009"),
1126 AdapterError::UserSessionsDisallowed => SqlState::from_code("MZ010"),
1127 AdapterError::NetworkPolicyDenied(_) => SqlState::from_code("MZ011"),
1128 AdapterError::ReadOnly => SqlState::READ_ONLY_SQL_TRANSACTION,
1131 AdapterError::AlterClusterTimeout => SqlState::QUERY_CANCELED,
1132 AdapterError::AlterClusterSuperseded => SqlState::QUERY_CANCELED,
1133 AdapterError::AlterClusterResourceExhausted => SqlState::INSUFFICIENT_RESOURCES,
1134 AdapterError::AlterClusterUnmanagedWhileReconfiguring => SqlState::OBJECT_IN_USE,
1135 AdapterError::AlterClusterUnmanagedWhileBursting => SqlState::OBJECT_IN_USE,
1136 AdapterError::AlterClusterScheduleWhileReconfiguring => SqlState::OBJECT_IN_USE,
1137 AdapterError::AlterClusterWaitOnScheduledCluster => SqlState::FEATURE_NOT_SUPPORTED,
1138 AdapterError::ReplacementSchemaMismatch(_) => SqlState::FEATURE_NOT_SUPPORTED,
1139 AdapterError::AuthenticationError(AuthenticationError::InvalidCredentials) => {
1140 SqlState::INVALID_PASSWORD
1141 }
1142 AdapterError::AuthenticationError(_) => SqlState::INVALID_AUTHORIZATION_SPECIFICATION,
1143 AdapterError::ReplaceMaterializedViewSealed { .. } => {
1144 SqlState::OBJECT_NOT_IN_PREREQUISITE_STATE
1145 }
1146 AdapterError::ImpossibleTimestampConstraints { .. } => SqlState::DATA_EXCEPTION,
1148 AdapterError::OidcGroupSyncFailed(_) => SqlState::INTERNAL_ERROR,
1149 AdapterError::BoundedStalenessExceeded { .. } => SqlState::T_R_SERIALIZATION_FAILURE,
1150 AdapterError::BoundedStalenessReadOnly => SqlState::READ_ONLY_SQL_TRANSACTION,
1153 AdapterError::BoundedStalenessRealTimeRecencyConflict => {
1154 SqlState::FEATURE_NOT_SUPPORTED
1155 }
1156 AdapterError::BoundedStalenessTimelineUnsupported => SqlState::FEATURE_NOT_SUPPORTED,
1157 }
1158 }
1159
1160 pub fn internal<E: std::fmt::Display>(context: &str, e: E) -> AdapterError {
1161 AdapterError::Internal(format!("{context}: {e}"))
1162 }
1163
1164 pub fn concurrent_dependency_drop_from_collection_missing(e: CollectionMissing) -> Self {
1171 AdapterError::ConcurrentDependencyDrop {
1172 dependency_kind: "collection",
1173 dependency_id: e.0.to_string(),
1174 }
1175 }
1176
1177 pub fn concurrent_dependency_drop_from_collection_lookup_error(
1179 e: CollectionLookupError,
1180 compute_instance: ComputeInstanceId,
1181 ) -> Self {
1182 match e {
1183 CollectionLookupError::InstanceMissing(id) => AdapterError::ConcurrentDependencyDrop {
1184 dependency_kind: "cluster",
1185 dependency_id: id.to_string(),
1186 },
1187 CollectionLookupError::CollectionMissing(id) => {
1188 AdapterError::ConcurrentDependencyDrop {
1189 dependency_kind: "collection",
1190 dependency_id: id.to_string(),
1191 }
1192 }
1193 CollectionLookupError::InstanceShutDown => AdapterError::ConcurrentDependencyDrop {
1194 dependency_kind: "cluster",
1195 dependency_id: compute_instance.to_string(),
1196 },
1197 }
1198 }
1199
1200 pub fn concurrent_dependency_drop_from_compute_lookup_error(
1202 e: compute_error::CollectionLookupError,
1203 ) -> Self {
1204 match e {
1205 compute_error::CollectionLookupError::InstanceMissing(id) => {
1206 AdapterError::ConcurrentDependencyDrop {
1207 dependency_kind: "cluster",
1208 dependency_id: id.to_string(),
1209 }
1210 }
1211 compute_error::CollectionLookupError::CollectionMissing(id) => {
1212 AdapterError::ConcurrentDependencyDrop {
1213 dependency_kind: "collection",
1214 dependency_id: id.to_string(),
1215 }
1216 }
1217 }
1218 }
1219
1220 pub fn concurrent_dependency_drop_from_instance_peek_error(
1221 e: mz_compute_client::controller::instance_client::PeekError,
1222 compute_instance: ComputeInstanceId,
1223 ) -> AdapterError {
1224 use mz_compute_client::controller::instance_client::PeekError::*;
1225 match e {
1226 ReplicaMissing(id) => AdapterError::ConcurrentDependencyDrop {
1227 dependency_kind: "replica",
1228 dependency_id: id.to_string(),
1229 },
1230 InstanceShutDown => AdapterError::ConcurrentDependencyDrop {
1231 dependency_kind: "cluster",
1232 dependency_id: compute_instance.to_string(),
1233 },
1234 e @ ReadHoldIdMismatch(_) => AdapterError::internal("instance peek error", e),
1235 e @ ReadHoldInsufficient(_) => AdapterError::internal("instance peek error", e),
1236 }
1237 }
1238
1239 pub fn concurrent_dependency_drop_from_collection_update_error(
1240 e: compute_error::CollectionUpdateError,
1241 ) -> Self {
1242 use compute_error::CollectionUpdateError::*;
1243 match e {
1244 InstanceMissing(id) => AdapterError::ConcurrentDependencyDrop {
1245 dependency_kind: "cluster",
1246 dependency_id: id.to_string(),
1247 },
1248 CollectionMissing(id) => AdapterError::ConcurrentDependencyDrop {
1249 dependency_kind: "collection",
1250 dependency_id: id.to_string(),
1251 },
1252 }
1253 }
1254
1255 pub fn concurrent_dependency_drop_from_peek_error(
1256 e: mz_compute_client::controller::error::PeekError,
1257 ) -> AdapterError {
1258 use mz_compute_client::controller::error::PeekError::*;
1259 match e {
1260 InstanceMissing(id) => AdapterError::ConcurrentDependencyDrop {
1261 dependency_kind: "cluster",
1262 dependency_id: id.to_string(),
1263 },
1264 CollectionMissing(id) => AdapterError::ConcurrentDependencyDrop {
1265 dependency_kind: "collection",
1266 dependency_id: id.to_string(),
1267 },
1268 ReplicaMissing(id) => AdapterError::ConcurrentDependencyDrop {
1269 dependency_kind: "replica",
1270 dependency_id: id.to_string(),
1271 },
1272 e @ (ReadHoldIdMismatch(_) | SinceViolation(_)) => {
1273 AdapterError::internal("peek error", e)
1274 }
1275 }
1276 }
1277
1278 pub fn concurrent_dependency_drop_from_dataflow_creation_error(
1279 e: compute_error::DataflowCreationError,
1280 ) -> Self {
1281 use compute_error::DataflowCreationError::*;
1282 match e {
1283 InstanceMissing(id) => AdapterError::ConcurrentDependencyDrop {
1284 dependency_kind: "cluster",
1285 dependency_id: id.to_string(),
1286 },
1287 CollectionMissing(id) => AdapterError::ConcurrentDependencyDrop {
1288 dependency_kind: "collection",
1289 dependency_id: id.to_string(),
1290 },
1291 ReplicaMissing(id) => AdapterError::ConcurrentDependencyDrop {
1292 dependency_kind: "replica",
1293 dependency_id: id.to_string(),
1294 },
1295 MissingAsOf | SinceViolation(..) | EmptyAsOfForSubscribe | EmptyAsOfForCopyTo => {
1296 AdapterError::internal("dataflow creation error", e)
1297 }
1298 }
1299 }
1300}
1301
1302impl fmt::Display for AdapterError {
1303 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1304 match self {
1305 AdapterError::AbsurdSubscribeBounds { as_of, up_to } => {
1306 write!(
1307 f,
1308 "subscription lower bound (`AS OF`) is greater than its upper bound (`UP TO`): \
1309 {as_of} > {up_to}",
1310 )
1311 }
1312 AdapterError::AmbiguousSystemColumnReference => {
1313 write!(
1314 f,
1315 "cannot use wildcard expansions or NATURAL JOINs in a view that depends on \
1316 system objects"
1317 )
1318 }
1319 AdapterError::ChangedPlan(e) => write!(f, "{}", e),
1320 AdapterError::Catalog(e) => e.fmt(f),
1321 AdapterError::DuplicateCursor(name) => {
1322 write!(f, "cursor {} already exists", name.quoted())
1323 }
1324 AdapterError::Eval(e) => e.fmt(f),
1325 AdapterError::Dataflow(e) => e.fmt(f),
1326 AdapterError::Explain(e) => e.fmt(f),
1327 AdapterError::IdExhaustionError => f.write_str("ID allocator exhausted all valid IDs"),
1328 AdapterError::Internal(e) => write!(f, "internal error: {}", e),
1329 AdapterError::IntrospectionDisabled { .. } => write!(
1330 f,
1331 "cannot read log sources of replica with disabled introspection"
1332 ),
1333 AdapterError::InvalidLogDependency { object_type, .. } => {
1334 write!(f, "{object_type} objects cannot depend on log sources")
1335 }
1336 AdapterError::InvalidClusterReplicaAz { az, expected: _ } => {
1337 write!(f, "unknown cluster replica availability zone {az}",)
1338 }
1339 AdapterError::InvalidSetIsolationLevel => write!(
1340 f,
1341 "SET TRANSACTION ISOLATION LEVEL must be called before any query"
1342 ),
1343 AdapterError::InvalidSetCluster => {
1344 write!(f, "SET cluster cannot be called in an active transaction")
1345 }
1346 AdapterError::InvalidStorageClusterSize { size, .. } => {
1347 write!(f, "unknown source size {size}")
1348 }
1349 AdapterError::SourceOrSinkSizeRequired { .. } => {
1350 write!(f, "must specify either cluster or size option")
1351 }
1352 AdapterError::InvalidTableMutationSelection { .. } => {
1353 write!(
1354 f,
1355 "invalid selection: every relation leaf must be a writable user table"
1356 )
1357 }
1358 AdapterError::ReadThenWriteDependencyLimitExceeded {
1359 max_rw_dependencies,
1360 } => {
1361 write!(
1362 f,
1363 "selection has too many transitive dependencies to validate (limit {max_rw_dependencies})"
1364 )
1365 }
1366 AdapterError::PeekRowIterationLimitExceeded { limit } => {
1367 write!(
1368 f,
1369 "query exceeded the configured row iteration limit of {limit} rows"
1370 )
1371 }
1372 AdapterError::ReplaceMaterializedViewSealed { name } => {
1373 write!(
1374 f,
1375 "materialized view {name} is sealed and thus cannot be replaced"
1376 )
1377 }
1378 AdapterError::ConstraintViolation(not_null_violation) => {
1379 write!(f, "{}", not_null_violation)
1380 }
1381 AdapterError::CopyFormatError(e) => write!(f, "{e}"),
1382 AdapterError::ConcurrentClusterDrop => {
1383 write!(f, "the transaction's active cluster has been dropped")
1384 }
1385 AdapterError::ConcurrentDependencyDrop {
1386 dependency_kind,
1387 dependency_id,
1388 } => {
1389 write!(f, "{dependency_kind} '{dependency_id}' was dropped")
1390 }
1391 AdapterError::ConcurrentDependencyMutation { dependency_id } => {
1392 write!(
1393 f,
1394 "catalog item '{dependency_id}' was concurrently modified"
1395 )
1396 }
1397 AdapterError::ReadThenWriteContention => {
1398 write!(
1399 f,
1400 "read-then-write exceeded maximum retry attempts under contention"
1401 )
1402 }
1403 AdapterError::ReadThenWriteTimestampTooFarAhead {
1404 target_timestamp,
1405 limit,
1406 } => {
1407 write!(
1408 f,
1409 "read-then-write would have to commit at {target_timestamp}, past the \
1410 highest timestamp the write timeline may be advanced to ({limit})"
1411 )
1412 }
1413 AdapterError::CollectionUnreadable { id } => {
1414 write!(f, "collection '{id}' is not readable at any timestamp")
1415 }
1416 AdapterError::NoClusterReplicasAvailable { name, .. } => {
1417 write!(
1418 f,
1419 "CLUSTER {} has no replicas available to service request",
1420 name.quoted()
1421 )
1422 }
1423 AdapterError::OperationProhibitsTransaction(op) => {
1424 write!(f, "{} cannot be run inside a transaction block", op)
1425 }
1426 AdapterError::OperationRequiresTransaction(op) => {
1427 write!(f, "{} can only be used in transaction blocks", op)
1428 }
1429 AdapterError::ParseError(e) => e.fmt(f),
1430 AdapterError::PlanError(e) => e.fmt(f),
1431 AdapterError::PreparedStatementExists(name) => {
1432 write!(f, "prepared statement {} already exists", name.quoted())
1433 }
1434 AdapterError::ReadOnlyTransaction => f.write_str("transaction in read-only mode"),
1435 AdapterError::SingleStatementTransaction => {
1436 f.write_str("this transaction can only execute a single statement")
1437 }
1438 AdapterError::ReadWriteUnavailable => {
1439 f.write_str("transaction read-write mode must be set before any query")
1440 }
1441 AdapterError::StatementTimeout => {
1442 write!(f, "canceling statement due to statement timeout")
1443 }
1444 AdapterError::Canceled => {
1445 write!(f, "canceling statement due to user request")
1446 }
1447 AdapterError::IdleInTransactionSessionTimeout => {
1448 write!(
1449 f,
1450 "terminating connection due to idle-in-transaction timeout"
1451 )
1452 }
1453 AdapterError::RecursionLimit(e) => e.fmt(f),
1454 AdapterError::RelationOutsideTimeDomain { .. } => {
1455 write!(
1456 f,
1457 "Transactions can only reference objects in the same timedomain. \
1458 See https://materialize.com/docs/sql/begin/#same-timedomain-error",
1459 )
1460 }
1461 AdapterError::ResourceExhaustion {
1462 resource_type,
1463 limit_name,
1464 desired,
1465 limit,
1466 current,
1467 } => {
1468 write!(
1469 f,
1470 "creating {resource_type} would violate {limit_name} limit (desired: {desired}, limit: {limit}, current: {current})"
1471 )
1472 }
1473 AdapterError::ResultSize(e) => write!(f, "{e}"),
1474 AdapterError::SubscribeFellBehind {
1475 buffered_bytes,
1476 max_buffered_bytes,
1477 } => {
1478 use bytesize::ByteSize;
1479 let buffered = u64::cast_from(*buffered_bytes);
1480 let max = u64::cast_from(*max_buffered_bytes);
1481 write!(
1482 f,
1483 "SUBSCRIBE fell behind: the client did not read results fast enough, so its backlog reached {}, exceeding the {} budget",
1484 ByteSize::b(buffered),
1485 ByteSize::b(max),
1486 )
1487 }
1488 AdapterError::SafeModeViolation(feature) => {
1489 write!(f, "cannot create {} in safe mode", feature)
1490 }
1491 AdapterError::SubscribeOnlyTransaction => {
1492 f.write_str("SUBSCRIBE in transactions must be the only read statement")
1493 }
1494 AdapterError::Optimizer(e) => e.fmt(f),
1495 AdapterError::UnallowedOnCluster {
1496 depends_on,
1497 cluster,
1498 } => {
1499 let items = depends_on.into_iter().map(|item| item.quoted()).join(", ");
1500 write!(
1501 f,
1502 "querying the following items {items} is not allowed from the {} cluster",
1503 cluster.quoted()
1504 )
1505 }
1506 AdapterError::Unauthorized(unauthorized) => {
1507 write!(f, "{unauthorized}")
1508 }
1509 AdapterError::UnknownCursor(name) => {
1510 write!(f, "cursor {} does not exist", name.quoted())
1511 }
1512 AdapterError::UnknownLoginRole(name) => {
1513 write!(f, "role {} does not exist", name.quoted())
1514 }
1515 AdapterError::Unsupported(features) => write!(f, "{} are not supported", features),
1516 AdapterError::Unstructured(e) => write!(f, "{}", e.display_with_causes()),
1517 AdapterError::WriteOnlyTransaction => f.write_str("transaction in write-only mode"),
1518 AdapterError::UnknownPreparedStatement(name) => {
1519 write!(f, "prepared statement {} does not exist", name.quoted())
1520 }
1521 AdapterError::UnknownClusterReplica {
1522 cluster_name,
1523 replica_name,
1524 } => write!(
1525 f,
1526 "cluster replica '{cluster_name}.{replica_name}' does not exist"
1527 ),
1528 AdapterError::UnrecognizedConfigurationParam(setting_name) => write!(
1529 f,
1530 "unrecognized configuration parameter {}",
1531 setting_name.quoted()
1532 ),
1533 AdapterError::UntargetedLogRead { .. } => {
1534 f.write_str("log source reads must target a replica")
1535 }
1536 AdapterError::DDLOnlyTransaction => f.write_str(
1537 "transactions which modify objects are restricted to just modifying objects",
1538 ),
1539 AdapterError::DDLTransactionOutcomeChanged => f.write_str(
1540 "a statement in this transaction would produce a different result at commit",
1541 ),
1542 AdapterError::ClusterStateChanged { cluster_id } => {
1543 write!(f, "cluster {cluster_id} was concurrently modified")
1544 }
1545 AdapterError::Storage(e) => e.fmt(f),
1546 AdapterError::Compute(e) => e.fmt(f),
1547 AdapterError::Orchestrator(e) => e.fmt(f),
1548 AdapterError::DependentObject(dependent_objects) => {
1549 let role_str = if dependent_objects.keys().count() == 1 {
1550 "role"
1551 } else {
1552 "roles"
1553 };
1554 write!(
1555 f,
1556 "{role_str} \"{}\" cannot be dropped because some objects depend on it",
1557 dependent_objects.keys().join(", ")
1558 )
1559 }
1560 AdapterError::IndexInUse { blocked } => {
1561 let dependents = fmt_dependents(blocked.iter().flat_map(|(_, deps)| deps).unique());
1564 match &blocked[..] {
1565 [(index_name, _)] => write!(
1566 f,
1567 "cannot drop index {}: still depended upon by {dependents}",
1568 index_name.quoted()
1569 ),
1570 blocked => write!(
1571 f,
1572 "cannot drop indexes {}: still depended upon by {dependents}",
1573 blocked.iter().map(|(name, _)| name.quoted()).join(", ")
1574 ),
1575 }
1576 }
1577 AdapterError::InvalidAlter(t, e) => {
1578 write!(f, "invalid ALTER {t}: {e}")
1579 }
1580 AdapterError::ConnectionValidation(e) => e.fmt(f),
1581 AdapterError::MaterializedViewWouldNeverRefresh(_, _) => {
1582 write!(
1583 f,
1584 "all the specified refreshes of the materialized view would be too far in the past, and thus they \
1585 would never happen"
1586 )
1587 }
1588 AdapterError::InputNotReadableAtRefreshAtTime(_, _) => {
1589 write!(
1590 f,
1591 "REFRESH AT requested for a time where not all the inputs are readable"
1592 )
1593 }
1594 AdapterError::RtrTimeout(_) => {
1595 write!(
1596 f,
1597 "timed out before ingesting the source's visible frontier when real-time-recency query issued"
1598 )
1599 }
1600 AdapterError::RtrDropFailure(_) => write!(
1601 f,
1602 "real-time source dropped before ingesting the upstream system's visible frontier"
1603 ),
1604 AdapterError::UnreadableSinkCollection => {
1605 write!(f, "collection is not readable at any time")
1606 }
1607 AdapterError::UserSessionsDisallowed => write!(f, "login blocked"),
1608 AdapterError::NetworkPolicyDenied(_) => write!(f, "session denied"),
1609 AdapterError::ReadOnly => write!(f, "cannot write in read-only mode"),
1610 AdapterError::AlterClusterTimeout => {
1611 write!(f, "canceling statement, provided timeout lapsed")
1612 }
1613 AdapterError::AlterClusterSuperseded => {
1614 write!(
1615 f,
1616 "cluster reconfiguration was superseded by another ALTER CLUSTER"
1617 )
1618 }
1619 AdapterError::AlterClusterResourceExhausted => {
1620 write!(
1621 f,
1622 "cluster reconfiguration could not provision its target within the available resource limits"
1623 )
1624 }
1625 AdapterError::AuthenticationError(e) => {
1626 write!(f, "authentication error {e}")
1627 }
1628 AdapterError::UnavailableFeature { feature, docs } => {
1629 write!(f, "{} is not supported in this environment.", feature)?;
1630 if let Some(docs) = docs {
1631 write!(
1632 f,
1633 " For more information consult the documentation at {docs}"
1634 )?;
1635 }
1636 Ok(())
1637 }
1638 AdapterError::AlterClusterUnmanagedWhileReconfiguring => {
1639 write!(
1640 f,
1641 "cannot convert cluster to unmanaged while a reconfiguration is in progress"
1642 )
1643 }
1644 AdapterError::AlterClusterUnmanagedWhileBursting => {
1645 write!(
1646 f,
1647 "cannot convert cluster to unmanaged while a hydration burst is in progress"
1648 )
1649 }
1650 AdapterError::AlterClusterScheduleWhileReconfiguring => {
1651 write!(
1652 f,
1653 "cannot change the cluster schedule while a reconfiguration is in progress"
1654 )
1655 }
1656 AdapterError::AlterClusterWaitOnScheduledCluster => {
1657 write!(
1658 f,
1659 "WAIT is not supported when the cluster SCHEDULE is not MANUAL"
1660 )
1661 }
1662 AdapterError::ReplacementSchemaMismatch(_) => {
1663 write!(f, "replacement schema differs from target schema")
1664 }
1665 AdapterError::ImpossibleTimestampConstraints { .. } => {
1666 write!(f, "could not find a valid timestamp for the query")
1667 }
1668 AdapterError::OidcGroupSyncFailed(msg) => {
1669 write!(f, "OIDC group-to-role sync failed: {msg}")
1670 }
1671 AdapterError::BoundedStalenessExceeded { bound, .. } => {
1672 write!(
1673 f,
1674 "cannot serve query under bounded staleness {}",
1675 humantime::format_duration(*bound),
1676 )
1677 }
1678 AdapterError::BoundedStalenessReadOnly => {
1679 f.write_str("writes are not permitted under bounded staleness isolation")
1680 }
1681 AdapterError::BoundedStalenessRealTimeRecencyConflict => {
1682 f.write_str("real_time_recency cannot be combined with bounded staleness isolation")
1683 }
1684 AdapterError::BoundedStalenessTimelineUnsupported => {
1685 f.write_str("bounded staleness isolation requires the EpochMilliseconds timeline")
1686 }
1687 }
1688 }
1689}
1690
1691impl From<anyhow::Error> for AdapterError {
1692 fn from(e: anyhow::Error) -> AdapterError {
1693 match e.downcast::<PlanError>() {
1694 Ok(plan_error) => AdapterError::PlanError(plan_error),
1695 Err(e) => AdapterError::Unstructured(e),
1696 }
1697 }
1698}
1699
1700impl From<TryFromIntError> for AdapterError {
1701 fn from(e: TryFromIntError) -> AdapterError {
1702 AdapterError::Unstructured(e.into())
1703 }
1704}
1705
1706impl From<TryFromDecimalError> for AdapterError {
1707 fn from(e: TryFromDecimalError) -> AdapterError {
1708 AdapterError::Unstructured(e.into())
1709 }
1710}
1711
1712impl From<mz_catalog::memory::error::Error> for AdapterError {
1713 fn from(e: mz_catalog::memory::error::Error) -> AdapterError {
1714 AdapterError::Catalog(e)
1715 }
1716}
1717
1718impl From<mz_catalog::durable::CatalogError> for AdapterError {
1719 fn from(e: mz_catalog::durable::CatalogError) -> Self {
1720 mz_catalog::memory::error::Error::from(e).into()
1721 }
1722}
1723
1724impl From<mz_catalog::durable::DurableCatalogError> for AdapterError {
1725 fn from(e: mz_catalog::durable::DurableCatalogError) -> Self {
1726 mz_catalog::durable::CatalogError::from(e).into()
1727 }
1728}
1729
1730impl From<EvalError> for AdapterError {
1731 fn from(e: EvalError) -> AdapterError {
1732 AdapterError::Eval(e)
1733 }
1734}
1735
1736impl From<mz_compute_client::protocol::response::PeekError> for AdapterError {
1737 fn from(error: mz_compute_client::protocol::response::PeekError) -> Self {
1738 use mz_compute_client::protocol::response::PeekError;
1739
1740 match error {
1741 PeekError::Dataflow(error) => AdapterError::Dataflow(error),
1742 PeekError::Unstructured(error) => AdapterError::Unstructured(anyhow::Error::msg(error)),
1743 PeekError::RowIterationLimitExceeded { limit } => {
1744 AdapterError::PeekRowIterationLimitExceeded { limit }
1745 }
1746 error @ PeekError::ResultExceedsMaxSize { .. } => {
1747 AdapterError::ResultSize(error.to_string())
1748 }
1749 }
1750 }
1751}
1752
1753impl From<ExplainError> for AdapterError {
1754 fn from(e: ExplainError) -> AdapterError {
1755 match e {
1756 ExplainError::RecursionLimitError(e) => AdapterError::RecursionLimit(e),
1757 e => AdapterError::Explain(e),
1758 }
1759 }
1760}
1761
1762impl From<mz_sql::catalog::CatalogError> for AdapterError {
1763 fn from(e: mz_sql::catalog::CatalogError) -> AdapterError {
1764 AdapterError::Catalog(mz_catalog::memory::error::Error::from(e))
1765 }
1766}
1767
1768impl From<PlanError> for AdapterError {
1769 fn from(e: PlanError) -> AdapterError {
1770 match e {
1771 PlanError::UnknownCursor(name) => AdapterError::UnknownCursor(name),
1772 _ => AdapterError::PlanError(e),
1773 }
1774 }
1775}
1776
1777impl From<OptimizerError> for AdapterError {
1778 fn from(e: OptimizerError) -> AdapterError {
1779 use OptimizerError::*;
1780 match e {
1781 PlanError(e) => Self::PlanError(e),
1782 RecursionLimitError(e) => Self::RecursionLimit(e),
1783 EvalError(e) => Self::Eval(e),
1784 InternalUnsafeMfpPlan(e) => Self::Internal(e),
1785 Internal(e) => Self::Internal(e),
1786 RestrictedFunction(func) => {
1787 Self::Unauthorized(mz_sql::rbac::UnauthorizedError::RestrictedSystemObject {
1788 object_name: format!("function {func}"),
1789 })
1790 }
1791 e => Self::Optimizer(e),
1792 }
1793 }
1794}
1795
1796impl From<NotNullViolation> for AdapterError {
1797 fn from(e: NotNullViolation) -> AdapterError {
1798 AdapterError::ConstraintViolation(e)
1799 }
1800}
1801
1802impl From<RecursionLimitError> for AdapterError {
1803 fn from(e: RecursionLimitError) -> AdapterError {
1804 AdapterError::RecursionLimit(e)
1805 }
1806}
1807
1808impl From<oneshot::error::RecvError> for AdapterError {
1809 fn from(e: oneshot::error::RecvError) -> AdapterError {
1810 AdapterError::Unstructured(e.into())
1811 }
1812}
1813
1814impl From<StorageError> for AdapterError {
1815 fn from(e: StorageError) -> Self {
1816 AdapterError::Storage(e)
1817 }
1818}
1819
1820impl From<compute_error::InstanceExists> for AdapterError {
1821 fn from(e: compute_error::InstanceExists) -> Self {
1822 AdapterError::Compute(e.into())
1823 }
1824}
1825
1826impl From<TimestampError> for AdapterError {
1827 fn from(e: TimestampError) -> Self {
1828 let e: EvalError = e.into();
1829 e.into()
1830 }
1831}
1832
1833impl From<mz_sql_parser::parser::ParserStatementError> for AdapterError {
1834 fn from(e: mz_sql_parser::parser::ParserStatementError) -> Self {
1835 AdapterError::ParseError(e)
1836 }
1837}
1838
1839impl From<VarError> for AdapterError {
1840 fn from(e: VarError) -> Self {
1841 let e: mz_catalog::memory::error::Error = e.into();
1842 e.into()
1843 }
1844}
1845
1846impl From<rbac::UnauthorizedError> for AdapterError {
1847 fn from(e: rbac::UnauthorizedError) -> Self {
1848 AdapterError::Unauthorized(e)
1849 }
1850}
1851
1852impl From<mz_sql_parser::ast::IdentError> for AdapterError {
1853 fn from(value: mz_sql_parser::ast::IdentError) -> Self {
1854 AdapterError::PlanError(PlanError::InvalidIdent(value))
1855 }
1856}
1857
1858impl From<mz_pgwire_common::ConnectionError> for AdapterError {
1859 fn from(value: mz_pgwire_common::ConnectionError) -> Self {
1860 match value {
1861 mz_pgwire_common::ConnectionError::TooManyConnections { current, limit } => {
1862 AdapterError::ResourceExhaustion {
1863 resource_type: "connection".into(),
1864 limit_name: "max_connections".into(),
1865 desired: (current + 1).to_string(),
1866 limit: limit.to_string(),
1867 current: current.to_string(),
1868 }
1869 }
1870 }
1871 }
1872}
1873
1874impl From<NetworkPolicyError> for AdapterError {
1875 fn from(value: NetworkPolicyError) -> Self {
1876 AdapterError::NetworkPolicyDenied(value)
1877 }
1878}
1879
1880impl From<ConnectionValidationError> for AdapterError {
1881 fn from(e: ConnectionValidationError) -> AdapterError {
1882 AdapterError::ConnectionValidation(e)
1883 }
1884}
1885
1886impl Error for AdapterError {}
1887
1888#[cfg(test)]
1889mod tests {
1890 use super::*;
1891
1892 #[mz_ore::test]
1893 fn alter_cluster_resource_exhausted_is_specific() {
1894 let error = AdapterError::AlterClusterResourceExhausted;
1895 assert_eq!(error.code(), SqlState::INSUFFICIENT_RESOURCES);
1896 assert_eq!(
1897 error.hint().as_deref(),
1898 Some(
1899 "Reduce the target resource use, or inspect \
1900 mz_internal.mz_cluster_reconfigurations for the retained outcome."
1901 )
1902 );
1903 assert_eq!(
1904 error.to_string(),
1905 "cluster reconfiguration could not provision its target within the available resource limits"
1906 );
1907 }
1908
1909 #[mz_ore::test]
1910 fn peek_row_iteration_limit_error_is_user_facing() {
1911 let response = AdapterError::PeekRowIterationLimitExceeded { limit: 1000 }
1912 .into_response(Severity::Error);
1913
1914 assert_eq!(response.code, SqlState::PROGRAM_LIMIT_EXCEEDED);
1915 assert_eq!(
1916 response.message,
1917 "query exceeded the configured row iteration limit of 1000 rows"
1918 );
1919 assert_eq!(
1920 response.detail.as_deref(),
1921 Some(
1922 "The query attempted to examine more than 1000 rows on a single compute worker. \
1923 This limit prevents long-running SELECT queries from delaying other work on the \
1924 cluster."
1925 )
1926 );
1927 assert!(
1928 response
1929 .hint
1930 .as_deref()
1931 .is_some_and(|hint| hint.contains("compute_peek_row_iteration_limit"))
1932 );
1933 }
1934
1935 #[mz_ore::test]
1936 fn structured_dataflow_error_preserves_message_and_code() {
1937 use mz_compute_client::protocol::response::PeekError;
1938
1939 let dataflow_error = DataflowError::from(EvalError::DivisionByZero);
1940 let expected_message = dataflow_error.to_string();
1941 let response =
1942 AdapterError::from(PeekError::from(dataflow_error)).into_response(Severity::Error);
1943
1944 assert_eq!(response.code, SqlState::DIVISION_BY_ZERO);
1945 assert_eq!(response.message, expected_message);
1946 }
1947}
1948
1949fn fmt_dependents<'a>(dependents: impl Iterator<Item = &'a (String, String)>) -> String {
1951 dependents
1952 .map(|(dependent_type, dependent_name)| {
1953 format!("{} {}", dependent_type, dependent_name.quoted())
1954 })
1955 .join(", ")
1956}