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 OidcGroupSyncReservedRole {
153 group: String,
154 },
155 OidcGroupSyncError {
157 message: String,
158 },
159}
160
161impl AdapterNotice {
162 pub fn into_response(self) -> ErrorResponse {
163 ErrorResponse {
164 severity: self.severity(),
165 code: self.code(),
166 message: self.to_string(),
167 detail: self.detail(),
168 hint: self.hint(),
169 position: None,
170 }
171 }
172
173 pub fn severity(&self) -> Severity {
175 match self {
176 AdapterNotice::DatabaseAlreadyExists { .. } => Severity::Notice,
177 AdapterNotice::SchemaAlreadyExists { .. } => Severity::Notice,
178 AdapterNotice::TableAlreadyExists { .. } => Severity::Notice,
179 AdapterNotice::ObjectAlreadyExists { .. } => Severity::Notice,
180 AdapterNotice::DatabaseDoesNotExist { .. } => Severity::Notice,
181 AdapterNotice::ClusterDoesNotExist { .. } => Severity::Notice,
182 AdapterNotice::DefaultClusterDoesNotExist { .. } => Severity::Notice,
183 AdapterNotice::NoResolvableSearchPathSchema { .. } => Severity::Notice,
184 AdapterNotice::ExistingTransactionInProgress => Severity::Warning,
185 AdapterNotice::ExplicitTransactionControlInImplicitTransaction => Severity::Warning,
186 AdapterNotice::UserRequested { severity } => match severity {
187 NoticeSeverity::Debug => Severity::Debug,
188 NoticeSeverity::Info => Severity::Info,
189 NoticeSeverity::Log => Severity::Log,
190 NoticeSeverity::Notice => Severity::Notice,
191 NoticeSeverity::Warning => Severity::Warning,
192 },
193 AdapterNotice::ClusterReplicaStatusChanged { .. } => Severity::Notice,
194 AdapterNotice::CascadeDroppedObject { .. } => Severity::Notice,
195 AdapterNotice::DroppedActiveDatabase { .. } => Severity::Notice,
196 AdapterNotice::DroppedActiveCluster { .. } => Severity::Notice,
197 AdapterNotice::QueryTimestamp { .. } => Severity::Notice,
198 AdapterNotice::EqualSubscribeBounds { .. } => Severity::Notice,
199 AdapterNotice::QueryTrace { .. } => Severity::Notice,
200 AdapterNotice::UnimplementedIsolationLevel { .. } => Severity::Notice,
201 AdapterNotice::StrongSessionSerializable => Severity::Notice,
202 AdapterNotice::BadStartupSetting { .. } => Severity::Notice,
203 AdapterNotice::RbacUserDisabled => Severity::Notice,
204 AdapterNotice::RoleMembershipAlreadyExists { .. } => Severity::Notice,
205 AdapterNotice::RoleMembershipDoesNotExists { .. } => Severity::Warning,
206 AdapterNotice::AutoRunOnCatalogServerCluster => Severity::Debug,
207 AdapterNotice::AlterIndexOwner { .. } => Severity::Warning,
208 AdapterNotice::CannotRevoke { .. } => Severity::Warning,
209 AdapterNotice::NonApplicablePrivilegeTypes { .. } => Severity::Notice,
210 AdapterNotice::PlanNotice(notice) => match notice {
211 PlanNotice::ObjectDoesNotExist { .. } => Severity::Notice,
212 PlanNotice::ColumnAlreadyExists { .. } => Severity::Notice,
213 PlanNotice::UpsertSinkKeyNotEnforced { .. } => Severity::Warning,
214 PlanNotice::ReplicaDiskOptionDeprecated { .. } => Severity::Notice,
215 },
216 AdapterNotice::UnknownSessionDatabase(_) => Severity::Notice,
217 AdapterNotice::OptimizerNotice { .. } => Severity::Notice,
218 AdapterNotice::WebhookSourceCreated { .. } => Severity::Notice,
219 AdapterNotice::DroppedInUseIndex { .. } => Severity::Notice,
220 AdapterNotice::PerReplicaLogRead { .. } => Severity::Notice,
221 AdapterNotice::VarDefaultUpdated { .. } => Severity::Notice,
222 AdapterNotice::StartupOnlyVarUpdated { .. } => Severity::Warning,
223 AdapterNotice::Welcome(_) => Severity::Notice,
224 AdapterNotice::PlanInsights(_) => Severity::Notice,
225 AdapterNotice::IntrospectionClusterUsage => Severity::Warning,
226 AdapterNotice::AutoRouteIntrospectionQueriesUsage => Severity::Warning,
227 AdapterNotice::SingleReplicaSourcesOnMultiReplicaCluster { .. } => Severity::Warning,
228 AdapterNotice::OidcGroupSyncReservedRole { .. } => Severity::Warning,
229 AdapterNotice::OidcGroupSyncError { .. } => Severity::Warning,
230 }
231 }
232
233 pub fn detail(&self) -> Option<String> {
235 match self {
236 AdapterNotice::PlanNotice(notice) => notice.detail(),
237 AdapterNotice::SingleReplicaSourcesOnMultiReplicaCluster { .. } => Some(
238 "Adding replicas to the cluster does not make these sources more fault tolerant \
239 and does not increase their ingestion throughput."
240 .into(),
241 ),
242 AdapterNotice::QueryTimestamp { explanation } => Some(format!("\n{explanation}")),
243 AdapterNotice::CascadeDroppedObject { objects } => Some(
244 objects
245 .iter()
246 .map(|obj_info| format!("drop cascades to {}", obj_info))
247 .join("\n"),
248 ),
249 _ => None,
250 }
251 }
252
253 pub fn hint(&self) -> Option<String> {
255 match self {
256 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()),
257 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()),
258 AdapterNotice::DefaultClusterDoesNotExist {
259 name: _,
260 kind: _,
261 suggested_action,
262 } => Some(suggested_action.clone()),
263 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()),
264 AdapterNotice::DroppedActiveDatabase { name: _ } => Some("Choose a new active database by executing SET DATABASE = <name>.".into()),
265 AdapterNotice::DroppedActiveCluster { name: _ } => Some("Choose a new active cluster by executing SET CLUSTER = <name>.".into()),
266 AdapterNotice::ClusterReplicaStatusChanged { status, .. } => {
267 match status {
268 ServiceStatus::Offline(None)
269 | ServiceStatus::Offline(Some(OfflineReason::Initializing)) => Some("The cluster replica may be restarting or going offline.".into()),
270 ServiceStatus::Offline(Some(OfflineReason::OomKilled)) => Some("The cluster replica may have run out of memory and been killed.".into()),
271 ServiceStatus::Online => None,
272 }
273 },
274 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()),
275 AdapterNotice::AlterIndexOwner {name: _} => Some("Change the ownership of the index's relation, instead.".into()),
276 AdapterNotice::UnknownSessionDatabase(_) => Some(
277 "Create the database with CREATE DATABASE \
278 or pick an extant database with SET DATABASE = name. \
279 List available databases with SHOW DATABASES."
280 .into(),
281 ),
282 AdapterNotice::OptimizerNotice { notice: _, hint } => Some(hint.clone()),
283 AdapterNotice::DroppedInUseIndex(..) => Some("To free up the resources used by the index, recreate all the above-mentioned objects.".into()),
284 AdapterNotice::IntrospectionClusterUsage => Some("Use the new name instead.".into()),
285 AdapterNotice::AutoRouteIntrospectionQueriesUsage => Some("Use the new name instead.".into()),
286 AdapterNotice::SingleReplicaSourcesOnMultiReplicaCluster { .. } => Some(
287 "To achieve fault tolerance for other objects in the cluster, consider moving \
288 these sources to a separate cluster with a single replica."
289 .into(),
290 ),
291 _ => None
292 }
293 }
294
295 pub fn code(&self) -> SqlState {
297 match self {
298 AdapterNotice::DatabaseAlreadyExists { .. } => SqlState::DUPLICATE_DATABASE,
299 AdapterNotice::SchemaAlreadyExists { .. } => SqlState::DUPLICATE_SCHEMA,
300 AdapterNotice::TableAlreadyExists { .. } => SqlState::DUPLICATE_TABLE,
301 AdapterNotice::ObjectAlreadyExists { .. } => SqlState::DUPLICATE_OBJECT,
302 AdapterNotice::DatabaseDoesNotExist { .. } => SqlState::from_code("MZ006"),
303 AdapterNotice::ClusterDoesNotExist { .. } => SqlState::from_code("MZ007"),
304 AdapterNotice::NoResolvableSearchPathSchema { .. } => SqlState::from_code("MZ008"),
305 AdapterNotice::ExistingTransactionInProgress => SqlState::ACTIVE_SQL_TRANSACTION,
306 AdapterNotice::ExplicitTransactionControlInImplicitTransaction => {
307 SqlState::NO_ACTIVE_SQL_TRANSACTION
308 }
309 AdapterNotice::UserRequested { severity } => match severity {
310 NoticeSeverity::Warning => SqlState::WARNING,
311 _ => SqlState::SUCCESSFUL_COMPLETION,
312 },
313 AdapterNotice::ClusterReplicaStatusChanged { .. } => SqlState::SUCCESSFUL_COMPLETION,
314 AdapterNotice::CascadeDroppedObject { .. } => SqlState::SUCCESSFUL_COMPLETION,
315 AdapterNotice::DroppedActiveDatabase { .. } => SqlState::from_code("MZ002"),
316 AdapterNotice::DroppedActiveCluster { .. } => SqlState::from_code("MZ003"),
317 AdapterNotice::QueryTimestamp { .. } => SqlState::SUCCESSFUL_COMPLETION,
318 AdapterNotice::EqualSubscribeBounds { .. } => SqlState::SUCCESSFUL_COMPLETION,
319 AdapterNotice::QueryTrace { .. } => SqlState::SUCCESSFUL_COMPLETION,
320 AdapterNotice::UnimplementedIsolationLevel { .. } => SqlState::SUCCESSFUL_COMPLETION,
321 AdapterNotice::StrongSessionSerializable => SqlState::SUCCESSFUL_COMPLETION,
322 AdapterNotice::BadStartupSetting { .. } => SqlState::SUCCESSFUL_COMPLETION,
323 AdapterNotice::RbacUserDisabled => SqlState::SUCCESSFUL_COMPLETION,
324 AdapterNotice::RoleMembershipAlreadyExists { .. } => SqlState::SUCCESSFUL_COMPLETION,
325 AdapterNotice::RoleMembershipDoesNotExists { .. } => SqlState::WARNING,
326 AdapterNotice::AutoRunOnCatalogServerCluster => SqlState::SUCCESSFUL_COMPLETION,
327 AdapterNotice::AlterIndexOwner { .. } => SqlState::WARNING,
328 AdapterNotice::CannotRevoke { .. } => SqlState::WARNING_PRIVILEGE_NOT_REVOKED,
329 AdapterNotice::NonApplicablePrivilegeTypes { .. } => SqlState::SUCCESSFUL_COMPLETION,
330 AdapterNotice::PlanNotice(plan) => match plan {
331 PlanNotice::ObjectDoesNotExist { .. } => SqlState::UNDEFINED_OBJECT,
332 PlanNotice::ColumnAlreadyExists { .. } => SqlState::DUPLICATE_COLUMN,
333 PlanNotice::UpsertSinkKeyNotEnforced { .. } => SqlState::WARNING,
334 PlanNotice::ReplicaDiskOptionDeprecated { .. } => {
335 SqlState::WARNING_DEPRECATED_FEATURE
336 }
337 },
338 AdapterNotice::UnknownSessionDatabase(_) => SqlState::from_code("MZ004"),
339 AdapterNotice::DefaultClusterDoesNotExist { .. } => SqlState::from_code("MZ005"),
340 AdapterNotice::OptimizerNotice { .. } => SqlState::SUCCESSFUL_COMPLETION,
341 AdapterNotice::DroppedInUseIndex { .. } => SqlState::SUCCESSFUL_COMPLETION,
342 AdapterNotice::WebhookSourceCreated { .. } => SqlState::SUCCESSFUL_COMPLETION,
343 AdapterNotice::PerReplicaLogRead { .. } => SqlState::SUCCESSFUL_COMPLETION,
344 AdapterNotice::VarDefaultUpdated { .. } => SqlState::SUCCESSFUL_COMPLETION,
345 AdapterNotice::StartupOnlyVarUpdated { .. } => SqlState::WARNING,
346 AdapterNotice::Welcome(_) => SqlState::SUCCESSFUL_COMPLETION,
347 AdapterNotice::PlanInsights(_) => SqlState::from_code("MZ001"),
348 AdapterNotice::IntrospectionClusterUsage => SqlState::WARNING,
349 AdapterNotice::AutoRouteIntrospectionQueriesUsage => SqlState::WARNING,
350 AdapterNotice::SingleReplicaSourcesOnMultiReplicaCluster { .. } => SqlState::WARNING,
351 AdapterNotice::OidcGroupSyncReservedRole { .. } => SqlState::WARNING,
352 AdapterNotice::OidcGroupSyncError { .. } => SqlState::WARNING,
353 }
354 }
355}
356
357impl fmt::Display for AdapterNotice {
358 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
359 match self {
360 AdapterNotice::DatabaseAlreadyExists { name } => {
361 write!(f, "database {} already exists, skipping", name.quoted())
362 }
363 AdapterNotice::SchemaAlreadyExists { name } => {
364 write!(f, "schema {} already exists, skipping", name.quoted())
365 }
366 AdapterNotice::TableAlreadyExists { name } => {
367 write!(f, "table {} already exists, skipping", name.quoted())
368 }
369 AdapterNotice::ObjectAlreadyExists { name, ty } => {
370 write!(f, "{} {} already exists, skipping", ty, name.quoted())
371 }
372 AdapterNotice::DatabaseDoesNotExist { name } => {
373 write!(f, "database {} does not exist", name.quoted())
374 }
375 AdapterNotice::CascadeDroppedObject { objects } => {
376 write!(f, "drop cascades to {} other objects", objects.len())
377 }
378 AdapterNotice::ClusterDoesNotExist { name } => {
379 write!(f, "cluster {} does not exist", name.quoted())
380 }
381 AdapterNotice::DefaultClusterDoesNotExist { kind, name, .. } => {
382 write!(f, "{kind} default cluster {} does not exist", name.quoted())
383 }
384 AdapterNotice::NoResolvableSearchPathSchema { search_path } => {
385 write!(
386 f,
387 "no schema on the search path exists: {}",
388 search_path.join(", ")
389 )
390 }
391 AdapterNotice::ExistingTransactionInProgress => {
392 write!(f, "there is already a transaction in progress")
393 }
394 AdapterNotice::ExplicitTransactionControlInImplicitTransaction => {
395 write!(f, "there is no transaction in progress")
396 }
397 AdapterNotice::UserRequested { severity } => {
398 write!(f, "raised a test {}", severity.to_string().to_lowercase())
399 }
400 AdapterNotice::ClusterReplicaStatusChanged {
401 cluster,
402 replica,
403 status,
404 time,
405 } => {
406 let mut time_buf = String::new();
407 strconv::format_timestamptz(&mut time_buf, time);
408 write!(
409 f,
410 "cluster replica {}.{} changed status to {} at {}",
411 cluster,
412 replica,
413 status.as_kebab_case_str().quoted(),
414 time_buf,
415 )?;
416 Ok(())
417 }
418 AdapterNotice::DroppedActiveDatabase { name } => {
419 write!(f, "active database {} has been dropped", name.quoted())
420 }
421 AdapterNotice::DroppedActiveCluster { name } => {
422 write!(f, "active cluster {} has been dropped", name.quoted())
423 }
424 AdapterNotice::QueryTimestamp { .. } => write!(f, "EXPLAIN TIMESTAMP for query"),
425 AdapterNotice::EqualSubscribeBounds { bound } => {
426 write!(
427 f,
428 "subscribe as of {bound} (inclusive) up to the same bound {bound} (exclusive) is guaranteed to be empty"
429 )
430 }
431 AdapterNotice::QueryTrace { trace_id } => {
432 write!(f, "trace id: {}", trace_id)
433 }
434 AdapterNotice::UnimplementedIsolationLevel { isolation_level } => {
435 write!(
436 f,
437 "transaction isolation level {isolation_level} is unimplemented, the session will be upgraded to {}",
438 IsolationLevel::Serializable
439 )
440 }
441 AdapterNotice::StrongSessionSerializable => {
442 write!(
443 f,
444 "The Strong Session Serializable isolation level may exhibit consistency violations when reading from catalog objects",
445 )
446 }
447 AdapterNotice::BadStartupSetting { name, reason } => {
448 write!(f, "startup setting {name} not set: {reason}")
449 }
450 AdapterNotice::RbacUserDisabled => {
451 write!(
452 f,
453 "RBAC is disabled so no role attributes or object ownership will be considered \
454 when executing statements"
455 )
456 }
457 AdapterNotice::RoleMembershipAlreadyExists {
458 role_name,
459 member_name,
460 } => write!(
461 f,
462 "role \"{member_name}\" is already a member of role \"{role_name}\""
463 ),
464 AdapterNotice::RoleMembershipDoesNotExists {
465 role_name,
466 member_name,
467 } => write!(
468 f,
469 "role \"{member_name}\" is not a member of role \"{role_name}\""
470 ),
471 AdapterNotice::AutoRunOnCatalogServerCluster => write!(
472 f,
473 "query was automatically run on the \"mz_catalog_server\" cluster"
474 ),
475 AdapterNotice::AlterIndexOwner { name } => {
476 write!(f, "cannot change owner of {}", name.quoted())
477 }
478 AdapterNotice::CannotRevoke { object_description } => {
479 write!(f, "no privileges could be revoked for {object_description}")
480 }
481 AdapterNotice::NonApplicablePrivilegeTypes {
482 non_applicable_privileges,
483 object_description,
484 } => {
485 write!(
486 f,
487 "non-applicable privilege types {} for {}",
488 non_applicable_privileges.to_error_string(),
489 object_description,
490 )
491 }
492 AdapterNotice::PlanNotice(plan) => plan.fmt(f),
493 AdapterNotice::UnknownSessionDatabase(name) => {
494 write!(f, "session database {} does not exist", name.quoted())
495 }
496 AdapterNotice::OptimizerNotice { notice, hint: _ } => notice.fmt(f),
497 AdapterNotice::WebhookSourceCreated { url } => {
498 write!(f, "URL to POST data is '{url}'")
499 }
500 AdapterNotice::DroppedInUseIndex(DroppedInUseIndex {
501 index_name,
502 dependant_objects,
503 }) => {
504 write!(
505 f,
506 "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!",
507 separated(", ", dependant_objects)
508 )
509 }
510 AdapterNotice::PerReplicaLogRead { log_names } => {
511 write!(
512 f,
513 "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.",
514 log_names.join(", ")
515 )
516 }
517 AdapterNotice::VarDefaultUpdated { role, var_name } => {
518 let vars = match var_name {
519 Some(name) => format!("variable {} was", name.quoted()),
520 None => "variables were".to_string(),
521 };
522 let target = match role {
523 Some(role_name) => role_name.quoted().to_string(),
524 None => "the system".to_string(),
525 };
526 write!(
527 f,
528 "{vars} updated for {target}, this will have no effect on the current session"
529 )
530 }
531 AdapterNotice::StartupOnlyVarUpdated { var_name } => write!(
532 f,
533 "changes to {} only take effect when environmentd restarts",
534 var_name.quoted()
535 ),
536 AdapterNotice::Welcome(message) => message.fmt(f),
537 AdapterNotice::PlanInsights(message) => message.fmt(f),
538 AdapterNotice::IntrospectionClusterUsage => write!(
539 f,
540 "The mz_introspection cluster has been renamed to mz_catalog_server."
541 ),
542 AdapterNotice::AutoRouteIntrospectionQueriesUsage => write!(
543 f,
544 "The auto_route_introspection_queries variable has been renamed to auto_route_catalog_queries."
545 ),
546 AdapterNotice::SingleReplicaSourcesOnMultiReplicaCluster { cluster, sources } => {
547 const MAX_LISTED_SOURCES: usize = 3;
549 write!(
550 f,
551 "cluster {} has more than one replica, but the following sources in it always \
552 run on only the first replica: {}",
553 cluster.quoted(),
554 separated(
555 ", ",
556 sources.iter().take(MAX_LISTED_SOURCES).map(|n| n.quoted())
557 ),
558 )?;
559 if sources.len() > MAX_LISTED_SOURCES {
560 write!(f, ", and {} more", sources.len() - MAX_LISTED_SOURCES)?;
561 }
562 Ok(())
563 }
564 AdapterNotice::OidcGroupSyncReservedRole { group } => {
565 write!(
566 f,
567 "OIDC group \"{}\" maps to a reserved role name, skipping",
568 group
569 )
570 }
571 AdapterNotice::OidcGroupSyncError { message } => {
572 write!(f, "OIDC group-to-role sync failed: {}", message)
573 }
574 }
575 }
576}
577
578#[derive(Clone, Debug)]
579pub struct DroppedInUseIndex {
580 pub index_name: String,
581 pub dependant_objects: Vec<String>,
582}
583
584impl From<PlanNotice> for AdapterNotice {
585 fn from(notice: PlanNotice) -> AdapterNotice {
586 AdapterNotice::PlanNotice(notice)
587 }
588}