1use std::fmt;
11
12use chrono::{DateTime, Utc};
13use itertools::Itertools;
14use mz_controller::clusters::ClusterStatus;
15use mz_orchestrator::{OfflineReason, ServiceStatus};
16use mz_ore::str::{StrExt, separated};
17use mz_pgwire_common::{ErrorResponse, Severity};
18use mz_repr::adt::mz_acl_item::AclMode;
19use mz_repr::strconv;
20use mz_sql::ast::NoticeSeverity;
21use mz_sql::catalog::ErrorMessageObjectDescription;
22use mz_sql::plan::PlanNotice;
23use mz_sql::session::vars::IsolationLevel;
24use tokio_postgres::error::SqlState;
25
26use crate::TimestampExplanation;
27
28#[derive(Clone, Debug)]
33pub enum AdapterNotice {
34 DatabaseAlreadyExists {
35 name: String,
36 },
37 SchemaAlreadyExists {
38 name: String,
39 },
40 TableAlreadyExists {
41 name: String,
42 },
43 ObjectAlreadyExists {
44 name: String,
45 ty: &'static str,
46 },
47 DatabaseDoesNotExist {
48 name: String,
49 },
50 ClusterDoesNotExist {
51 name: String,
52 },
53 DefaultClusterDoesNotExist {
54 name: String,
55 kind: &'static str,
56 suggested_action: String,
57 },
58 NoResolvableSearchPathSchema {
59 search_path: Vec<String>,
60 },
61 ExistingTransactionInProgress,
62 ExplicitTransactionControlInImplicitTransaction,
63 UserRequested {
64 severity: NoticeSeverity,
65 },
66 ClusterReplicaStatusChanged {
67 cluster: String,
68 replica: String,
69 status: ClusterStatus,
70 time: DateTime<Utc>,
71 },
72 CascadeDroppedObject {
73 objects: Vec<String>,
74 },
75 DroppedActiveDatabase {
76 name: String,
77 },
78 DroppedActiveCluster {
79 name: String,
80 },
81 QueryTimestamp {
82 explanation: TimestampExplanation,
83 },
84 EqualSubscribeBounds {
85 bound: mz_repr::Timestamp,
86 },
87 QueryTrace {
88 trace_id: opentelemetry::trace::TraceId,
89 },
90 UnimplementedIsolationLevel {
91 isolation_level: String,
92 },
93 StrongSessionSerializable,
94 BadStartupSetting {
95 name: String,
96 reason: String,
97 },
98 RbacUserDisabled,
99 RoleMembershipAlreadyExists {
100 role_name: String,
101 member_name: String,
102 },
103 RoleMembershipDoesNotExists {
104 role_name: String,
105 member_name: String,
106 },
107 AutoRunOnCatalogServerCluster,
108 AlterIndexOwner {
109 name: String,
110 },
111 CannotRevoke {
112 object_description: ErrorMessageObjectDescription,
113 },
114 NonApplicablePrivilegeTypes {
115 non_applicable_privileges: AclMode,
116 object_description: ErrorMessageObjectDescription,
117 },
118 PlanNotice(PlanNotice),
119 UnknownSessionDatabase(String),
120 OptimizerNotice {
121 notice: String,
122 hint: String,
123 },
124 WebhookSourceCreated {
125 url: url::Url,
126 },
127 DroppedInUseIndex(DroppedInUseIndex),
128 PerReplicaLogRead {
129 log_names: Vec<String>,
130 },
131 VarDefaultUpdated {
132 role: Option<String>,
133 var_name: Option<String>,
134 },
135 StartupOnlyVarUpdated {
139 var_name: String,
140 },
141 Welcome(String),
142 PlanInsights(String),
143 IntrospectionClusterUsage,
144 AutoRouteIntrospectionQueriesUsage,
145 SingleReplicaSourcesOnMultiReplicaCluster {
148 cluster: String,
149 sources: Vec<String>,
150 },
151 OidcGroupSyncUnmatchedGroup {
153 group: String,
154 },
155 OidcGroupSyncReservedRole {
157 group: String,
158 },
159 OidcGroupSyncError {
161 message: String,
162 },
163}
164
165impl AdapterNotice {
166 pub fn into_response(self) -> ErrorResponse {
167 ErrorResponse {
168 severity: self.severity(),
169 code: self.code(),
170 message: self.to_string(),
171 detail: self.detail(),
172 hint: self.hint(),
173 position: None,
174 }
175 }
176
177 pub fn severity(&self) -> Severity {
179 match self {
180 AdapterNotice::DatabaseAlreadyExists { .. } => Severity::Notice,
181 AdapterNotice::SchemaAlreadyExists { .. } => Severity::Notice,
182 AdapterNotice::TableAlreadyExists { .. } => Severity::Notice,
183 AdapterNotice::ObjectAlreadyExists { .. } => Severity::Notice,
184 AdapterNotice::DatabaseDoesNotExist { .. } => Severity::Notice,
185 AdapterNotice::ClusterDoesNotExist { .. } => Severity::Notice,
186 AdapterNotice::DefaultClusterDoesNotExist { .. } => Severity::Notice,
187 AdapterNotice::NoResolvableSearchPathSchema { .. } => Severity::Notice,
188 AdapterNotice::ExistingTransactionInProgress => Severity::Warning,
189 AdapterNotice::ExplicitTransactionControlInImplicitTransaction => Severity::Warning,
190 AdapterNotice::UserRequested { severity } => match severity {
191 NoticeSeverity::Debug => Severity::Debug,
192 NoticeSeverity::Info => Severity::Info,
193 NoticeSeverity::Log => Severity::Log,
194 NoticeSeverity::Notice => Severity::Notice,
195 NoticeSeverity::Warning => Severity::Warning,
196 },
197 AdapterNotice::ClusterReplicaStatusChanged { .. } => Severity::Notice,
198 AdapterNotice::CascadeDroppedObject { .. } => Severity::Notice,
199 AdapterNotice::DroppedActiveDatabase { .. } => Severity::Notice,
200 AdapterNotice::DroppedActiveCluster { .. } => Severity::Notice,
201 AdapterNotice::QueryTimestamp { .. } => Severity::Notice,
202 AdapterNotice::EqualSubscribeBounds { .. } => Severity::Notice,
203 AdapterNotice::QueryTrace { .. } => Severity::Notice,
204 AdapterNotice::UnimplementedIsolationLevel { .. } => Severity::Notice,
205 AdapterNotice::StrongSessionSerializable => Severity::Notice,
206 AdapterNotice::BadStartupSetting { .. } => Severity::Notice,
207 AdapterNotice::RbacUserDisabled => Severity::Notice,
208 AdapterNotice::RoleMembershipAlreadyExists { .. } => Severity::Notice,
209 AdapterNotice::RoleMembershipDoesNotExists { .. } => Severity::Warning,
210 AdapterNotice::AutoRunOnCatalogServerCluster => Severity::Debug,
211 AdapterNotice::AlterIndexOwner { .. } => Severity::Warning,
212 AdapterNotice::CannotRevoke { .. } => Severity::Warning,
213 AdapterNotice::NonApplicablePrivilegeTypes { .. } => Severity::Notice,
214 AdapterNotice::PlanNotice(notice) => match notice {
215 PlanNotice::ObjectDoesNotExist { .. } => Severity::Notice,
216 PlanNotice::ColumnAlreadyExists { .. } => Severity::Notice,
217 PlanNotice::UpsertSinkKeyNotEnforced { .. } => Severity::Warning,
218 PlanNotice::ReplicaDiskOptionDeprecated { .. } => Severity::Notice,
219 },
220 AdapterNotice::UnknownSessionDatabase(_) => Severity::Notice,
221 AdapterNotice::OptimizerNotice { .. } => Severity::Notice,
222 AdapterNotice::WebhookSourceCreated { .. } => Severity::Notice,
223 AdapterNotice::DroppedInUseIndex { .. } => Severity::Notice,
224 AdapterNotice::PerReplicaLogRead { .. } => Severity::Notice,
225 AdapterNotice::VarDefaultUpdated { .. } => Severity::Notice,
226 AdapterNotice::StartupOnlyVarUpdated { .. } => Severity::Warning,
227 AdapterNotice::Welcome(_) => Severity::Notice,
228 AdapterNotice::PlanInsights(_) => Severity::Notice,
229 AdapterNotice::IntrospectionClusterUsage => Severity::Warning,
230 AdapterNotice::AutoRouteIntrospectionQueriesUsage => Severity::Warning,
231 AdapterNotice::SingleReplicaSourcesOnMultiReplicaCluster { .. } => Severity::Warning,
232 AdapterNotice::OidcGroupSyncUnmatchedGroup { .. } => Severity::Notice,
233 AdapterNotice::OidcGroupSyncReservedRole { .. } => Severity::Warning,
234 AdapterNotice::OidcGroupSyncError { .. } => Severity::Warning,
235 }
236 }
237
238 pub fn detail(&self) -> Option<String> {
240 match self {
241 AdapterNotice::PlanNotice(notice) => notice.detail(),
242 AdapterNotice::SingleReplicaSourcesOnMultiReplicaCluster { .. } => Some(
243 "Adding replicas to the cluster does not make these sources more fault tolerant \
244 and does not increase their ingestion throughput."
245 .into(),
246 ),
247 AdapterNotice::QueryTimestamp { explanation } => Some(format!("\n{explanation}")),
248 AdapterNotice::CascadeDroppedObject { objects } => Some(
249 objects
250 .iter()
251 .map(|obj_info| format!("drop cascades to {}", obj_info))
252 .join("\n"),
253 ),
254 _ => None,
255 }
256 }
257
258 pub fn hint(&self) -> Option<String> {
260 match self {
261 AdapterNotice::DatabaseDoesNotExist { name: _ } => Some("Create the database with CREATE DATABASE or pick an extant database with SET DATABASE = name. List available databases with SHOW DATABASES.".into()),
262 AdapterNotice::ClusterDoesNotExist { name: _ } => Some("Create the cluster with CREATE CLUSTER or pick an extant cluster with SET CLUSTER = name. List available clusters with SHOW CLUSTERS.".into()),
263 AdapterNotice::DefaultClusterDoesNotExist {
264 name: _,
265 kind: _,
266 suggested_action,
267 } => Some(suggested_action.clone()),
268 AdapterNotice::NoResolvableSearchPathSchema { search_path: _ } => Some("Create a schema with CREATE SCHEMA or pick an extant schema with SET SCHEMA = name. List available schemas with SHOW SCHEMAS.".into()),
269 AdapterNotice::DroppedActiveDatabase { name: _ } => Some("Choose a new active database by executing SET DATABASE = <name>.".into()),
270 AdapterNotice::DroppedActiveCluster { name: _ } => Some("Choose a new active cluster by executing SET CLUSTER = <name>.".into()),
271 AdapterNotice::ClusterReplicaStatusChanged { status, .. } => {
272 match status {
273 ServiceStatus::Offline(None)
274 | ServiceStatus::Offline(Some(OfflineReason::Initializing)) => Some("The cluster replica may be restarting or going offline.".into()),
275 ServiceStatus::Offline(Some(OfflineReason::OomKilled)) => Some("The cluster replica may have run out of memory and been killed.".into()),
276 ServiceStatus::Online => None,
277 }
278 },
279 AdapterNotice::RbacUserDisabled => Some("To enable RBAC globally run `ALTER SYSTEM SET enable_rbac_checks TO TRUE` as a superuser. TO enable RBAC for just this session run `SET enable_session_rbac_checks TO TRUE`.".into()),
280 AdapterNotice::AlterIndexOwner {name: _} => Some("Change the ownership of the index's relation, instead.".into()),
281 AdapterNotice::UnknownSessionDatabase(_) => Some(
282 "Create the database with CREATE DATABASE \
283 or pick an extant database with SET DATABASE = name. \
284 List available databases with SHOW DATABASES."
285 .into(),
286 ),
287 AdapterNotice::OptimizerNotice { notice: _, hint } => Some(hint.clone()),
288 AdapterNotice::DroppedInUseIndex(..) => Some("To free up the resources used by the index, recreate all the above-mentioned objects.".into()),
289 AdapterNotice::IntrospectionClusterUsage => Some("Use the new name instead.".into()),
290 AdapterNotice::AutoRouteIntrospectionQueriesUsage => Some("Use the new name instead.".into()),
291 AdapterNotice::SingleReplicaSourcesOnMultiReplicaCluster { .. } => Some(
292 "To achieve fault tolerance for other objects in the cluster, consider moving \
293 these sources to a separate cluster with a single replica."
294 .into(),
295 ),
296 _ => None
297 }
298 }
299
300 pub fn code(&self) -> SqlState {
302 match self {
303 AdapterNotice::DatabaseAlreadyExists { .. } => SqlState::DUPLICATE_DATABASE,
304 AdapterNotice::SchemaAlreadyExists { .. } => SqlState::DUPLICATE_SCHEMA,
305 AdapterNotice::TableAlreadyExists { .. } => SqlState::DUPLICATE_TABLE,
306 AdapterNotice::ObjectAlreadyExists { .. } => SqlState::DUPLICATE_OBJECT,
307 AdapterNotice::DatabaseDoesNotExist { .. } => SqlState::from_code("MZ006"),
308 AdapterNotice::ClusterDoesNotExist { .. } => SqlState::from_code("MZ007"),
309 AdapterNotice::NoResolvableSearchPathSchema { .. } => SqlState::from_code("MZ008"),
310 AdapterNotice::ExistingTransactionInProgress => SqlState::ACTIVE_SQL_TRANSACTION,
311 AdapterNotice::ExplicitTransactionControlInImplicitTransaction => {
312 SqlState::NO_ACTIVE_SQL_TRANSACTION
313 }
314 AdapterNotice::UserRequested { severity } => match severity {
315 NoticeSeverity::Warning => SqlState::WARNING,
316 _ => SqlState::SUCCESSFUL_COMPLETION,
317 },
318 AdapterNotice::ClusterReplicaStatusChanged { .. } => SqlState::SUCCESSFUL_COMPLETION,
319 AdapterNotice::CascadeDroppedObject { .. } => SqlState::SUCCESSFUL_COMPLETION,
320 AdapterNotice::DroppedActiveDatabase { .. } => SqlState::from_code("MZ002"),
321 AdapterNotice::DroppedActiveCluster { .. } => SqlState::from_code("MZ003"),
322 AdapterNotice::QueryTimestamp { .. } => SqlState::SUCCESSFUL_COMPLETION,
323 AdapterNotice::EqualSubscribeBounds { .. } => SqlState::SUCCESSFUL_COMPLETION,
324 AdapterNotice::QueryTrace { .. } => SqlState::SUCCESSFUL_COMPLETION,
325 AdapterNotice::UnimplementedIsolationLevel { .. } => SqlState::SUCCESSFUL_COMPLETION,
326 AdapterNotice::StrongSessionSerializable => SqlState::SUCCESSFUL_COMPLETION,
327 AdapterNotice::BadStartupSetting { .. } => SqlState::SUCCESSFUL_COMPLETION,
328 AdapterNotice::RbacUserDisabled => SqlState::SUCCESSFUL_COMPLETION,
329 AdapterNotice::RoleMembershipAlreadyExists { .. } => SqlState::SUCCESSFUL_COMPLETION,
330 AdapterNotice::RoleMembershipDoesNotExists { .. } => SqlState::WARNING,
331 AdapterNotice::AutoRunOnCatalogServerCluster => SqlState::SUCCESSFUL_COMPLETION,
332 AdapterNotice::AlterIndexOwner { .. } => SqlState::WARNING,
333 AdapterNotice::CannotRevoke { .. } => SqlState::WARNING_PRIVILEGE_NOT_REVOKED,
334 AdapterNotice::NonApplicablePrivilegeTypes { .. } => SqlState::SUCCESSFUL_COMPLETION,
335 AdapterNotice::PlanNotice(plan) => match plan {
336 PlanNotice::ObjectDoesNotExist { .. } => SqlState::UNDEFINED_OBJECT,
337 PlanNotice::ColumnAlreadyExists { .. } => SqlState::DUPLICATE_COLUMN,
338 PlanNotice::UpsertSinkKeyNotEnforced { .. } => SqlState::WARNING,
339 PlanNotice::ReplicaDiskOptionDeprecated { .. } => {
340 SqlState::WARNING_DEPRECATED_FEATURE
341 }
342 },
343 AdapterNotice::UnknownSessionDatabase(_) => SqlState::from_code("MZ004"),
344 AdapterNotice::DefaultClusterDoesNotExist { .. } => SqlState::from_code("MZ005"),
345 AdapterNotice::OptimizerNotice { .. } => SqlState::SUCCESSFUL_COMPLETION,
346 AdapterNotice::DroppedInUseIndex { .. } => SqlState::SUCCESSFUL_COMPLETION,
347 AdapterNotice::WebhookSourceCreated { .. } => SqlState::SUCCESSFUL_COMPLETION,
348 AdapterNotice::PerReplicaLogRead { .. } => SqlState::SUCCESSFUL_COMPLETION,
349 AdapterNotice::VarDefaultUpdated { .. } => SqlState::SUCCESSFUL_COMPLETION,
350 AdapterNotice::StartupOnlyVarUpdated { .. } => SqlState::WARNING,
351 AdapterNotice::Welcome(_) => SqlState::SUCCESSFUL_COMPLETION,
352 AdapterNotice::PlanInsights(_) => SqlState::from_code("MZ001"),
353 AdapterNotice::IntrospectionClusterUsage => SqlState::WARNING,
354 AdapterNotice::AutoRouteIntrospectionQueriesUsage => SqlState::WARNING,
355 AdapterNotice::SingleReplicaSourcesOnMultiReplicaCluster { .. } => SqlState::WARNING,
356 AdapterNotice::OidcGroupSyncUnmatchedGroup { .. } => SqlState::SUCCESSFUL_COMPLETION,
357 AdapterNotice::OidcGroupSyncReservedRole { .. } => SqlState::WARNING,
358 AdapterNotice::OidcGroupSyncError { .. } => SqlState::WARNING,
359 }
360 }
361}
362
363impl fmt::Display for AdapterNotice {
364 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
365 match self {
366 AdapterNotice::DatabaseAlreadyExists { name } => {
367 write!(f, "database {} already exists, skipping", name.quoted())
368 }
369 AdapterNotice::SchemaAlreadyExists { name } => {
370 write!(f, "schema {} already exists, skipping", name.quoted())
371 }
372 AdapterNotice::TableAlreadyExists { name } => {
373 write!(f, "table {} already exists, skipping", name.quoted())
374 }
375 AdapterNotice::ObjectAlreadyExists { name, ty } => {
376 write!(f, "{} {} already exists, skipping", ty, name.quoted())
377 }
378 AdapterNotice::DatabaseDoesNotExist { name } => {
379 write!(f, "database {} does not exist", name.quoted())
380 }
381 AdapterNotice::CascadeDroppedObject { objects } => {
382 write!(f, "drop cascades to {} other objects", objects.len())
383 }
384 AdapterNotice::ClusterDoesNotExist { name } => {
385 write!(f, "cluster {} does not exist", name.quoted())
386 }
387 AdapterNotice::DefaultClusterDoesNotExist { kind, name, .. } => {
388 write!(f, "{kind} default cluster {} does not exist", name.quoted())
389 }
390 AdapterNotice::NoResolvableSearchPathSchema { search_path } => {
391 write!(
392 f,
393 "no schema on the search path exists: {}",
394 search_path.join(", ")
395 )
396 }
397 AdapterNotice::ExistingTransactionInProgress => {
398 write!(f, "there is already a transaction in progress")
399 }
400 AdapterNotice::ExplicitTransactionControlInImplicitTransaction => {
401 write!(f, "there is no transaction in progress")
402 }
403 AdapterNotice::UserRequested { severity } => {
404 write!(f, "raised a test {}", severity.to_string().to_lowercase())
405 }
406 AdapterNotice::ClusterReplicaStatusChanged {
407 cluster,
408 replica,
409 status,
410 time,
411 } => {
412 let mut time_buf = String::new();
413 strconv::format_timestamptz(&mut time_buf, time);
414 write!(
415 f,
416 "cluster replica {}.{} changed status to {} at {}",
417 cluster,
418 replica,
419 status.as_kebab_case_str().quoted(),
420 time_buf,
421 )?;
422 Ok(())
423 }
424 AdapterNotice::DroppedActiveDatabase { name } => {
425 write!(f, "active database {} has been dropped", name.quoted())
426 }
427 AdapterNotice::DroppedActiveCluster { name } => {
428 write!(f, "active cluster {} has been dropped", name.quoted())
429 }
430 AdapterNotice::QueryTimestamp { .. } => write!(f, "EXPLAIN TIMESTAMP for query"),
431 AdapterNotice::EqualSubscribeBounds { bound } => {
432 write!(
433 f,
434 "subscribe as of {bound} (inclusive) up to the same bound {bound} (exclusive) is guaranteed to be empty"
435 )
436 }
437 AdapterNotice::QueryTrace { trace_id } => {
438 write!(f, "trace id: {}", trace_id)
439 }
440 AdapterNotice::UnimplementedIsolationLevel { isolation_level } => {
441 write!(
442 f,
443 "transaction isolation level {isolation_level} is unimplemented, the session will be upgraded to {}",
444 IsolationLevel::Serializable
445 )
446 }
447 AdapterNotice::StrongSessionSerializable => {
448 write!(
449 f,
450 "The Strong Session Serializable isolation level may exhibit consistency violations when reading from catalog objects",
451 )
452 }
453 AdapterNotice::BadStartupSetting { name, reason } => {
454 write!(f, "startup setting {name} not set: {reason}")
455 }
456 AdapterNotice::RbacUserDisabled => {
457 write!(
458 f,
459 "RBAC is disabled so no role attributes or object ownership will be considered \
460 when executing statements"
461 )
462 }
463 AdapterNotice::RoleMembershipAlreadyExists {
464 role_name,
465 member_name,
466 } => write!(
467 f,
468 "role \"{member_name}\" is already a member of role \"{role_name}\""
469 ),
470 AdapterNotice::RoleMembershipDoesNotExists {
471 role_name,
472 member_name,
473 } => write!(
474 f,
475 "role \"{member_name}\" is not a member of role \"{role_name}\""
476 ),
477 AdapterNotice::AutoRunOnCatalogServerCluster => write!(
478 f,
479 "query was automatically run on the \"mz_catalog_server\" cluster"
480 ),
481 AdapterNotice::AlterIndexOwner { name } => {
482 write!(f, "cannot change owner of {}", name.quoted())
483 }
484 AdapterNotice::CannotRevoke { object_description } => {
485 write!(f, "no privileges could be revoked for {object_description}")
486 }
487 AdapterNotice::NonApplicablePrivilegeTypes {
488 non_applicable_privileges,
489 object_description,
490 } => {
491 write!(
492 f,
493 "non-applicable privilege types {} for {}",
494 non_applicable_privileges.to_error_string(),
495 object_description,
496 )
497 }
498 AdapterNotice::PlanNotice(plan) => plan.fmt(f),
499 AdapterNotice::UnknownSessionDatabase(name) => {
500 write!(f, "session database {} does not exist", name.quoted())
501 }
502 AdapterNotice::OptimizerNotice { notice, hint: _ } => notice.fmt(f),
503 AdapterNotice::WebhookSourceCreated { url } => {
504 write!(f, "URL to POST data is '{url}'")
505 }
506 AdapterNotice::DroppedInUseIndex(DroppedInUseIndex {
507 index_name,
508 dependant_objects,
509 }) => {
510 write!(
511 f,
512 "The dropped index {index_name} is being used by the following objects: {}. The index is now dropped from the catalog, but it will continue to be maintained and take up resources until all dependent objects are dropped, altered, or Materialize is restarted!",
513 separated(", ", dependant_objects)
514 )
515 }
516 AdapterNotice::PerReplicaLogRead { log_names } => {
517 write!(
518 f,
519 "Queried introspection relations: {}. Unlike other objects in Materialize, results from querying these objects depend on the current values of the `cluster` and `cluster_replica` session variables.",
520 log_names.join(", ")
521 )
522 }
523 AdapterNotice::VarDefaultUpdated { role, var_name } => {
524 let vars = match var_name {
525 Some(name) => format!("variable {} was", name.quoted()),
526 None => "variables were".to_string(),
527 };
528 let target = match role {
529 Some(role_name) => role_name.quoted().to_string(),
530 None => "the system".to_string(),
531 };
532 write!(
533 f,
534 "{vars} updated for {target}, this will have no effect on the current session"
535 )
536 }
537 AdapterNotice::StartupOnlyVarUpdated { var_name } => write!(
538 f,
539 "changes to {} only take effect when environmentd restarts",
540 var_name.quoted()
541 ),
542 AdapterNotice::Welcome(message) => message.fmt(f),
543 AdapterNotice::PlanInsights(message) => message.fmt(f),
544 AdapterNotice::IntrospectionClusterUsage => write!(
545 f,
546 "The mz_introspection cluster has been renamed to mz_catalog_server."
547 ),
548 AdapterNotice::AutoRouteIntrospectionQueriesUsage => write!(
549 f,
550 "The auto_route_introspection_queries variable has been renamed to auto_route_catalog_queries."
551 ),
552 AdapterNotice::SingleReplicaSourcesOnMultiReplicaCluster { cluster, sources } => {
553 const MAX_LISTED_SOURCES: usize = 3;
555 write!(
556 f,
557 "cluster {} has more than one replica, but the following sources in it always \
558 run on only the first replica: {}",
559 cluster.quoted(),
560 separated(
561 ", ",
562 sources.iter().take(MAX_LISTED_SOURCES).map(|n| n.quoted())
563 ),
564 )?;
565 if sources.len() > MAX_LISTED_SOURCES {
566 write!(f, ", and {} more", sources.len() - MAX_LISTED_SOURCES)?;
567 }
568 Ok(())
569 }
570 AdapterNotice::OidcGroupSyncUnmatchedGroup { group } => {
571 write!(
572 f,
573 "OIDC group \"{}\" has no matching Materialize role, skipping",
574 group
575 )
576 }
577 AdapterNotice::OidcGroupSyncReservedRole { group } => {
578 write!(
579 f,
580 "OIDC group \"{}\" maps to a reserved role name, skipping",
581 group
582 )
583 }
584 AdapterNotice::OidcGroupSyncError { message } => {
585 write!(f, "OIDC group-to-role sync failed: {}", message)
586 }
587 }
588 }
589}
590
591#[derive(Clone, Debug)]
592pub struct DroppedInUseIndex {
593 pub index_name: String,
594 pub dependant_objects: Vec<String>,
595}
596
597impl From<PlanNotice> for AdapterNotice {
598 fn from(notice: PlanNotice) -> AdapterNotice {
599 AdapterNotice::PlanNotice(notice)
600 }
601}