1use std::borrow::Cow;
11use std::collections::{BTreeMap, BTreeSet, VecDeque};
12use std::sync::Arc;
13use std::time::{Duration, Instant};
14use std::{future, iter};
15
16use anyhow::anyhow;
17use futures::Future;
18use futures::future::BoxFuture;
19use maplit::btreemap;
20use mz_adapter_types::compaction::CompactionWindow;
21use mz_adapter_types::connection::ConnectionId;
22use mz_adapter_types::dyncfgs::{ENABLE_EXPRESSION_CACHE, ENABLE_PASSWORD_AUTH};
23use mz_catalog::memory::error::ErrorKind;
24use mz_catalog::memory::objects::{
25 CatalogEntry, CatalogItem, Connection, DataSourceDesc, Index, Sink, Source, Table,
26 TableDataSource, Type,
27};
28use mz_compute_types::ComputeInstanceId;
29use mz_compute_types::dataflows::LirDataflowDescription;
30use mz_compute_types::plan::scalar::LirScalarExpr;
31use mz_expr::{MfpPlan, ResultSpec};
32use mz_ore::collections::{CollectionExt, HashSet};
33use mz_ore::future::OreFutureExt;
34use mz_ore::task::{self, JoinHandle, spawn};
35use mz_ore::tracing::OpenTelemetryContext;
36use mz_ore::{instrument, soft_panic_or_log};
37use mz_repr::adt::jsonb::Jsonb;
38use mz_repr::adt::mz_acl_item::{MzAclItem, PrivilegeMap};
39use mz_repr::explain::{ExprHumanizer, ExprHumanizerExt, TransientItem};
40use mz_repr::role_id::RoleId;
41use mz_repr::{CatalogItemId, Datum, Diff, GlobalId, RelationDesc, Row, Timestamp};
42use mz_secrets::SecretsReader;
43use mz_sql::ast::{CreateSinkOption, CreateSinkOptionName};
44use mz_sql::catalog::{
45 CatalogCluster, CatalogClusterReplica, CatalogDatabase, CatalogError,
46 CatalogItem as SqlCatalogItem, CatalogRole, CatalogSchema, CatalogTypeDetails,
47 ErrorMessageObjectDescription, ObjectType, RoleAttributesRaw, RoleVars, SessionCatalog,
48};
49use mz_sql::names::{
50 ObjectId, QualifiedItemName, ResolvedDatabaseSpecifier, ResolvedIds, ResolvedItemName,
51 SchemaSpecifier, SystemObjectId,
52};
53use mz_sql::plan::{
54 AlterMaterializedViewApplyReplacementPlan, ConnectionDetails, NetworkPolicyRule, PlanError,
55};
56use mz_storage_types::sinks::StorageSinkDesc;
57use mz_sql::plan::{
59 AlterConnectionAction, AlterConnectionPlan, ExplainSinkSchemaPlan, Explainee,
60 ExplaineeStatement, Params, Plan, PlannedAlterRoleOption, PlannedRoleVariable,
61 SideEffectingFunc, UpdatePrivilege, VariableValue,
62};
63use mz_sql::session::metadata::SessionMetadata;
64use mz_sql::session::user::UserKind;
65use mz_sql::session::vars::{
66 self, IsolationLevel, MAX_CONCURRENT_OCC_WRITES, NETWORK_POLICY, OwnedVarInput, SCHEMA_ALIAS,
67 TRANSACTION_ISOLATION_VAR_NAME, Var, VarError, VarInput,
68};
69use mz_sql::{plan, rbac};
70use mz_sql_parser::ast::display::AstDisplay;
71use mz_sql_parser::ast::{
72 ConnectionOption, ConnectionOptionName, Statement, TransactionMode, WithOptionValue,
73};
74use mz_ssh_util::keys::SshKeyPairSet;
75use mz_storage_client::controller::ExportDescription;
76use mz_storage_types::AlterCompatible;
77use mz_storage_types::connections::AwsPrivatelinkConnection;
78use mz_storage_types::connections::inline::IntoInlineConnection;
79use mz_storage_types::controller::StorageError;
80use mz_storage_types::sources::SourceConnection;
81use mz_transform::dataflow::DataflowMetainfo;
82use mz_transform::notice::{OptimizerNotice, RawOptimizerNotice};
83use smallvec::SmallVec;
84use timely::progress::Antichain;
85use tokio::sync::watch;
86use tracing::{Instrument, Span, info, warn};
87
88use crate::active_compute_sink::ActiveComputeSink;
89use crate::catalog::{
90 self, Catalog, CatalogState, ConnCatalog, DropObjectInfo, UpdatePrivilegeVariant,
91};
92use crate::command::ExecuteResponse;
93use crate::coord::appends::{BuiltinTableAppendNotify, PendingWriteTxn, UserWriteResponder};
94use crate::coord::sequencer::emit_optimizer_notices;
95use crate::coord::{
96 AlterConnectionValidationReady, AlterMaterializedViewReadyContext, AlterSinkReadyContext,
97 Coordinator, CreateConnectionValidationReady, DeferredDdl, ExecuteContext, Message,
98 NetworkPolicyError, PendingReadTxn, PendingTxn, PendingTxnResponse, PlanValidity, StageResult,
99 Staged, StagedContext, WatchSetResponse, validate_ip_with_policy_rules,
100};
101use crate::error::AdapterError;
102use crate::notice::{AdapterNotice, DroppedInUseIndex, InFlightIndexReaders};
103use crate::session::{
104 EndTransactionAction, RequireLinearization, Session, TransactionOps, TransactionStatus, WriteOp,
105};
106use crate::util::{ResultExt, viewable_variables};
107use crate::{CollectionIdBundle, ReadHolds};
108
109type RtrTimestampFuture = BoxFuture<'static, Result<Timestamp, StorageError>>;
111
112mod cluster;
113mod copy_from;
114mod create_index;
115mod create_materialized_view;
116mod create_metric_sink;
117mod create_view;
118mod explain_timestamp;
119mod secret;
120mod subscribe;
121
122macro_rules! return_if_err {
125 ($expr:expr, $ctx:expr) => {
126 match $expr {
127 Ok(v) => v,
128 Err(e) => return $ctx.retire(Err(e.into())),
129 }
130 };
131}
132
133pub(super) use return_if_err;
134
135fn check_connection_details(details: &ConnectionDetails) -> Result<(), PlanError> {
138 match details {
139 ConnectionDetails::AwsPrivatelink(privatelink) => {
140 AwsPrivatelinkConnection::check_service_name(&privatelink.service_name)
141 .map_err(PlanError::InvalidPrivatelinkServiceName)
142 }
143 _ => Ok(()),
144 }
145}
146
147struct DropOps {
148 ops: Vec<catalog::Op>,
149 dropped_active_db: bool,
150 dropped_active_cluster: bool,
151}
152
153struct IndexDropNotices {
155 orphaned: Vec<DroppedInUseIndex>,
158 in_flight: Vec<InFlightIndexReaders>,
160}
161
162struct CreateSourceInner {
164 ops: Vec<catalog::Op>,
165 sources: Vec<(CatalogItemId, QualifiedItemName, Source)>,
166 if_not_exists_ids: BTreeMap<CatalogItemId, QualifiedItemName>,
167}
168
169impl Coordinator {
170 pub(crate) async fn sequence_staged<S>(
177 &mut self,
178 mut ctx: S::Ctx,
179 parent_span: Span,
180 mut stage: S,
181 ) where
182 S: Staged + 'static,
183 S::Ctx: Send + 'static,
184 {
185 return_if_err!(stage.validity().check(self.catalog()), ctx);
186 loop {
187 let mut cancel_enabled = stage.cancel_enabled();
188 if let Some(session) = ctx.session() {
189 if cancel_enabled {
190 if let Some((_prev_tx, prev_rx)) = self
193 .connection_cancel_watches
194 .insert(session.conn_id().clone(), watch::channel(false))
195 {
196 let was_canceled = *prev_rx.borrow();
197 if was_canceled {
198 ctx.retire(Err(AdapterError::Canceled));
199 return;
200 }
201 }
202 } else {
203 self.connection_cancel_watches.remove(session.conn_id());
206 }
207 } else {
208 cancel_enabled = false
209 };
210 let next = Box::pin(stage.stage(self, &mut ctx).instrument(parent_span.clone())).await;
213 let res = return_if_err!(next, ctx);
214 stage = match res {
215 StageResult::Handle(handle) => {
216 let internal_cmd_tx = self.internal_cmd_tx.clone();
217 self.handle_spawn(ctx, handle, cancel_enabled, move |ctx, next| {
218 let _ = internal_cmd_tx.send(next.message(ctx, parent_span));
219 });
220 return;
221 }
222 StageResult::HandleRetire(handle) => {
223 self.handle_spawn(ctx, handle, cancel_enabled, move |ctx, resp| {
224 ctx.retire(Ok(resp));
225 });
226 return;
227 }
228 StageResult::Response(resp) => {
229 ctx.retire(Ok(resp));
230 return;
231 }
232 StageResult::Immediate(stage) => *stage,
233 }
234 }
235 }
236
237 fn handle_spawn<C, T, F>(
240 &self,
241 ctx: C,
242 handle: JoinHandle<Result<T, AdapterError>>,
243 cancel_enabled: bool,
244 f: F,
245 ) where
246 C: StagedContext + Send + 'static,
247 T: Send + 'static,
248 F: FnOnce(C, T) + Send + 'static,
249 {
250 let rx: BoxFuture<()> = if let Some((_tx, rx)) = ctx
251 .session()
252 .and_then(|session| self.connection_cancel_watches.get(session.conn_id()))
253 {
254 let mut rx = rx.clone();
255 Box::pin(async move {
256 let _ = rx.wait_for(|v| *v).await;
258 ()
259 })
260 } else {
261 Box::pin(future::pending())
262 };
263 spawn(|| "sequence_staged", async move {
264 tokio::select! {
265 res = handle => {
266 let next = return_if_err!(res, ctx);
267 f(ctx, next);
268 }
269 _ = rx, if cancel_enabled => {
270 ctx.retire(Err(AdapterError::Canceled));
271 }
272 }
273 });
274 }
275
276 async fn create_source_inner(
277 &self,
278 session: &Session,
279 plans: Vec<plan::CreateSourcePlanBundle>,
280 ) -> Result<CreateSourceInner, AdapterError> {
281 let mut ops = vec![];
282 let mut sources = vec![];
283
284 let if_not_exists_ids = plans
285 .iter()
286 .filter_map(
287 |plan::CreateSourcePlanBundle {
288 item_id,
289 global_id: _,
290 plan,
291 resolved_ids: _,
292 available_source_references: _,
293 }| {
294 if plan.if_not_exists {
295 Some((*item_id, plan.name.clone()))
296 } else {
297 None
298 }
299 },
300 )
301 .collect::<BTreeMap<_, _>>();
302
303 for plan::CreateSourcePlanBundle {
304 item_id,
305 global_id,
306 mut plan,
307 resolved_ids,
308 available_source_references,
309 } in plans
310 {
311 let name = plan.name.clone();
312
313 if let mz_sql::plan::DataSourceDesc::Webhook {
315 validate_using: Some(validate),
316 ..
317 } = &mut plan.source.data_source
318 {
319 if let Err(reason) = validate.reduce_expression().await {
320 self.metrics
321 .webhook_validation_reduce_failures
322 .with_label_values(&[reason])
323 .inc();
324 return Err(AdapterError::Internal(format!(
325 "failed to reduce check expression, {reason}"
326 )));
327 }
328 }
329
330 let mut reference_ops = vec![];
333 if let Some(references) = &available_source_references {
334 reference_ops.push(catalog::Op::UpdateSourceReferences {
335 source_id: item_id,
336 references: references.clone().into(),
337 });
338 }
339
340 let source = Source::new(plan, global_id, resolved_ids, None, false);
341 ops.push(catalog::Op::CreateItem {
342 id: item_id,
343 name: name.clone(),
344 item: CatalogItem::Source(source.clone()),
345 owner_id: *session.current_role_id(),
346 });
347 sources.push((item_id, name, source));
348 ops.extend(reference_ops);
350 }
351
352 Ok(CreateSourceInner {
353 ops,
354 sources,
355 if_not_exists_ids,
356 })
357 }
358
359 #[instrument]
360 pub(super) async fn sequence_create_source(
361 &mut self,
362 ctx: &mut ExecuteContext,
363 plans: Vec<plan::CreateSourcePlanBundle>,
364 ) -> Result<ExecuteResponse, AdapterError> {
365 let CreateSourceInner {
366 ops,
367 sources,
368 if_not_exists_ids,
369 } = self.create_source_inner(ctx.session(), plans).await?;
370
371 let transact_result = self
372 .catalog_transact_with_context(None, Some(ctx), ops)
373 .await;
374
375 for (item_id, _name, source) in &sources {
377 if matches!(source.data_source, DataSourceDesc::Webhook { .. }) {
378 if let Some(url) = self.catalog().state().try_get_webhook_url(item_id) {
379 ctx.session()
380 .add_notice(AdapterNotice::WebhookSourceCreated { url });
381 }
382 }
383 }
384
385 match transact_result {
386 Ok(()) => {
387 for (_item_id, name, source) in &sources {
390 let cluster_id = match &source.data_source {
391 DataSourceDesc::Ingestion { desc, cluster_id }
392 | DataSourceDesc::OldSyntaxIngestion {
393 desc, cluster_id, ..
394 } if desc.connection.prefers_single_replica() => cluster_id,
395 _ => continue,
396 };
397 let Some(cluster) = self.catalog().try_get_cluster(*cluster_id) else {
398 continue;
399 };
400 let state = ctx
401 .session()
402 .transaction()
403 .catalog_state()
404 .unwrap_or_else(|| self.catalog().state());
405 let name = state.resolve_full_name(name, None);
406 self.notify_single_replica_sources(ctx.session(), cluster, Some(&name));
407 }
408 Ok(ExecuteResponse::CreatedSource)
409 }
410 Err(AdapterError::Catalog(mz_catalog::memory::error::Error {
411 kind: ErrorKind::Sql(CatalogError::ItemAlreadyExists(id, _)),
412 })) if if_not_exists_ids.contains_key(&id) => {
413 ctx.session()
414 .add_notice(AdapterNotice::ObjectAlreadyExists {
415 name: if_not_exists_ids[&id].item.clone(),
416 ty: "source",
417 });
418 Ok(ExecuteResponse::CreatedSource)
419 }
420 Err(err) => Err(err),
421 }
422 }
423
424 async fn check_connection_secret_content_guards(
428 &self,
429 details: &ConnectionDetails,
430 ) -> Result<(), AdapterError> {
431 for (secret_id, guard) in details.secret_content_guards() {
432 let contents = self.caching_secrets_reader.read_string(secret_id).await?;
433 guard(&contents)?;
434 }
435 Ok(())
436 }
437
438 pub(super) fn check_secret_content_guards_of_dependents(
443 &self,
444 secret_id: CatalogItemId,
445 contents: &str,
446 ) -> Result<(), AdapterError> {
447 for dependent_id in self.catalog().get_entry(&secret_id).used_by() {
448 if let CatalogItem::Connection(conn) = self.catalog().get_entry(dependent_id).item() {
449 for (guarded_id, guard) in conn.details.secret_content_guards() {
450 if guarded_id == secret_id {
451 guard(contents)?;
452 }
453 }
454 }
455 }
456 Ok(())
457 }
458
459 #[instrument]
460 pub(super) async fn sequence_create_connection(
461 &mut self,
462 mut ctx: ExecuteContext,
463 plan: plan::CreateConnectionPlan,
464 resolved_ids: ResolvedIds,
465 ) {
466 if let Err(err) = check_connection_details(&plan.connection.details) {
467 return ctx.retire(Err(AdapterError::PlanError(err)));
468 }
469
470 let (connection_id, connection_gid) = match self.allocate_user_id().await {
471 Ok(item_id) => item_id,
472 Err(err) => return ctx.retire(Err(err)),
473 };
474
475 match &plan.connection.details {
476 ConnectionDetails::Ssh { key_1, key_2, .. } => {
477 let key_1 = match key_1.as_key_pair() {
478 Some(key_1) => key_1.clone(),
479 None => {
480 return ctx.retire(Err(AdapterError::Unstructured(anyhow!(
481 "the PUBLIC KEY 1 option cannot be explicitly specified"
482 ))));
483 }
484 };
485
486 let key_2 = match key_2.as_key_pair() {
487 Some(key_2) => key_2.clone(),
488 None => {
489 return ctx.retire(Err(AdapterError::Unstructured(anyhow!(
490 "the PUBLIC KEY 2 option cannot be explicitly specified"
491 ))));
492 }
493 };
494
495 let key_set = SshKeyPairSet::from_parts(key_1, key_2);
496 let secret = key_set.to_bytes();
497 if let Err(err) = self.secrets_controller.ensure(connection_id, &secret).await {
498 return ctx.retire(Err(err.into()));
499 }
500 self.caching_secrets_reader.invalidate(connection_id);
501 }
502 _ => (),
503 };
504
505 if let Err(err) = self
508 .check_connection_secret_content_guards(&plan.connection.details)
509 .await
510 {
511 return ctx.retire(Err(err));
512 }
513
514 if plan.validate {
515 let internal_cmd_tx = self.internal_cmd_tx.clone();
516 let catalog = self.owned_catalog();
517 let conn_id = ctx.session().conn_id().clone();
518 let otel_ctx = OpenTelemetryContext::obtain();
519 let role_metadata = ctx.session().role_metadata().clone();
520
521 let connection = plan
522 .connection
523 .details
524 .to_connection()
525 .into_inline_connection(self.catalog().state());
526
527 let current_storage_parameters = self.controller.storage.config().clone();
528 task::spawn(|| format!("validate_connection:{conn_id}"), async move {
529 let result = match std::panic::AssertUnwindSafe(
530 connection.validate(connection_id, ¤t_storage_parameters),
531 )
532 .ore_catch_unwind()
533 .await
534 {
535 Ok(Ok(())) => Ok(plan),
536 Ok(Err(err)) => Err(err.into()),
537 Err(_panic) => {
538 tracing::error!("connection validation panicked");
539 Err(AdapterError::Internal(
540 "connection validation panicked".into(),
541 ))
542 }
543 };
544
545 let result = internal_cmd_tx.send(Message::CreateConnectionValidationReady(
547 CreateConnectionValidationReady {
548 ctx,
549 result,
550 connection_id,
551 connection_gid,
552 plan_validity: PlanValidity::new(
553 &catalog,
554 resolved_ids.items().copied().collect(),
555 None,
556 None,
557 role_metadata,
558 ),
559 otel_ctx,
560 resolved_ids: resolved_ids.clone(),
561 },
562 ));
563 if let Err(e) = result {
564 tracing::warn!("internal_cmd_rx dropped before we could send: {:?}", e);
565 }
566 });
567 } else {
568 let result = self
569 .sequence_create_connection_stage_finish(
570 &mut ctx,
571 connection_id,
572 connection_gid,
573 plan,
574 resolved_ids,
575 )
576 .await;
577 ctx.retire(result);
578 }
579 }
580
581 #[instrument]
582 pub(crate) async fn sequence_create_connection_stage_finish(
583 &mut self,
584 ctx: &mut ExecuteContext,
585 connection_id: CatalogItemId,
586 connection_gid: GlobalId,
587 plan: plan::CreateConnectionPlan,
588 resolved_ids: ResolvedIds,
589 ) -> Result<ExecuteResponse, AdapterError> {
590 let ops = vec![catalog::Op::CreateItem {
591 id: connection_id,
592 name: plan.name.clone(),
593 item: CatalogItem::Connection(Connection {
594 create_sql: plan.connection.create_sql,
595 global_id: connection_gid,
596 details: plan.connection.details.clone(),
597 resolved_ids,
598 }),
599 owner_id: *ctx.session().current_role_id(),
600 }];
601
602 let conn_id = ctx.session().conn_id().clone();
605 let transact_result = self
606 .catalog_transact_with_context(Some(&conn_id), Some(ctx), ops)
607 .await;
608
609 match transact_result {
610 Ok(_) => Ok(ExecuteResponse::CreatedConnection),
611 Err(AdapterError::Catalog(mz_catalog::memory::error::Error {
612 kind: ErrorKind::Sql(CatalogError::ItemAlreadyExists(_, _)),
613 })) if plan.if_not_exists => {
614 if matches!(plan.connection.details, ConnectionDetails::Ssh { .. }) {
617 if let Err(e) = self.secrets_controller.delete(connection_id).await {
618 tracing::warn!(
619 "Dropping SSH secret for existing connection has encountered an error: {}",
620 e
621 );
622 } else {
623 self.caching_secrets_reader.invalidate(connection_id);
624 }
625 }
626 ctx.session()
627 .add_notice(AdapterNotice::ObjectAlreadyExists {
628 name: plan.name.item,
629 ty: "connection",
630 });
631 Ok(ExecuteResponse::CreatedConnection)
632 }
633 Err(err) => {
634 if matches!(plan.connection.details, ConnectionDetails::Ssh { .. }) {
637 if let Err(e) = self.secrets_controller.delete(connection_id).await {
638 tracing::warn!(
639 "Dropping SSH secret for failed connection has encountered an error: {}",
640 e
641 );
642 } else {
643 self.caching_secrets_reader.invalidate(connection_id);
644 }
645 }
646 Err(err)
647 }
648 }
649 }
650
651 #[instrument]
652 pub(super) async fn sequence_create_database(
653 &mut self,
654 session: &Session,
655 plan: plan::CreateDatabasePlan,
656 ) -> Result<ExecuteResponse, AdapterError> {
657 let ops = vec![catalog::Op::CreateDatabase {
658 name: plan.name.clone(),
659 owner_id: *session.current_role_id(),
660 }];
661 match self.catalog_transact(Some(session), ops).await {
662 Ok(_) => Ok(ExecuteResponse::CreatedDatabase),
663 Err(AdapterError::Catalog(mz_catalog::memory::error::Error {
664 kind: ErrorKind::Sql(CatalogError::DatabaseAlreadyExists(_)),
665 })) if plan.if_not_exists => {
666 session.add_notice(AdapterNotice::DatabaseAlreadyExists { name: plan.name });
667 Ok(ExecuteResponse::CreatedDatabase)
668 }
669 Err(err) => Err(err),
670 }
671 }
672
673 #[instrument]
674 pub(super) async fn sequence_create_schema(
675 &mut self,
676 session: &Session,
677 plan: plan::CreateSchemaPlan,
678 ) -> Result<ExecuteResponse, AdapterError> {
679 let op = catalog::Op::CreateSchema {
680 database_id: plan.database_spec,
681 schema_name: plan.schema_name.clone(),
682 owner_id: *session.current_role_id(),
683 };
684 match self.catalog_transact(Some(session), vec![op]).await {
685 Ok(_) => Ok(ExecuteResponse::CreatedSchema),
686 Err(AdapterError::Catalog(mz_catalog::memory::error::Error {
687 kind: ErrorKind::Sql(CatalogError::SchemaAlreadyExists(_)),
688 })) if plan.if_not_exists => {
689 session.add_notice(AdapterNotice::SchemaAlreadyExists {
690 name: plan.schema_name,
691 });
692 Ok(ExecuteResponse::CreatedSchema)
693 }
694 Err(err) => Err(err),
695 }
696 }
697
698 fn validate_role_attributes(&self, attributes: &RoleAttributesRaw) -> Result<(), AdapterError> {
700 if !ENABLE_PASSWORD_AUTH.get(self.catalog().system_config().dyncfgs()) {
701 if attributes.superuser.is_some() || attributes.password.is_some() {
702 return Err(AdapterError::UnavailableFeature {
703 feature: "SUPERUSER and PASSWORD attributes".to_string(),
704 docs: Some("https://materialize.com/docs/sql/create-role/#details".to_string()),
705 });
706 }
707 }
708 Ok(())
709 }
710
711 #[instrument]
712 pub(super) async fn sequence_create_role(
713 &mut self,
714 conn_id: Option<&ConnectionId>,
715 plan::CreateRolePlan { name, attributes }: plan::CreateRolePlan,
716 ) -> Result<ExecuteResponse, AdapterError> {
717 self.validate_role_attributes(&attributes.clone())?;
718 let op = catalog::Op::CreateRole { name, attributes };
719 self.catalog_transact_with_context(conn_id, None, vec![op])
720 .await
721 .map(|_| ExecuteResponse::CreatedRole)
722 }
723
724 #[instrument]
725 pub(super) async fn sequence_create_network_policy(
726 &mut self,
727 session: &Session,
728 plan::CreateNetworkPolicyPlan { name, rules }: plan::CreateNetworkPolicyPlan,
729 ) -> Result<ExecuteResponse, AdapterError> {
730 let op = catalog::Op::CreateNetworkPolicy {
731 rules,
732 name,
733 owner_id: *session.current_role_id(),
734 };
735 self.catalog_transact_with_context(Some(session.conn_id()), None, vec![op])
736 .await
737 .map(|_| ExecuteResponse::CreatedNetworkPolicy)
738 }
739
740 #[instrument]
741 pub(super) async fn sequence_alter_network_policy(
742 &mut self,
743 session: &Session,
744 plan::AlterNetworkPolicyPlan { id, name, rules }: plan::AlterNetworkPolicyPlan,
745 ) -> Result<ExecuteResponse, AdapterError> {
746 let current_network_policy_name =
748 self.catalog().system_config().default_network_policy_name();
749 if current_network_policy_name == name {
751 self.validate_alter_network_policy(session, &rules)?;
752 }
753
754 let op = catalog::Op::AlterNetworkPolicy {
755 id,
756 rules,
757 name,
758 owner_id: *session.current_role_id(),
759 };
760 self.catalog_transact_with_context(Some(session.conn_id()), None, vec![op])
761 .await
762 .map(|_| ExecuteResponse::AlteredObject(ObjectType::NetworkPolicy))
763 }
764
765 #[instrument]
766 pub(super) async fn sequence_create_table(
767 &mut self,
768 ctx: &mut ExecuteContext,
769 plan: plan::CreateTablePlan,
770 resolved_ids: ResolvedIds,
771 ) -> Result<ExecuteResponse, AdapterError> {
772 let plan::CreateTablePlan {
773 name,
774 table,
775 if_not_exists,
776 } = plan;
777
778 let (table_id, global_id) = self.allocate_user_id().await?;
779 let table = Table::from_plan(
780 table,
781 global_id,
782 ctx.session().conn_id().clone(),
783 resolved_ids,
784 );
785 let is_webhook = matches!(
786 &table.data_source,
787 TableDataSource::DataSource {
788 desc: DataSourceDesc::Webhook { .. },
789 ..
790 }
791 );
792 let ops = vec![catalog::Op::CreateItem {
793 id: table_id,
794 name: name.clone(),
795 item: CatalogItem::Table(table.clone()),
796 owner_id: *ctx.session().current_role_id(),
797 }];
798
799 let catalog_result = self
800 .catalog_transact_with_context(None, Some(ctx), ops)
801 .await;
802
803 if is_webhook {
804 if let Some(url) = self.catalog().state().try_get_webhook_url(&table_id) {
807 ctx.session()
808 .add_notice(AdapterNotice::WebhookSourceCreated { url })
809 }
810 }
811
812 match catalog_result {
813 Ok(()) => Ok(ExecuteResponse::CreatedTable),
814 Err(AdapterError::Catalog(mz_catalog::memory::error::Error {
815 kind: ErrorKind::Sql(CatalogError::ItemAlreadyExists(_, _)),
816 })) if if_not_exists => {
817 ctx.session_mut()
818 .add_notice(AdapterNotice::ObjectAlreadyExists {
819 name: name.item,
820 ty: "table",
821 });
822 Ok(ExecuteResponse::CreatedTable)
823 }
824 Err(err) => Err(err),
825 }
826 }
827
828 #[instrument]
829 pub(super) async fn sequence_create_sink(
830 &mut self,
831 ctx: ExecuteContext,
832 plan: plan::CreateSinkPlan,
833 resolved_ids: ResolvedIds,
834 ) {
835 let plan::CreateSinkPlan {
836 name,
837 sink,
838 with_snapshot,
839 if_not_exists,
840 in_cluster,
841 } = plan;
842
843 let (item_id, global_id) = return_if_err!(self.allocate_user_id().await, ctx);
845
846 let catalog_sink = Sink {
847 create_sql: sink.create_sql,
848 global_id,
849 from: sink.from,
850 connection: sink.connection,
851 envelope: sink.envelope,
852 version: sink.version,
853 with_snapshot,
854 resolved_ids,
855 cluster_id: in_cluster,
856 commit_interval: sink.commit_interval,
857 };
858
859 let ops = vec![catalog::Op::CreateItem {
860 id: item_id,
861 name: name.clone(),
862 item: CatalogItem::Sink(catalog_sink.clone()),
863 owner_id: *ctx.session().current_role_id(),
864 }];
865
866 let result = self.catalog_transact(Some(ctx.session()), ops).await;
867
868 match result {
869 Ok(()) => {}
870 Err(AdapterError::Catalog(mz_catalog::memory::error::Error {
871 kind: ErrorKind::Sql(CatalogError::ItemAlreadyExists(_, _)),
872 })) if if_not_exists => {
873 ctx.session()
874 .add_notice(AdapterNotice::ObjectAlreadyExists {
875 name: name.item,
876 ty: "sink",
877 });
878 ctx.retire(Ok(ExecuteResponse::CreatedSink));
879 return;
880 }
881 Err(e) => {
882 ctx.retire(Err(e));
883 return;
884 }
885 };
886
887 self.create_storage_export(global_id, &catalog_sink)
888 .await
889 .unwrap_or_terminate("cannot fail to create exports");
890
891 self.initialize_storage_read_policies([item_id].into(), CompactionWindow::Default)
892 .await;
893
894 ctx.retire(Ok(ExecuteResponse::CreatedSink))
895 }
896
897 pub(super) fn validate_system_column_references(
920 &self,
921 uses_ambiguous_columns: bool,
922 depends_on: &BTreeSet<GlobalId>,
923 ) -> Result<(), AdapterError> {
924 if uses_ambiguous_columns
925 && depends_on
926 .iter()
927 .any(|id| id.is_system() && self.catalog().get_entry_by_global_id(id).is_relation())
928 {
929 Err(AdapterError::AmbiguousSystemColumnReference)
930 } else {
931 Ok(())
932 }
933 }
934
935 #[instrument]
936 pub(super) async fn sequence_create_type(
937 &mut self,
938 session: &Session,
939 plan: plan::CreateTypePlan,
940 resolved_ids: ResolvedIds,
941 ) -> Result<ExecuteResponse, AdapterError> {
942 let (item_id, global_id) = self.allocate_user_id().await?;
943 plan.typ
945 .inner
946 .desc(&self.catalog().for_session(session))
947 .map_err(AdapterError::from)?;
948 let typ = Type {
949 create_sql: Some(plan.typ.create_sql),
950 global_id,
951 details: CatalogTypeDetails {
952 array_id: None,
953 typ: plan.typ.inner,
954 pg_metadata: None,
955 },
956 resolved_ids,
957 };
958 let op = catalog::Op::CreateItem {
959 id: item_id,
960 name: plan.name,
961 item: CatalogItem::Type(typ),
962 owner_id: *session.current_role_id(),
963 };
964 match self.catalog_transact(Some(session), vec![op]).await {
965 Ok(()) => Ok(ExecuteResponse::CreatedType),
966 Err(err) => Err(err),
967 }
968 }
969
970 #[instrument]
971 pub(super) async fn sequence_comment_on(
972 &mut self,
973 session: &Session,
974 plan: plan::CommentPlan,
975 ) -> Result<ExecuteResponse, AdapterError> {
976 let op = catalog::Op::Comment {
977 object_id: plan.object_id,
978 sub_component: plan.sub_component,
979 comment: plan.comment,
980 };
981 self.catalog_transact(Some(session), vec![op]).await?;
982 Ok(ExecuteResponse::Comment)
983 }
984
985 #[instrument]
986 pub(super) async fn sequence_drop_objects(
987 &mut self,
988 ctx: &mut ExecuteContext,
989 plan::DropObjectsPlan {
990 drop_ids,
991 object_type,
992 referenced_ids,
993 }: plan::DropObjectsPlan,
994 ) -> Result<ExecuteResponse, AdapterError> {
995 let IndexDropNotices {
996 orphaned,
997 in_flight,
998 } = self.check_index_dependents(ctx.session(), &drop_ids)?;
999
1000 let referenced_ids_hashset = referenced_ids.iter().collect::<HashSet<_>>();
1001 let mut objects = Vec::new();
1002 for obj_id in &drop_ids {
1003 if !referenced_ids_hashset.contains(obj_id) {
1004 let object_info = ErrorMessageObjectDescription::from_id(
1005 obj_id,
1006 &self.catalog().for_session(ctx.session()),
1007 )
1008 .to_string();
1009 objects.push(object_info);
1010 }
1011 }
1012
1013 if !objects.is_empty() {
1014 ctx.session()
1015 .add_notice(AdapterNotice::CascadeDroppedObject { objects });
1016 }
1017
1018 let expr_cache_invalidate_ids: BTreeSet<_> = drop_ids
1020 .iter()
1021 .filter_map(|id| match id {
1022 ObjectId::Item(item_id) => Some(self.catalog().get_entry(item_id).global_ids()),
1023 _ => None,
1024 })
1025 .flatten()
1026 .collect();
1027
1028 let DropOps {
1029 ops,
1030 dropped_active_db,
1031 dropped_active_cluster,
1032 } = self.sequence_drop_common(ctx.session(), drop_ids)?;
1033
1034 self.catalog_transact_with_context(None, Some(ctx), ops)
1035 .await?;
1036
1037 if !expr_cache_invalidate_ids.is_empty() {
1039 let _fut = self.catalog().update_expression_cache(
1040 Default::default(),
1041 Default::default(),
1042 expr_cache_invalidate_ids,
1043 );
1044 }
1045
1046 fail::fail_point!("after_sequencer_drop_replica");
1047
1048 if dropped_active_db {
1049 ctx.session()
1050 .add_notice(AdapterNotice::DroppedActiveDatabase {
1051 name: ctx.session().vars().database().to_string(),
1052 });
1053 }
1054 if dropped_active_cluster {
1055 ctx.session()
1056 .add_notice(AdapterNotice::DroppedActiveCluster {
1057 name: ctx.session().vars().cluster().to_string(),
1058 });
1059 }
1060 self.emit_index_drop_notices(ctx.session(), orphaned, in_flight);
1061 Ok(ExecuteResponse::DroppedObject(object_type))
1062 }
1063
1064 fn validate_dropped_role_ownership(
1065 &self,
1066 session: &Session,
1067 dropped_roles: &BTreeMap<RoleId, &str>,
1068 ) -> Result<(), AdapterError> {
1069 fn privilege_check(
1070 privileges: &PrivilegeMap,
1071 dropped_roles: &BTreeMap<RoleId, &str>,
1072 dependent_objects: &mut BTreeMap<String, Vec<String>>,
1073 object_id: &SystemObjectId,
1074 catalog: &ConnCatalog,
1075 ) {
1076 for privilege in privileges.all_values() {
1077 if let Some(role_name) = dropped_roles.get(&privilege.grantee) {
1078 let grantor_name = catalog.get_role(&privilege.grantor).name();
1079 let object_description =
1080 ErrorMessageObjectDescription::from_sys_id(object_id, catalog);
1081 dependent_objects
1082 .entry(role_name.to_string())
1083 .or_default()
1084 .push(format!(
1085 "privileges on {object_description} granted by {grantor_name}",
1086 ));
1087 }
1088 if let Some(role_name) = dropped_roles.get(&privilege.grantor) {
1089 let grantee_name = catalog.get_role(&privilege.grantee).name();
1090 let object_description =
1091 ErrorMessageObjectDescription::from_sys_id(object_id, catalog);
1092 dependent_objects
1093 .entry(role_name.to_string())
1094 .or_default()
1095 .push(format!(
1096 "privileges granted on {object_description} to {grantee_name}"
1097 ));
1098 }
1099 }
1100 }
1101
1102 let catalog = self.catalog().for_session(session);
1103 let mut dependent_objects: BTreeMap<_, Vec<_>> = BTreeMap::new();
1104 for entry in self.catalog.entries() {
1105 let id = SystemObjectId::Object(entry.id().into());
1106 if let Some(role_name) = dropped_roles.get(entry.owner_id()) {
1107 let object_description = ErrorMessageObjectDescription::from_sys_id(&id, &catalog);
1108 dependent_objects
1109 .entry(role_name.to_string())
1110 .or_default()
1111 .push(format!("owner of {object_description}"));
1112 }
1113 privilege_check(
1114 entry.privileges(),
1115 dropped_roles,
1116 &mut dependent_objects,
1117 &id,
1118 &catalog,
1119 );
1120 }
1121 for database in self.catalog.databases() {
1122 let database_id = SystemObjectId::Object(database.id().into());
1123 if let Some(role_name) = dropped_roles.get(&database.owner_id) {
1124 let object_description =
1125 ErrorMessageObjectDescription::from_sys_id(&database_id, &catalog);
1126 dependent_objects
1127 .entry(role_name.to_string())
1128 .or_default()
1129 .push(format!("owner of {object_description}"));
1130 }
1131 privilege_check(
1132 &database.privileges,
1133 dropped_roles,
1134 &mut dependent_objects,
1135 &database_id,
1136 &catalog,
1137 );
1138 for schema in database.schemas_by_id.values() {
1139 let schema_id = SystemObjectId::Object(
1140 (ResolvedDatabaseSpecifier::Id(database.id()), *schema.id()).into(),
1141 );
1142 if let Some(role_name) = dropped_roles.get(&schema.owner_id) {
1143 let object_description =
1144 ErrorMessageObjectDescription::from_sys_id(&schema_id, &catalog);
1145 dependent_objects
1146 .entry(role_name.to_string())
1147 .or_default()
1148 .push(format!("owner of {object_description}"));
1149 }
1150 privilege_check(
1151 &schema.privileges,
1152 dropped_roles,
1153 &mut dependent_objects,
1154 &schema_id,
1155 &catalog,
1156 );
1157 }
1158 }
1159 for cluster in self.catalog.clusters() {
1160 let cluster_id = SystemObjectId::Object(cluster.id().into());
1161 if let Some(role_name) = dropped_roles.get(&cluster.owner_id) {
1162 let object_description =
1163 ErrorMessageObjectDescription::from_sys_id(&cluster_id, &catalog);
1164 dependent_objects
1165 .entry(role_name.to_string())
1166 .or_default()
1167 .push(format!("owner of {object_description}"));
1168 }
1169 privilege_check(
1170 &cluster.privileges,
1171 dropped_roles,
1172 &mut dependent_objects,
1173 &cluster_id,
1174 &catalog,
1175 );
1176 for replica in cluster.replicas() {
1177 if let Some(role_name) = dropped_roles.get(&replica.owner_id) {
1178 let replica_id =
1179 SystemObjectId::Object((replica.cluster_id(), replica.replica_id()).into());
1180 let object_description =
1181 ErrorMessageObjectDescription::from_sys_id(&replica_id, &catalog);
1182 dependent_objects
1183 .entry(role_name.to_string())
1184 .or_default()
1185 .push(format!("owner of {object_description}"));
1186 }
1187 }
1188 }
1189 privilege_check(
1190 self.catalog().system_privileges(),
1191 dropped_roles,
1192 &mut dependent_objects,
1193 &SystemObjectId::System,
1194 &catalog,
1195 );
1196 for (default_privilege_object, default_privilege_acl_items) in
1197 self.catalog.default_privileges()
1198 {
1199 if let Some(role_name) = dropped_roles.get(&default_privilege_object.role_id) {
1200 dependent_objects
1201 .entry(role_name.to_string())
1202 .or_default()
1203 .push(format!(
1204 "default privileges on {}S created by {}",
1205 default_privilege_object.object_type, role_name
1206 ));
1207 }
1208 for default_privilege_acl_item in default_privilege_acl_items {
1209 if let Some(role_name) = dropped_roles.get(&default_privilege_acl_item.grantee) {
1210 dependent_objects
1211 .entry(role_name.to_string())
1212 .or_default()
1213 .push(format!(
1214 "default privileges on {}S granted to {}",
1215 default_privilege_object.object_type, role_name
1216 ));
1217 }
1218 }
1219 }
1220
1221 if !dependent_objects.is_empty() {
1222 Err(AdapterError::DependentObject(dependent_objects))
1223 } else {
1224 Ok(())
1225 }
1226 }
1227
1228 #[instrument]
1229 pub(super) async fn sequence_drop_owned(
1230 &mut self,
1231 session: &Session,
1232 plan: plan::DropOwnedPlan,
1233 ) -> Result<ExecuteResponse, AdapterError> {
1234 for role_id in &plan.role_ids {
1235 self.catalog().ensure_not_reserved_role(role_id)?;
1236 }
1237
1238 let mut privilege_revokes = plan.privilege_revokes;
1239
1240 let session_catalog = self.catalog().for_session(session);
1242 if rbac::is_rbac_enabled_for_session(session_catalog.system_vars(), session)
1243 && !session.is_superuser()
1244 {
1245 let role_membership =
1247 session_catalog.collect_role_membership(session.current_role_id());
1248 let invalid_revokes: BTreeSet<_> = privilege_revokes
1249 .extract_if(.., |(_, privilege)| {
1250 !role_membership.contains(&privilege.grantor)
1251 })
1252 .map(|(object_id, _)| object_id)
1253 .collect();
1254 for invalid_revoke in invalid_revokes {
1255 let object_description =
1256 ErrorMessageObjectDescription::from_sys_id(&invalid_revoke, &session_catalog);
1257 session.add_notice(AdapterNotice::CannotRevoke { object_description });
1258 }
1259 }
1260
1261 let mut privilege_revokes_by_target: BTreeMap<SystemObjectId, Vec<MzAclItem>> =
1263 BTreeMap::new();
1264 for (object_id, privilege) in privilege_revokes {
1265 privilege_revokes_by_target
1266 .entry(object_id)
1267 .or_default()
1268 .push(privilege);
1269 }
1270 let privilege_revoke_ops =
1271 privilege_revokes_by_target
1272 .into_iter()
1273 .map(|(target_id, privileges)| catalog::Op::UpdatePrivilege {
1274 target_id,
1275 privileges,
1276 variant: UpdatePrivilegeVariant::Revoke,
1277 });
1278 let default_privilege_revoke_ops = plan.default_privilege_revokes.into_iter().map(
1279 |(privilege_object, privilege_acl_item)| catalog::Op::UpdateDefaultPrivilege {
1280 privilege_object,
1281 privilege_acl_item,
1282 variant: UpdatePrivilegeVariant::Revoke,
1283 },
1284 );
1285 let IndexDropNotices {
1286 orphaned,
1287 in_flight,
1288 } = self.check_index_dependents(session, &plan.drop_ids)?;
1289 let DropOps {
1290 ops: drop_ops,
1291 dropped_active_db,
1292 dropped_active_cluster,
1293 } = self.sequence_drop_common(session, plan.drop_ids)?;
1294
1295 let ops = privilege_revoke_ops
1296 .chain(default_privilege_revoke_ops)
1297 .chain(drop_ops.into_iter())
1298 .collect();
1299
1300 self.catalog_transact(Some(session), ops).await?;
1301
1302 if dropped_active_db {
1303 session.add_notice(AdapterNotice::DroppedActiveDatabase {
1304 name: session.vars().database().to_string(),
1305 });
1306 }
1307 if dropped_active_cluster {
1308 session.add_notice(AdapterNotice::DroppedActiveCluster {
1309 name: session.vars().cluster().to_string(),
1310 });
1311 }
1312 self.emit_index_drop_notices(session, orphaned, in_flight);
1313 Ok(ExecuteResponse::DroppedOwned)
1314 }
1315
1316 fn check_index_dependents(
1333 &self,
1334 session: &Session,
1335 drop_ids: &[ObjectId],
1336 ) -> Result<IndexDropNotices, AdapterError> {
1337 let humanizer = self.catalog().for_session(session);
1338 let drop_in_use_allowed = self.catalog().system_config().enable_drop_in_use_index();
1339 let humanize_index = |index: &Index, item_id: &CatalogItemId| {
1340 humanizer
1341 .humanize_id(index.global_id())
1342 .unwrap_or_else(|| item_id.to_string())
1343 };
1344
1345 let drop_set: BTreeSet<ObjectId> = drop_ids.iter().cloned().collect();
1346 let mut orphaned = Vec::new();
1347 let mut blocked = Vec::new();
1348 let mut transient_by_index = Vec::new();
1349 for id in drop_ids {
1350 let ObjectId::Item(item_id) = id else {
1351 continue;
1352 };
1353 let Some(index) = self.catalog().get_entry(item_id).index() else {
1354 continue;
1355 };
1356 let (durable, transient) = self.index_readers(index, &drop_set);
1357 if !transient.is_empty() {
1358 transient_by_index.push((index, item_id, transient));
1359 }
1360 if durable.is_empty() {
1361 continue;
1362 }
1363 if !drop_in_use_allowed {
1364 let dependents = durable
1365 .into_iter()
1366 .map(|dependent_id| {
1367 let entry = self.catalog().get_entry(&dependent_id);
1368 (
1369 entry.item_type().to_string(),
1370 humanizer.minimal_qualification(entry.name()).to_string(),
1371 )
1372 })
1373 .collect();
1374 let index_name = humanizer
1376 .minimal_qualification(self.catalog().get_entry(item_id).name())
1377 .to_string();
1378 blocked.push((index_name, dependents));
1379 continue;
1380 }
1381 let dependant_objects = durable
1382 .into_iter()
1383 .filter_map(|dependent_id| {
1384 let entry = self.catalog().get_entry(&dependent_id);
1385 humanizer.humanize_id(entry.latest_global_id())
1386 })
1387 .collect();
1388 orphaned.push(DroppedInUseIndex {
1389 index_name: humanize_index(index, item_id),
1390 dependant_objects,
1391 });
1392 }
1393
1394 if !blocked.is_empty() {
1395 return Err(AdapterError::IndexInUse { blocked });
1396 }
1397
1398 let mut in_flight = Vec::new();
1399 for (index, item_id, transient) in transient_by_index {
1400 let mut readers = InFlightIndexReaders {
1401 index_name: humanize_index(index, item_id),
1402 ..Default::default()
1403 };
1404 for gid in transient {
1405 match self.active_compute_sinks.get(&gid) {
1406 Some(ActiveComputeSink::Subscribe(_)) => readers.subscribes += 1,
1407 Some(ActiveComputeSink::CopyTo(_)) => readers.copy_tos += 1,
1408 None if self.introspection_subscribes.contains_key(&gid)
1409 || self.is_metric_sink(gid) =>
1410 {
1411 readers.system += 1
1412 }
1413 None => readers.selects += 1,
1416 }
1417 }
1418 in_flight.push(readers);
1419 }
1420
1421 Ok(IndexDropNotices {
1422 orphaned,
1423 in_flight,
1424 })
1425 }
1426
1427 fn index_readers(
1430 &self,
1431 index: &Index,
1432 drop_set: &BTreeSet<ObjectId>,
1433 ) -> (Vec<CatalogItemId>, Vec<GlobalId>) {
1434 let mut durable = Vec::new();
1435 let mut transient = Vec::new();
1436 let readers = self
1437 .controller
1438 .compute
1439 .collection_reverse_dependencies(index.cluster_id, index.global_id())
1440 .ok()
1441 .into_iter()
1442 .flatten();
1443 for gid in readers {
1444 if gid.is_transient() {
1445 transient.push(gid);
1446 continue;
1447 }
1448 let Some(entry) = self.catalog().try_get_entry_by_global_id(&gid) else {
1450 continue;
1451 };
1452 if !drop_set.contains(&ObjectId::Item(entry.id())) {
1453 durable.push(entry.id());
1454 }
1455 }
1456 (durable, transient)
1457 }
1458
1459 fn emit_index_drop_notices(
1464 &self,
1465 session: &Session,
1466 orphaned: Vec<DroppedInUseIndex>,
1467 in_flight: Vec<InFlightIndexReaders>,
1468 ) {
1469 for orphaned in orphaned {
1470 tracing::error!(
1471 index = %orphaned.index_name,
1472 dependents = ?orphaned.dependant_objects,
1473 "dropped an index that other dataflows read from because enable_drop_in_use_index is set"
1474 );
1475 self.metrics
1476 .optimization_notices
1477 .with_label_values(&["DroppedInUseIndex"])
1478 .inc();
1479 session.add_notice(AdapterNotice::DroppedInUseIndex(orphaned));
1480 }
1481 session.add_notices(
1482 in_flight
1483 .into_iter()
1484 .map(AdapterNotice::DroppedIndexReadByInFlightStatements),
1485 );
1486 }
1487
1488 fn sequence_drop_common(
1489 &self,
1490 session: &Session,
1491 ids: Vec<ObjectId>,
1492 ) -> Result<DropOps, AdapterError> {
1493 let mut dropped_active_db = false;
1494 let mut dropped_active_cluster = false;
1495 let mut dropped_roles = BTreeMap::new();
1496 let mut dropped_databases = BTreeSet::new();
1497 let mut dropped_schemas = BTreeSet::new();
1498 let mut role_revokes = BTreeSet::new();
1502 let mut default_privilege_revokes = BTreeSet::new();
1505
1506 let mut clusters_to_drop = BTreeSet::new();
1508
1509 for id in &ids {
1510 match id {
1511 ObjectId::Database(id) => {
1512 let name = self.catalog().get_database(id).name();
1513 if name == session.vars().database() {
1514 dropped_active_db = true;
1515 }
1516 dropped_databases.insert(id);
1517 }
1518 ObjectId::Schema((_, spec)) => {
1519 if let SchemaSpecifier::Id(id) = spec {
1520 dropped_schemas.insert(id);
1521 }
1522 }
1523 ObjectId::Cluster(id) => {
1524 clusters_to_drop.insert(*id);
1525 if let Some(active_id) = self
1526 .catalog()
1527 .active_cluster(session)
1528 .ok()
1529 .map(|cluster| cluster.id())
1530 {
1531 if id == &active_id {
1532 dropped_active_cluster = true;
1533 }
1534 }
1535 }
1536 ObjectId::Role(id) => {
1537 let role = self.catalog().get_role(id);
1538 let name = role.name();
1539 dropped_roles.insert(*id, name);
1540 for (group_id, grantor_id) in &role.membership.map {
1542 role_revokes.insert((*group_id, *id, *grantor_id));
1543 }
1544 }
1545 _ => {}
1546 }
1547 }
1548
1549 for id in &ids {
1550 match id {
1551 ObjectId::ClusterReplica((cluster_id, replica_id)) => {
1555 if !clusters_to_drop.contains(cluster_id) {
1556 let cluster = self.catalog.get_cluster(*cluster_id);
1557 if cluster.is_managed() {
1558 let replica =
1559 cluster.replica(*replica_id).expect("Catalog out of sync");
1560 if !replica.config.location.internal() {
1561 coord_bail!("cannot drop replica of managed cluster");
1562 }
1563 }
1564 }
1565 }
1566 _ => {}
1567 }
1568 }
1569
1570 for role_id in dropped_roles.keys() {
1571 self.catalog().ensure_not_reserved_role(role_id)?;
1572 }
1573 self.validate_dropped_role_ownership(session, &dropped_roles)?;
1574 let dropped_role_ids: BTreeSet<_> = dropped_roles.keys().collect();
1576 for role in self.catalog().user_roles() {
1577 for dropped_role_id in
1578 dropped_role_ids.intersection(&role.membership.map.keys().collect())
1579 {
1580 role_revokes.insert((
1581 **dropped_role_id,
1582 role.id(),
1583 *role
1584 .membership
1585 .map
1586 .get(*dropped_role_id)
1587 .expect("included in keys above"),
1588 ));
1589 }
1590 }
1591
1592 for (default_privilege_object, default_privilege_acls) in
1593 self.catalog().default_privileges()
1594 {
1595 if matches!(
1596 &default_privilege_object.database_id,
1597 Some(database_id) if dropped_databases.contains(database_id),
1598 ) || matches!(
1599 &default_privilege_object.schema_id,
1600 Some(schema_id) if dropped_schemas.contains(schema_id),
1601 ) {
1602 for default_privilege_acl in default_privilege_acls {
1603 default_privilege_revokes.insert((
1604 default_privilege_object.clone(),
1605 default_privilege_acl.clone(),
1606 ));
1607 }
1608 }
1609 }
1610
1611 let ops = role_revokes
1612 .into_iter()
1613 .map(|(role_id, member_id, grantor_id)| catalog::Op::RevokeRole {
1614 role_id,
1615 member_id,
1616 grantor_id,
1617 })
1618 .chain(default_privilege_revokes.into_iter().map(
1619 |(privilege_object, privilege_acl_item)| catalog::Op::UpdateDefaultPrivilege {
1620 privilege_object,
1621 privilege_acl_item,
1622 variant: UpdatePrivilegeVariant::Revoke,
1623 },
1624 ))
1625 .chain(iter::once(catalog::Op::DropObjects(
1626 ids.into_iter()
1627 .map(DropObjectInfo::manual_drop_from_object_id)
1628 .collect(),
1629 )))
1630 .collect();
1631
1632 Ok(DropOps {
1633 ops,
1634 dropped_active_db,
1635 dropped_active_cluster,
1636 })
1637 }
1638
1639 pub(super) fn sequence_explain_schema(
1640 &self,
1641 ExplainSinkSchemaPlan { json_schema, .. }: ExplainSinkSchemaPlan,
1642 ) -> Result<ExecuteResponse, AdapterError> {
1643 let value: serde_json::Value = serde_json::from_str(&json_schema).map_err(|error| {
1644 AdapterError::Explain(mz_repr::explain::ExplainError::SerdeJsonError(error))
1645 })?;
1646 let json = mz_repr::explain::json::json_string(&value);
1647 let row = Row::pack_slice(&[Datum::String(&json)]);
1648 Ok(Self::send_immediate_rows(row))
1649 }
1650
1651 pub(super) fn sequence_show_all_variables(
1652 &self,
1653 session: &Session,
1654 ) -> Result<ExecuteResponse, AdapterError> {
1655 let mut rows = viewable_variables(self.catalog().state(), session)
1656 .map(|v| (v.name(), v.value(), v.description()))
1657 .collect::<Vec<_>>();
1658 rows.sort_by_cached_key(|(name, _, _)| name.to_lowercase());
1659
1660 let rows: Vec<_> = rows
1662 .into_iter()
1663 .map(|(name, val, desc)| {
1664 Row::pack_slice(&[
1665 Datum::String(name),
1666 Datum::String(&val),
1667 Datum::String(desc),
1668 ])
1669 })
1670 .collect();
1671 Ok(Self::send_immediate_rows(rows))
1672 }
1673
1674 pub(super) fn sequence_show_variable(
1675 &self,
1676 session: &Session,
1677 plan: plan::ShowVariablePlan,
1678 ) -> Result<ExecuteResponse, AdapterError> {
1679 if &plan.name == SCHEMA_ALIAS {
1680 let schemas = self.catalog.resolve_search_path(session);
1681 let schema = schemas.first();
1682 return match schema {
1683 Some((database_spec, schema_spec)) => {
1684 let schema_name = &self
1685 .catalog
1686 .get_schema(database_spec, schema_spec, session.conn_id())
1687 .name()
1688 .schema;
1689 let row = Row::pack_slice(&[Datum::String(schema_name)]);
1690 Ok(Self::send_immediate_rows(row))
1691 }
1692 None => {
1693 if session.vars().current_object_missing_warnings() {
1694 session.add_notice(AdapterNotice::NoResolvableSearchPathSchema {
1695 search_path: session
1696 .vars()
1697 .search_path()
1698 .into_iter()
1699 .map(|schema| schema.to_string())
1700 .collect(),
1701 });
1702 }
1703 Ok(Self::send_immediate_rows(Row::pack_slice(&[Datum::Null])))
1704 }
1705 };
1706 }
1707
1708 let variable = session
1709 .vars()
1710 .get(self.catalog().system_config(), &plan.name)
1711 .or_else(|_| self.catalog().system_config().get(&plan.name))?;
1712
1713 variable.visible(session.user(), self.catalog().system_config())?;
1716
1717 let row = Row::pack_slice(&[Datum::String(&variable.value())]);
1718 if variable.name() == vars::DATABASE.name()
1719 && matches!(
1720 self.catalog().resolve_database(&variable.value()),
1721 Err(CatalogError::UnknownDatabase(_))
1722 )
1723 && session.vars().current_object_missing_warnings()
1724 {
1725 let name = variable.value();
1726 session.add_notice(AdapterNotice::DatabaseDoesNotExist { name });
1727 } else if variable.name() == vars::CLUSTER.name()
1728 && matches!(
1729 self.catalog().resolve_cluster(&variable.value()),
1730 Err(CatalogError::UnknownCluster(_))
1731 )
1732 && session.vars().current_object_missing_warnings()
1733 {
1734 let name = variable.value();
1735 session.add_notice(AdapterNotice::ClusterDoesNotExist { name });
1736 }
1737 Ok(Self::send_immediate_rows(row))
1738 }
1739
1740 #[instrument]
1741 pub(super) async fn sequence_inspect_shard(
1742 &self,
1743 session: &Session,
1744 plan: plan::InspectShardPlan,
1745 ) -> Result<ExecuteResponse, AdapterError> {
1746 if !session.user().is_internal() {
1749 return Err(AdapterError::Unauthorized(
1750 rbac::UnauthorizedError::MzSystem {
1751 action: "inspect".into(),
1752 },
1753 ));
1754 }
1755 let state = self
1756 .controller
1757 .storage
1758 .inspect_persist_state(plan.id)
1759 .await?;
1760 let jsonb = Jsonb::from_serde_json(state)?;
1761 Ok(Self::send_immediate_rows(jsonb.into_row()))
1762 }
1763
1764 #[instrument]
1765 pub(super) fn sequence_set_variable(
1766 &self,
1767 session: &mut Session,
1768 plan: plan::SetVariablePlan,
1769 ) -> Result<ExecuteResponse, AdapterError> {
1770 let (name, local) = (plan.name, plan.local);
1771 if &name == TRANSACTION_ISOLATION_VAR_NAME {
1772 self.validate_set_isolation_level(session)?;
1773 }
1774 if &name == vars::CLUSTER.name() {
1775 self.validate_set_cluster(session)?;
1776 }
1777
1778 let vars = session.vars_mut();
1779 let values = match plan.value {
1780 plan::VariableValue::Default => None,
1781 plan::VariableValue::Values(values) => Some(values),
1782 };
1783
1784 match values {
1785 Some(values) => {
1786 vars.set(
1787 self.catalog().system_config(),
1788 &name,
1789 VarInput::SqlSet(&values),
1790 local,
1791 )?;
1792
1793 let vars = session.vars();
1794
1795 if name == vars::OLD_AUTO_ROUTE_CATALOG_QUERIES {
1798 session.add_notice(AdapterNotice::AutoRouteIntrospectionQueriesUsage);
1799 } else if name == vars::CLUSTER.name()
1800 && values[0] == vars::OLD_CATALOG_SERVER_CLUSTER
1801 {
1802 session.add_notice(AdapterNotice::IntrospectionClusterUsage);
1803 }
1804
1805 if name.as_str() == vars::DATABASE.name()
1807 && matches!(
1808 self.catalog().resolve_database(vars.database()),
1809 Err(CatalogError::UnknownDatabase(_))
1810 )
1811 && session.vars().current_object_missing_warnings()
1812 {
1813 let name = vars.database().to_string();
1814 session.add_notice(AdapterNotice::DatabaseDoesNotExist { name });
1815 } else if name.as_str() == vars::CLUSTER.name()
1816 && matches!(
1817 self.catalog().resolve_cluster(vars.cluster()),
1818 Err(CatalogError::UnknownCluster(_))
1819 )
1820 && session.vars().current_object_missing_warnings()
1821 {
1822 let name = vars.cluster().to_string();
1823 session.add_notice(AdapterNotice::ClusterDoesNotExist { name });
1824 } else if name.as_str() == TRANSACTION_ISOLATION_VAR_NAME {
1825 let v = values.into_first().to_lowercase();
1826 if v == IsolationLevel::ReadUncommitted.as_variant_str()
1827 || v == IsolationLevel::ReadCommitted.as_variant_str()
1828 || v == IsolationLevel::RepeatableRead.as_variant_str()
1829 {
1830 session.add_notice(AdapterNotice::UnimplementedIsolationLevel {
1831 isolation_level: v,
1832 });
1833 } else if v == IsolationLevel::StrongSessionSerializable.as_variant_str() {
1834 session.add_notice(AdapterNotice::StrongSessionSerializable);
1835 }
1836 }
1837
1838 if (name.as_str() == TRANSACTION_ISOLATION_VAR_NAME
1842 || name.as_str() == vars::REAL_TIME_RECENCY.name())
1843 && session
1844 .vars()
1845 .transaction_isolation()
1846 .is_bounded_staleness()
1847 && session.vars().real_time_recency()
1848 {
1849 return Err(AdapterError::BoundedStalenessRealTimeRecencyConflict);
1850 }
1851 }
1852 None => vars.reset(self.catalog().system_config(), &name, local)?,
1853 }
1854
1855 Ok(ExecuteResponse::SetVariable { name, reset: false })
1856 }
1857
1858 pub(super) fn sequence_reset_variable(
1859 &self,
1860 session: &mut Session,
1861 plan: plan::ResetVariablePlan,
1862 ) -> Result<ExecuteResponse, AdapterError> {
1863 let name = plan.name;
1864 if &name == TRANSACTION_ISOLATION_VAR_NAME {
1865 self.validate_set_isolation_level(session)?;
1866 }
1867 if &name == vars::CLUSTER.name() {
1868 self.validate_set_cluster(session)?;
1869 }
1870 session
1871 .vars_mut()
1872 .reset(self.catalog().system_config(), &name, false)?;
1873 Ok(ExecuteResponse::SetVariable { name, reset: true })
1874 }
1875
1876 pub(super) fn sequence_set_transaction(
1877 &self,
1878 session: &mut Session,
1879 plan: plan::SetTransactionPlan,
1880 ) -> Result<ExecuteResponse, AdapterError> {
1881 for mode in plan.modes {
1883 match mode {
1884 TransactionMode::AccessMode(_) => {
1885 return Err(AdapterError::Unsupported("SET TRANSACTION <access-mode>"));
1886 }
1887 TransactionMode::IsolationLevel(isolation_level) => {
1888 self.validate_set_isolation_level(session)?;
1889
1890 session.vars_mut().set(
1891 self.catalog().system_config(),
1892 TRANSACTION_ISOLATION_VAR_NAME,
1893 VarInput::Flat(&isolation_level.to_ast_string_stable()),
1894 plan.local,
1895 )?
1896 }
1897 }
1898 }
1899 Ok(ExecuteResponse::SetVariable {
1900 name: TRANSACTION_ISOLATION_VAR_NAME.to_string(),
1901 reset: false,
1902 })
1903 }
1904
1905 fn validate_set_isolation_level(&self, session: &Session) -> Result<(), AdapterError> {
1906 if session.transaction().contains_ops() {
1907 Err(AdapterError::InvalidSetIsolationLevel)
1908 } else {
1909 Ok(())
1910 }
1911 }
1912
1913 fn validate_set_cluster(&self, session: &Session) -> Result<(), AdapterError> {
1914 if session.transaction().contains_ops() {
1915 Err(AdapterError::InvalidSetCluster)
1916 } else {
1917 Ok(())
1918 }
1919 }
1920
1921 #[instrument(level = "debug")]
1922 pub(super) async fn sequence_end_transaction(
1923 &mut self,
1924 mut ctx: ExecuteContext,
1925 mut action: EndTransactionAction,
1926 ) {
1927 if let (EndTransactionAction::Commit, TransactionStatus::Failed(_)) =
1929 (&action, ctx.session().transaction())
1930 {
1931 action = EndTransactionAction::Rollback;
1932 }
1933 let response = match action {
1934 EndTransactionAction::Commit => Ok(PendingTxnResponse::Committed {
1935 params: BTreeMap::new(),
1936 }),
1937 EndTransactionAction::Rollback => Ok(PendingTxnResponse::Rolledback {
1938 params: BTreeMap::new(),
1939 }),
1940 };
1941
1942 let result = self.sequence_end_transaction_inner(&mut ctx, action).await;
1943
1944 let (response, action) = match result {
1945 Ok(Some(TransactionOps::DDL { commands, .. })) => {
1946 self.sequence_ddl_commit(crate::coord::ddl::PendingDdlCommit::transaction(
1947 PendingTxn {
1948 ctx,
1949 response,
1950 action,
1951 },
1952 commands,
1953 ))
1954 .await;
1955 return;
1956 }
1957 Ok(Some(TransactionOps::Writes(writes))) if writes.is_empty() => (response, action),
1958 Ok(Some(TransactionOps::Writes(writes))) => {
1959 let mut collected_writes: BTreeMap<CatalogItemId, SmallVec<_>> = BTreeMap::new();
1960 for WriteOp { id, rows } in writes {
1961 let total_rows = collected_writes.entry(id).or_default();
1962 total_rows.push(rows);
1963 }
1964
1965 self.submit_write(PendingWriteTxn::User {
1966 span: Span::current(),
1967 writes: collected_writes,
1968 responder: UserWriteResponder::Session(PendingTxn {
1969 ctx,
1970 response,
1971 action,
1972 }),
1973 });
1974 return;
1975 }
1976 Ok(Some(TransactionOps::Peeks {
1977 determination,
1978 requires_linearization: RequireLinearization::Required,
1979 ..
1980 })) if ctx.session().vars().transaction_isolation()
1981 == &IsolationLevel::StrictSerializable =>
1982 {
1983 let conn_id = ctx.session().conn_id().clone();
1984 let pending_read_txn = PendingReadTxn {
1985 txn: PendingTxn {
1986 ctx,
1987 response,
1988 action,
1989 },
1990 timestamp_context: determination.timestamp_context,
1991 created: Instant::now(),
1992 num_requeues: 0,
1993 otel_ctx: OpenTelemetryContext::obtain(),
1994 };
1995 if let Some(pending_read_txn) = pending_read_txn.try_finish_read(&self.metrics) {
1996 self.strict_serializable_reads_tx
1997 .send((conn_id, pending_read_txn))
1998 .expect("sending to strict_serializable_reads_tx cannot fail");
1999 }
2000 return;
2001 }
2002 Ok(Some(TransactionOps::Peeks {
2003 determination,
2004 requires_linearization: RequireLinearization::Required,
2005 ..
2006 })) if ctx.session().vars().transaction_isolation()
2007 == &IsolationLevel::StrongSessionSerializable =>
2008 {
2009 if let Some((timeline, ts)) = determination.timestamp_context.timeline_timestamp() {
2010 ctx.session_mut()
2011 .ensure_timestamp_oracle(timeline.clone())
2012 .apply_write(*ts);
2013 }
2014 (response, action)
2015 }
2016 Ok(Some(TransactionOps::SingleStatement { stmt, params })) => {
2017 self.internal_cmd_tx
2018 .send(Message::ExecuteSingleStatementTransaction {
2019 ctx,
2020 otel_ctx: OpenTelemetryContext::obtain(),
2021 stmt,
2022 params,
2023 })
2024 .expect("must send");
2025 return;
2026 }
2027 Ok(_) => (response, action),
2028 Err(err) => (Err(err), EndTransactionAction::Rollback),
2029 };
2030 let changed = ctx.session_mut().vars_mut().end_transaction(action);
2031 let response = response.map(|mut r| {
2033 r.extend_params(changed);
2034 ExecuteResponse::from(r)
2035 });
2036
2037 ctx.retire(response);
2038 }
2039
2040 #[instrument(level = "debug")]
2041 async fn sequence_end_transaction_inner(
2042 &mut self,
2043 ctx: &mut ExecuteContext,
2044 action: EndTransactionAction,
2045 ) -> Result<Option<TransactionOps>, AdapterError> {
2046 let (txn, retire_notify) = self.clear_transaction(ctx.session_mut()).await;
2047 ctx.delay_response_until(retire_notify);
2048
2049 if let EndTransactionAction::Commit = action {
2050 if let Some(mut ops) = txn.into_ops() {
2051 match &mut ops {
2052 TransactionOps::Writes(writes) => {
2053 for WriteOp { id, .. } in &mut writes.iter() {
2054 let _ = self.catalog().try_get_entry(id).ok_or_else(|| {
2056 AdapterError::Catalog(mz_catalog::memory::error::Error {
2057 kind: ErrorKind::Sql(CatalogError::UnknownItem(id.to_string())),
2058 })
2059 })?;
2060 }
2061
2062 writes.retain(|WriteOp { rows, .. }| !rows.is_empty());
2064 }
2065 _ => (),
2066 }
2067 return Ok(Some(ops));
2068 }
2069 }
2070
2071 Ok(None)
2072 }
2073
2074 pub(crate) async fn execute_side_effecting_func(
2082 &mut self,
2083 plan: SideEffectingFunc,
2084 conn_id: ConnectionId,
2085 ) -> Result<ExecuteResponse, AdapterError> {
2086 match plan {
2087 SideEffectingFunc::PgCancelBackend { connection_id } => {
2088 let Some(connection_id) = connection_id else {
2089 return Ok(Self::send_immediate_rows(Row::pack_slice(&[Datum::Null])));
2092 };
2093
2094 if conn_id.unhandled() == connection_id {
2095 return Err(AdapterError::Canceled);
2099 }
2100
2101 if let Some((id_handle, _conn_meta)) =
2106 self.active_conns.get_key_value(&connection_id)
2107 {
2108 self.handle_privileged_cancel(id_handle.clone()).await;
2109 Ok(Self::send_immediate_rows(Row::pack_slice(&[Datum::True])))
2110 } else {
2111 Ok(Self::send_immediate_rows(Row::pack_slice(&[Datum::False])))
2113 }
2114 }
2115 }
2116 }
2117
2118 pub(crate) async fn determine_real_time_recent_timestamp(
2120 &self,
2121 source_ids: impl Iterator<Item = GlobalId>,
2122 real_time_recency_timeout: Duration,
2123 ) -> Result<Option<RtrTimestampFuture>, AdapterError> {
2124 let item_ids = source_ids
2125 .map(|gid| {
2126 self.catalog
2127 .try_resolve_item_id(&gid)
2128 .ok_or_else(|| AdapterError::RtrDropFailure(gid.to_string()))
2129 })
2130 .collect::<Result<Vec<_>, _>>()?;
2131
2132 let mut to_visit = VecDeque::from_iter(item_ids.into_iter().filter(CatalogItemId::is_user));
2138 if to_visit.is_empty() {
2141 return Ok(None);
2142 }
2143
2144 let mut timestamp_objects = BTreeSet::new();
2145
2146 while let Some(id) = to_visit.pop_front() {
2147 timestamp_objects.insert(id);
2148 to_visit.extend(
2149 self.catalog()
2150 .get_entry(&id)
2151 .uses()
2152 .into_iter()
2153 .filter(|id| !timestamp_objects.contains(id) && id.is_user()),
2154 );
2155 }
2156 let timestamp_objects = timestamp_objects
2157 .into_iter()
2158 .flat_map(|item_id| self.catalog().get_entry(&item_id).global_ids())
2159 .collect();
2160
2161 let r = self
2162 .controller
2163 .determine_real_time_recent_timestamp(timestamp_objects, real_time_recency_timeout)
2164 .await?;
2165
2166 Ok(Some(r))
2167 }
2168
2169 pub(crate) async fn await_real_time_recent_timestamp<F>(
2170 catalog: Arc<Catalog>,
2171 fut: F,
2172 ) -> Result<Timestamp, AdapterError>
2173 where
2174 F: Future<Output = Result<Timestamp, StorageError>>,
2175 {
2176 fut.await
2177 .map_err(|error| Self::real_time_recent_timestamp_error(&catalog, error))
2178 }
2179
2180 fn real_time_recent_timestamp_error(catalog: &Catalog, error: StorageError) -> AdapterError {
2181 let rtr_name = |id: &GlobalId| {
2182 catalog
2183 .try_get_entry_by_global_id(id)
2184 .map(|e| e.name().item.clone())
2185 .unwrap_or_else(|| id.to_string())
2186 };
2187
2188 match error {
2189 StorageError::RtrTimeout(id) => AdapterError::RtrTimeout(rtr_name(&id)),
2190 StorageError::RtrDropFailure(id) => AdapterError::RtrDropFailure(rtr_name(&id)),
2191 error => error.into(),
2192 }
2193 }
2194
2195 #[instrument]
2196 pub(super) async fn sequence_explain_plan(
2197 &mut self,
2198 ctx: ExecuteContext,
2199 plan: plan::ExplainPlanPlan,
2200 ) {
2201 match &plan.explainee {
2202 plan::Explainee::Statement(stmt) => match stmt {
2203 plan::ExplaineeStatement::CreateView { .. } => {
2204 self.explain_create_view(ctx, plan).await;
2205 }
2206 plan::ExplaineeStatement::CreateMaterializedView { .. } => {
2207 self.explain_create_materialized_view(ctx, plan).await;
2208 }
2209 plan::ExplaineeStatement::CreateIndex { .. } => {
2210 self.explain_create_index(ctx, plan).await;
2211 }
2212 stmt @ (plan::ExplaineeStatement::Select { .. }
2213 | plan::ExplaineeStatement::Subscribe { .. }) => {
2214 let msg = format!(
2215 "EXPLAIN of a {} reached the coordinator despite frontend routing",
2216 plan::ExplaineeStatementKind::from(stmt)
2217 );
2218 soft_panic_or_log!("{msg}");
2219 ctx.retire(Err(AdapterError::Internal(msg)));
2220 }
2221 },
2222 plan::Explainee::View(_) => {
2223 let result = self.explain_view(&ctx, plan);
2224 ctx.retire(result);
2225 }
2226 plan::Explainee::MaterializedView(_) => {
2227 let result = self.explain_materialized_view(&ctx, plan);
2228 ctx.retire(result);
2229 }
2230 plan::Explainee::Index(_) => {
2231 let result = self.explain_index(&ctx, plan);
2232 ctx.retire(result);
2233 }
2234 plan::Explainee::ReplanView(_) => {
2235 self.explain_replan_view(ctx, plan).await;
2236 }
2237 plan::Explainee::ReplanMaterializedView(_) => {
2238 self.explain_replan_materialized_view(ctx, plan).await;
2239 }
2240 plan::Explainee::ReplanIndex(_) => {
2241 self.explain_replan_index(ctx, plan).await;
2242 }
2243 };
2244 }
2245
2246 pub(super) async fn sequence_explain_pushdown(
2247 &self,
2248 ctx: ExecuteContext,
2249 plan: plan::ExplainPushdownPlan,
2250 ) {
2251 match plan.explainee {
2252 Explainee::Statement(ExplaineeStatement::Select { broken: false, .. }) => {
2253 let msg = "EXPLAIN FILTER PUSHDOWN of a SELECT reached the coordinator despite \
2254 frontend routing";
2255 soft_panic_or_log!("{msg}");
2256 ctx.retire(Err(AdapterError::Internal(msg.into())));
2257 }
2258 Explainee::MaterializedView(item_id) => {
2259 self.explain_pushdown_materialized_view(ctx, item_id).await;
2260 }
2261 _ => {
2262 ctx.retire(Err(AdapterError::Unsupported(
2263 "EXPLAIN FILTER PUSHDOWN queries for this explainee type",
2264 )));
2265 }
2266 };
2267 }
2268
2269 async fn execute_explain_pushdown_with_read_holds(
2271 &self,
2272 ctx: ExecuteContext,
2273 as_of: Antichain<Timestamp>,
2274 mz_now: ResultSpec<'static>,
2275 read_holds: Option<ReadHolds>,
2276 imports: impl IntoIterator<Item = (GlobalId, MfpPlan<LirScalarExpr>)> + 'static,
2277 ) {
2278 let fut = self
2279 .explain_pushdown_future(ctx.session(), as_of, mz_now, imports)
2280 .await;
2281 task::spawn(|| "render explain pushdown", async move {
2282 let _read_holds = read_holds;
2284 let res = fut.await;
2285 ctx.retire(res);
2286 });
2287 }
2288
2289 async fn explain_pushdown_future<I: IntoIterator<Item = (GlobalId, MfpPlan<LirScalarExpr>)>>(
2291 &self,
2292 session: &Session,
2293 as_of: Antichain<Timestamp>,
2294 mz_now: ResultSpec<'static>,
2295 imports: I,
2296 ) -> impl Future<Output = Result<ExecuteResponse, AdapterError>> + use<I> {
2297 super::explain_pushdown_future_inner(
2299 session,
2300 &self.catalog,
2301 &self.controller.storage_collections,
2302 as_of,
2303 mz_now,
2304 imports,
2305 )
2306 .await
2307 }
2308
2309 #[instrument]
2310 pub(super) async fn sequence_alter_retain_history(
2311 &mut self,
2312 ctx: &mut ExecuteContext,
2313 plan: plan::AlterRetainHistoryPlan,
2314 ) -> Result<ExecuteResponse, AdapterError> {
2315 let ops = vec![catalog::Op::AlterRetainHistory {
2316 id: plan.id,
2317 value: plan.value,
2318 window: plan.window,
2319 }];
2320 self.catalog_transact_with_context(None, Some(ctx), ops)
2321 .await?;
2322 Ok(ExecuteResponse::AlteredObject(plan.object_type))
2323 }
2324
2325 #[instrument]
2326 pub(super) async fn sequence_alter_source_timestamp_interval(
2327 &mut self,
2328 ctx: &mut ExecuteContext,
2329 plan: plan::AlterSourceTimestampIntervalPlan,
2330 ) -> Result<ExecuteResponse, AdapterError> {
2331 let ops = vec![catalog::Op::AlterSourceTimestampInterval {
2332 id: plan.id,
2333 value: plan.value,
2334 interval: plan.interval,
2335 }];
2336 self.catalog_transact_with_context(None, Some(ctx), ops)
2337 .await?;
2338 Ok(ExecuteResponse::AlteredObject(ObjectType::Source))
2339 }
2340
2341 #[instrument]
2342 pub(super) async fn sequence_alter_role(
2343 &mut self,
2344 session: &Session,
2345 plan::AlterRolePlan { id, name, option }: plan::AlterRolePlan,
2346 ) -> Result<ExecuteResponse, AdapterError> {
2347 let catalog = self.catalog().for_session(session);
2348 let role = catalog.get_role(&id);
2349
2350 let mut notices = vec![];
2352
2353 let mut attributes: RoleAttributesRaw = role.attributes().clone().into();
2355 let mut vars = role.vars().clone();
2356
2357 let mut nopassword = false;
2360
2361 match option {
2363 PlannedAlterRoleOption::Attributes(attrs) => {
2364 self.validate_role_attributes(&attrs.clone().into())?;
2365
2366 if let Some(inherit) = attrs.inherit {
2367 attributes.inherit = inherit;
2368 }
2369
2370 if let Some(password) = attrs.password {
2371 attributes.password = Some(password);
2372 attributes.scram_iterations =
2373 Some(self.catalog().system_config().scram_iterations())
2374 }
2375
2376 if let Some(superuser) = attrs.superuser {
2377 attributes.superuser = Some(superuser);
2378 }
2379
2380 if let Some(login) = attrs.login {
2381 attributes.login = Some(login);
2382 }
2383
2384 if attrs.nopassword.unwrap_or(false) {
2385 nopassword = true;
2386 }
2387
2388 if let Some(notice) = self.should_emit_rbac_notice(session) {
2389 notices.push(notice);
2390 }
2391 }
2392 PlannedAlterRoleOption::Variable(variable) => {
2393 let session_var = session.vars().inspect(variable.name())?;
2395 session_var.visible(session.user(), catalog.system_vars())?;
2397
2398 if variable.name() == vars::OLD_AUTO_ROUTE_CATALOG_QUERIES {
2401 notices.push(AdapterNotice::AutoRouteIntrospectionQueriesUsage);
2402 } else if let PlannedRoleVariable::Set {
2403 name,
2404 value: VariableValue::Values(vals),
2405 } = &variable
2406 {
2407 if name == vars::CLUSTER.name() && vals[0] == vars::OLD_CATALOG_SERVER_CLUSTER {
2408 notices.push(AdapterNotice::IntrospectionClusterUsage);
2409 }
2410 }
2411
2412 let var_name = match variable {
2413 PlannedRoleVariable::Set { name, value } => {
2414 match &value {
2416 VariableValue::Default => {
2417 vars.remove(&name);
2418 }
2419 VariableValue::Values(vals) => {
2420 let var = match &vals[..] {
2421 [val] => OwnedVarInput::Flat(val.clone()),
2422 vals => OwnedVarInput::SqlSet(vals.to_vec()),
2423 };
2424 session_var.check(var.borrow())?;
2426
2427 vars.insert(name.clone(), var);
2428 }
2429 };
2430 name
2431 }
2432 PlannedRoleVariable::Reset { name } => {
2433 vars.remove(&name);
2435 name
2436 }
2437 };
2438
2439 notices.push(AdapterNotice::VarDefaultUpdated {
2441 role: Some(name.clone()),
2442 var_name: Some(var_name),
2443 });
2444 }
2445 }
2446
2447 let op = catalog::Op::AlterRole {
2448 id,
2449 name,
2450 attributes,
2451 nopassword,
2452 vars: RoleVars { map: vars },
2453 };
2454 let response = self
2455 .catalog_transact(Some(session), vec![op])
2456 .await
2457 .map(|_| ExecuteResponse::AlteredRole)?;
2458
2459 session.add_notices(notices);
2461
2462 Ok(response)
2463 }
2464
2465 #[instrument]
2466 pub(super) async fn sequence_alter_sink_prepare(
2467 &mut self,
2468 ctx: ExecuteContext,
2469 plan: plan::AlterSinkPlan,
2470 ) {
2471 let id_bundle = crate::CollectionIdBundle {
2473 storage_ids: BTreeSet::from_iter([plan.sink.from]),
2474 compute_ids: BTreeMap::new(),
2475 };
2476 let read_hold = self.acquire_read_holds(&id_bundle);
2477
2478 let Some(read_ts) = read_hold.least_valid_read().into_option() else {
2479 ctx.retire(Err(AdapterError::UnreadableSinkCollection));
2480 return;
2481 };
2482
2483 let otel_ctx = OpenTelemetryContext::obtain();
2484 let from_item_id = self.catalog().resolve_item_id(&plan.sink.from);
2485
2486 let plan_validity = PlanValidity::new(
2487 self.catalog(),
2488 BTreeSet::from_iter([plan.item_id, from_item_id]),
2489 Some(plan.in_cluster),
2490 None,
2491 ctx.session().role_metadata().clone(),
2492 );
2493
2494 info!(
2495 "preparing alter sink for {}: frontiers={:?} export={:?}",
2496 plan.global_id,
2497 self.controller
2498 .storage_collections
2499 .collections_frontiers(vec![plan.global_id, plan.sink.from]),
2500 self.controller.storage.export(plan.global_id)
2501 );
2502
2503 self.install_storage_watch_set(
2509 ctx.session().conn_id().clone(),
2510 BTreeSet::from_iter([plan.global_id]),
2511 read_ts,
2512 WatchSetResponse::AlterSinkReady(AlterSinkReadyContext {
2513 ctx: Some(ctx),
2514 otel_ctx,
2515 plan,
2516 plan_validity,
2517 read_hold,
2518 }),
2519 ).expect("plan validity verified above; we are on the coordinator main task, so they couldn't have gone away since then");
2520 }
2521
2522 #[instrument]
2523 pub async fn sequence_alter_sink_finish(&mut self, mut ctx: AlterSinkReadyContext) {
2524 ctx.otel_ctx.attach_as_parent();
2525
2526 let plan::AlterSinkPlan {
2527 item_id,
2528 global_id,
2529 sink: sink_plan,
2530 with_snapshot,
2531 in_cluster,
2532 set_options,
2533 reset_options,
2534 } = ctx.plan.clone();
2535
2536 match ctx.plan_validity.check(self.catalog()) {
2545 Ok(()) => {}
2546 Err(err) => {
2547 ctx.retire(Err(err));
2548 return;
2549 }
2550 }
2551
2552 let entry = self.catalog().get_entry(&item_id);
2553 let CatalogItem::Sink(old_sink) = entry.item() else {
2554 panic!("invalid item kind for `AlterSinkPlan`");
2555 };
2556
2557 if sink_plan.version != old_sink.version + 1 {
2558 ctx.retire(Err(AdapterError::ChangedPlan(
2559 "sink was altered concurrently".into(),
2560 )));
2561 return;
2562 }
2563
2564 info!(
2565 "finishing alter sink for {global_id}: frontiers={:?} export={:?}",
2566 self.controller
2567 .storage_collections
2568 .collections_frontiers(vec![global_id, sink_plan.from]),
2569 self.controller.storage.export(global_id),
2570 );
2571
2572 let write_frontier = &self
2575 .controller
2576 .storage
2577 .export(global_id)
2578 .expect("sink known to exist")
2579 .write_frontier;
2580 let as_of = ctx.read_hold.least_valid_read();
2581 assert!(
2582 write_frontier.iter().all(|t| as_of.less_than(t)),
2583 "{:?} should be strictly less than {:?}",
2584 &*as_of,
2585 &**write_frontier
2586 );
2587
2588 let create_sql = &old_sink.create_sql;
2594 let parsed = mz_sql::parse::parse(create_sql).expect("valid create_sql");
2595 let Statement::CreateSink(mut stmt) = parsed.into_element().ast else {
2596 unreachable!("invalid statement kind for sink");
2597 };
2598
2599 plan::apply_sink_option_edits(
2601 &mut stmt.with_options,
2602 &[CreateSinkOption {
2603 name: CreateSinkOptionName::Version,
2604 value: Some(WithOptionValue::Value(mz_sql::ast::Value::Number(
2605 sink_plan.version.to_string(),
2606 ))),
2607 }],
2608 &[],
2609 );
2610
2611 let conn_catalog = self.catalog().for_system_session();
2612 let (mut stmt, resolved_ids) =
2613 mz_sql::names::resolve(&conn_catalog, stmt).expect("resolvable create_sql");
2614
2615 plan::apply_sink_option_edits(&mut stmt.with_options, &set_options, &reset_options);
2617
2618 let from_entry = self.catalog().get_entry_by_global_id(&sink_plan.from);
2620 let full_name = self.catalog().resolve_full_name(from_entry.name(), None);
2621 stmt.from = ResolvedItemName::Item {
2622 id: from_entry.id(),
2623 qualifiers: from_entry.name.qualifiers.clone(),
2624 full_name,
2625 print_id: true,
2626 version: from_entry.version,
2627 };
2628
2629 let mut resolved_ids = resolved_ids;
2636 resolved_ids.remove_item(&self.catalog().resolve_item_id(&old_sink.from));
2637 resolved_ids.add_item(from_entry.id());
2638
2639 let new_sink = Sink {
2640 create_sql: stmt.to_ast_string_stable(),
2641 global_id,
2642 from: sink_plan.from,
2643 connection: sink_plan.connection.clone(),
2644 envelope: sink_plan.envelope,
2645 version: sink_plan.version,
2646 with_snapshot,
2647 resolved_ids,
2648 cluster_id: in_cluster,
2649 commit_interval: sink_plan.commit_interval,
2650 };
2651
2652 let ops = vec![catalog::Op::UpdateItem {
2653 id: item_id,
2654 name: entry.name().clone(),
2655 to_item: CatalogItem::Sink(new_sink),
2656 }];
2657
2658 match self
2659 .catalog_transact(Some(ctx.ctx().session_mut()), ops)
2660 .await
2661 {
2662 Ok(()) => {}
2663 Err(err) => {
2664 ctx.retire(Err(err));
2665 return;
2666 }
2667 }
2668
2669 let storage_sink_desc = StorageSinkDesc {
2670 from: sink_plan.from,
2671 from_desc: from_entry
2672 .relation_desc()
2673 .expect("sinks can only be built on items with descs")
2674 .into_owned(),
2675 connection: sink_plan
2676 .connection
2677 .clone()
2678 .into_inline_connection(self.catalog().state()),
2679 envelope: sink_plan.envelope,
2680 as_of,
2681 with_snapshot,
2682 version: sink_plan.version,
2683 from_storage_metadata: (),
2684 to_storage_metadata: (),
2685 commit_interval: sink_plan.commit_interval,
2686 };
2687
2688 self.controller
2689 .storage
2690 .alter_export(
2691 global_id,
2692 ExportDescription {
2693 sink: storage_sink_desc,
2694 instance_id: in_cluster,
2695 },
2696 )
2697 .await
2698 .unwrap_or_terminate("cannot fail to alter source desc");
2699
2700 ctx.retire(Ok(ExecuteResponse::AlteredObject(ObjectType::Sink)));
2701 }
2702
2703 #[instrument]
2704 pub(super) async fn sequence_alter_connection(
2705 &mut self,
2706 ctx: ExecuteContext,
2707 AlterConnectionPlan { id, action }: AlterConnectionPlan,
2708 ) {
2709 match action {
2710 AlterConnectionAction::RotateKeys => {
2711 self.sequence_rotate_keys(ctx, id).await;
2712 }
2713 AlterConnectionAction::AlterOptions {
2714 set_options,
2715 drop_options,
2716 validate,
2717 } => {
2718 self.sequence_alter_connection_options(ctx, id, set_options, drop_options, validate)
2719 .await
2720 }
2721 }
2722 }
2723
2724 #[instrument]
2725 async fn sequence_alter_connection_options(
2726 &mut self,
2727 mut ctx: ExecuteContext,
2728 id: CatalogItemId,
2729 set_options: BTreeMap<ConnectionOptionName, Option<WithOptionValue<mz_sql::names::Aug>>>,
2730 drop_options: BTreeSet<ConnectionOptionName>,
2731 validate: bool,
2732 ) {
2733 let cur_entry = self.catalog().get_entry(&id);
2734 let cur_conn = cur_entry.connection().expect("known to be connection");
2735 let connection_gid = cur_conn.global_id();
2736
2737 let inner = || -> Result<Connection, AdapterError> {
2738 let create_conn_stmt = match mz_sql::parse::parse(&cur_conn.create_sql)
2740 .expect("invalid create sql persisted to catalog")
2741 .into_element()
2742 .ast
2743 {
2744 Statement::CreateConnection(stmt) => stmt,
2745 _ => unreachable!("proved type is source"),
2746 };
2747
2748 let catalog = self.catalog().for_system_session();
2749
2750 let (mut create_conn_stmt, resolved_ids) =
2752 mz_sql::names::resolve(&catalog, create_conn_stmt)
2753 .map_err(|e| AdapterError::internal("ALTER CONNECTION", e))?;
2754
2755 create_conn_stmt
2757 .values
2758 .retain(|o| !set_options.contains_key(&o.name) && !drop_options.contains(&o.name));
2759
2760 create_conn_stmt.values.extend(
2762 set_options
2763 .into_iter()
2764 .map(|(name, value)| ConnectionOption { name, value }),
2765 );
2766
2767 let mut catalog = self.catalog().for_system_session();
2770 catalog.mark_id_unresolvable_for_replanning(id);
2771
2772 let plan = match mz_sql::plan::plan(
2774 None,
2775 &catalog,
2776 Statement::CreateConnection(create_conn_stmt),
2777 &Params::empty(),
2778 &resolved_ids,
2779 )
2780 .map_err(|e| AdapterError::InvalidAlter("CONNECTION", e))?
2781 {
2782 (Plan::CreateConnection(plan), _sql_impl_ids) => plan,
2783 (p, _) => {
2784 unreachable!("create connection plan is only valid response, got {:?}", p)
2785 }
2786 };
2787
2788 let create_conn_stmt = match mz_sql::parse::parse(&plan.connection.create_sql)
2790 .expect("invalid create sql persisted to catalog")
2791 .into_element()
2792 .ast
2793 {
2794 Statement::CreateConnection(stmt) => stmt,
2795 _ => unreachable!("proved type is source"),
2796 };
2797
2798 let catalog = self.catalog().for_system_session();
2799
2800 let (_, new_deps) = mz_sql::names::resolve(&catalog, create_conn_stmt)
2802 .map_err(|e| AdapterError::internal("ALTER CONNECTION", e))?;
2803
2804 Ok(Connection {
2805 create_sql: plan.connection.create_sql,
2806 global_id: cur_conn.global_id,
2807 details: plan.connection.details,
2808 resolved_ids: new_deps,
2809 })
2810 };
2811
2812 let conn = match inner() {
2813 Ok(conn) => conn,
2814 Err(e) => {
2815 return ctx.retire(Err(e));
2816 }
2817 };
2818
2819 let usage_check = {
2823 let catalog = self.catalog().for_session(ctx.session());
2824 rbac::check_usage(
2825 &catalog,
2826 ctx.session(),
2827 &conn.resolved_ids,
2828 &rbac::CREATE_ITEM_USAGE,
2829 )
2830 };
2831 if let Err(err) = usage_check {
2832 return ctx.retire(Err(err.into()));
2833 }
2834
2835 if let Err(err) = check_connection_details(&conn.details) {
2838 return ctx.retire(Err(AdapterError::InvalidAlter("CONNECTION", err)));
2839 }
2840
2841 if let Err(err) = self
2844 .check_connection_secret_content_guards(&conn.details)
2845 .await
2846 {
2847 return ctx.retire(Err(err));
2848 }
2849
2850 if validate {
2851 let connection = conn
2852 .details
2853 .to_connection()
2854 .into_inline_connection(self.catalog().state());
2855
2856 let internal_cmd_tx = self.internal_cmd_tx.clone();
2857 let catalog = self.owned_catalog();
2858 let conn_id = ctx.session().conn_id().clone();
2859 let otel_ctx = OpenTelemetryContext::obtain();
2860 let role_metadata = ctx.session().role_metadata().clone();
2861 let current_storage_parameters = self.controller.storage.config().clone();
2862
2863 task::spawn(
2864 || format!("validate_alter_connection:{conn_id}"),
2865 async move {
2866 let resolved_ids = conn.resolved_ids.clone();
2867 let dependency_ids: BTreeSet<_> = resolved_ids.items().copied().collect();
2868 let result = match std::panic::AssertUnwindSafe(
2869 connection.validate(id, ¤t_storage_parameters),
2870 )
2871 .ore_catch_unwind()
2872 .await
2873 {
2874 Ok(Ok(())) => Ok(conn),
2875 Ok(Err(err)) => Err(err.into()),
2876 Err(_panic) => {
2877 tracing::error!("alter connection validation panicked");
2878 Err(AdapterError::Internal(
2879 "connection validation panicked".into(),
2880 ))
2881 }
2882 };
2883
2884 let result = internal_cmd_tx.send(Message::AlterConnectionValidationReady(
2886 AlterConnectionValidationReady {
2887 ctx,
2888 result,
2889 connection_id: id,
2890 connection_gid,
2891 plan_validity: PlanValidity::new(
2892 &catalog,
2893 dependency_ids.clone(),
2894 None,
2895 None,
2896 role_metadata,
2897 ),
2898 otel_ctx,
2899 resolved_ids,
2900 },
2901 ));
2902 if let Err(e) = result {
2903 tracing::warn!("internal_cmd_rx dropped before we could send: {:?}", e);
2904 }
2905 },
2906 );
2907 } else {
2908 let result = self
2909 .sequence_alter_connection_stage_finish(ctx.session_mut(), id, conn)
2910 .await;
2911 ctx.retire(result);
2912 }
2913 }
2914
2915 #[instrument]
2916 pub(crate) async fn sequence_alter_connection_stage_finish(
2917 &mut self,
2918 session: &Session,
2919 id: CatalogItemId,
2920 connection: Connection,
2921 ) -> Result<ExecuteResponse, AdapterError> {
2922 match self.catalog.get_entry(&id).item() {
2923 CatalogItem::Connection(curr_conn) => {
2924 curr_conn
2925 .details
2926 .to_connection()
2927 .alter_compatible(curr_conn.global_id, &connection.details.to_connection())
2928 .map_err(StorageError::from)?;
2929 }
2930 _ => unreachable!("known to be a connection"),
2931 };
2932
2933 let ops = vec![catalog::Op::UpdateItem {
2934 id,
2935 name: self.catalog.get_entry(&id).name().clone(),
2936 to_item: CatalogItem::Connection(connection.clone()),
2937 }];
2938
2939 self.catalog_transact(Some(session), ops).await?;
2940
2941 Ok(ExecuteResponse::AlteredObject(ObjectType::Connection))
2948 }
2949
2950 #[instrument]
2951 pub(super) async fn sequence_alter_system_set(
2952 &mut self,
2953 session: &Session,
2954 plan::AlterSystemSetPlan { name, value }: plan::AlterSystemSetPlan,
2955 ) -> Result<ExecuteResponse, AdapterError> {
2956 self.is_user_allowed_to_alter_system(session, Some(&name))?;
2957 if NETWORK_POLICY.name.to_string().to_lowercase() == name.clone().to_lowercase() {
2959 self.validate_alter_system_network_policy(session, &value)?;
2960 }
2961
2962 let op = match value {
2963 plan::VariableValue::Values(values) => catalog::Op::UpdateSystemConfiguration {
2964 name: name.clone(),
2965 value: OwnedVarInput::SqlSet(values),
2966 },
2967 plan::VariableValue::Default => {
2968 catalog::Op::ResetSystemConfiguration { name: name.clone() }
2969 }
2970 };
2971 self.catalog_transact(Some(session), vec![op]).await?;
2972
2973 Self::notice_if_startup_only(session, &name);
2974 session.add_notice(AdapterNotice::VarDefaultUpdated {
2975 role: None,
2976 var_name: Some(name),
2977 });
2978 Ok(ExecuteResponse::AlteredSystemConfiguration)
2979 }
2980
2981 #[instrument]
2982 pub(super) async fn sequence_alter_system_reset(
2983 &mut self,
2984 session: &Session,
2985 plan::AlterSystemResetPlan { name }: plan::AlterSystemResetPlan,
2986 ) -> Result<ExecuteResponse, AdapterError> {
2987 self.is_user_allowed_to_alter_system(session, Some(&name))?;
2988 let op = catalog::Op::ResetSystemConfiguration { name: name.clone() };
2989 self.catalog_transact(Some(session), vec![op]).await?;
2990 Self::notice_if_startup_only(session, &name);
2991 session.add_notice(AdapterNotice::VarDefaultUpdated {
2992 role: None,
2993 var_name: Some(name),
2994 });
2995 Ok(ExecuteResponse::AlteredSystemConfiguration)
2996 }
2997
2998 #[instrument]
2999 pub(super) async fn sequence_alter_system_reset_all(
3000 &mut self,
3001 session: &Session,
3002 _: plan::AlterSystemResetAllPlan,
3003 ) -> Result<ExecuteResponse, AdapterError> {
3004 self.is_user_allowed_to_alter_system(session, None)?;
3005 let startup_only_changed = self.startup_only_vars_changed_by_reset_all();
3008 let op = catalog::Op::ResetAllSystemConfiguration;
3009 self.catalog_transact(Some(session), vec![op]).await?;
3010 for name in startup_only_changed {
3011 session.add_notice(AdapterNotice::StartupOnlyVarUpdated {
3012 var_name: name.to_string(),
3013 });
3014 }
3015 session.add_notice(AdapterNotice::VarDefaultUpdated {
3016 role: None,
3017 var_name: None,
3018 });
3019 Ok(ExecuteResponse::AlteredSystemConfiguration)
3020 }
3021
3022 fn startup_only_vars() -> [&'static str; 2] {
3032 [
3033 MAX_CONCURRENT_OCC_WRITES.name(),
3034 ENABLE_EXPRESSION_CACHE.name(),
3035 ]
3036 }
3037
3038 fn notice_if_startup_only(session: &Session, name: &str) {
3041 if Self::startup_only_vars()
3042 .iter()
3043 .any(|n| n.eq_ignore_ascii_case(name))
3044 {
3045 session.add_notice(AdapterNotice::StartupOnlyVarUpdated {
3046 var_name: name.to_string(),
3047 });
3048 }
3049 }
3050
3051 fn startup_only_vars_changed_by_reset_all(&self) -> Vec<&'static str> {
3055 let config = self.catalog().system_config();
3059 let defaults = config.defaults();
3060 Self::startup_only_vars()
3061 .into_iter()
3062 .filter(|name| {
3063 let current = config
3066 .get(name)
3067 .expect("startup-only parameter is a registered system var")
3068 .value();
3069 defaults
3070 .get(*name)
3071 .is_some_and(|default| default != ¤t)
3072 })
3073 .collect()
3074 }
3075
3076 fn is_user_allowed_to_alter_system(
3078 &self,
3079 session: &Session,
3080 var_name: Option<&str>,
3081 ) -> Result<(), AdapterError> {
3082 match (session.user().kind(), var_name) {
3083 (UserKind::Superuser, None) if session.user().is_internal() => Ok(()),
3085 (UserKind::Superuser, Some(name))
3087 if session.user().is_internal()
3088 || self.catalog().system_config().user_modifiable(name) =>
3089 {
3090 let var = self.catalog().system_config().get(name)?;
3093 var.visible(session.user(), self.catalog().system_config())?;
3094 Ok(())
3095 }
3096 (UserKind::Regular, Some(name))
3099 if self.catalog().system_config().user_modifiable(name) =>
3100 {
3101 Err(AdapterError::Unauthorized(
3102 rbac::UnauthorizedError::Superuser {
3103 action: format!("toggle the '{name}' system configuration parameter"),
3104 },
3105 ))
3106 }
3107 _ => Err(AdapterError::Unauthorized(
3108 rbac::UnauthorizedError::MzSystem {
3109 action: "alter system".into(),
3110 },
3111 )),
3112 }
3113 }
3114
3115 fn validate_alter_system_network_policy(
3116 &self,
3117 session: &Session,
3118 policy_value: &plan::VariableValue,
3119 ) -> Result<(), AdapterError> {
3120 let policy_name = match &policy_value {
3121 plan::VariableValue::Default => Some(NETWORK_POLICY.default_value().format()),
3123 plan::VariableValue::Values(values) if values.len() == 1 => {
3124 values.iter().next().cloned()
3125 }
3126 plan::VariableValue::Values(values) => {
3127 tracing::warn!(?values, "can't set multiple network policies at once");
3128 None
3129 }
3130 };
3131 let maybe_network_policy = policy_name
3132 .as_ref()
3133 .and_then(|name| self.catalog.get_network_policy_by_name(name));
3134 let Some(network_policy) = maybe_network_policy else {
3135 return Err(AdapterError::PlanError(plan::PlanError::VarError(
3136 VarError::InvalidParameterValue {
3137 name: NETWORK_POLICY.name(),
3138 invalid_values: vec![policy_name.unwrap_or_else(|| "<none>".to_string())],
3139 reason: "no network policy with such name exists".to_string(),
3140 },
3141 )));
3142 };
3143 self.validate_alter_network_policy(session, &network_policy.rules)
3144 }
3145
3146 fn validate_alter_network_policy(
3151 &self,
3152 session: &Session,
3153 policy_rules: &Vec<NetworkPolicyRule>,
3154 ) -> Result<(), AdapterError> {
3155 if session.user().is_internal() {
3158 return Ok(());
3159 }
3160 if let Some(ip) = session.meta().client_ip() {
3161 validate_ip_with_policy_rules(ip, policy_rules)
3162 .map_err(|_| AdapterError::PlanError(plan::PlanError::NetworkPolicyLockoutError))?;
3163 } else {
3164 return Err(AdapterError::NetworkPolicyDenied(
3167 NetworkPolicyError::MissingIp,
3168 ));
3169 }
3170 Ok(())
3171 }
3172
3173 #[instrument]
3174 pub(super) async fn sequence_grant_privileges(
3175 &mut self,
3176 session: &Session,
3177 plan::GrantPrivilegesPlan {
3178 update_privileges,
3179 grantees,
3180 }: plan::GrantPrivilegesPlan,
3181 ) -> Result<ExecuteResponse, AdapterError> {
3182 self.sequence_update_privileges(
3183 session,
3184 update_privileges,
3185 grantees,
3186 UpdatePrivilegeVariant::Grant,
3187 )
3188 .await
3189 }
3190
3191 #[instrument]
3192 pub(super) async fn sequence_revoke_privileges(
3193 &mut self,
3194 session: &Session,
3195 plan::RevokePrivilegesPlan {
3196 update_privileges,
3197 revokees,
3198 }: plan::RevokePrivilegesPlan,
3199 ) -> Result<ExecuteResponse, AdapterError> {
3200 self.sequence_update_privileges(
3201 session,
3202 update_privileges,
3203 revokees,
3204 UpdatePrivilegeVariant::Revoke,
3205 )
3206 .await
3207 }
3208
3209 #[instrument]
3210 async fn sequence_update_privileges(
3211 &mut self,
3212 session: &Session,
3213 update_privileges: Vec<UpdatePrivilege>,
3214 grantees: Vec<RoleId>,
3215 variant: UpdatePrivilegeVariant,
3216 ) -> Result<ExecuteResponse, AdapterError> {
3217 let mut ops = Vec::with_capacity(update_privileges.len());
3218 let mut warnings = Vec::new();
3219 let catalog = self.catalog().for_session(session);
3220
3221 for UpdatePrivilege {
3222 acl_mode,
3223 target_id,
3224 grantor,
3225 acl_from_all,
3226 } in update_privileges
3227 {
3228 let actual_object_type = catalog.get_system_object_type(&target_id);
3229 if actual_object_type.is_relation() && !acl_from_all {
3235 let applicable_privileges = rbac::all_object_privileges(actual_object_type);
3236 let non_applicable_privileges = acl_mode.difference(applicable_privileges);
3237 if !non_applicable_privileges.is_empty() {
3238 let object_description =
3239 ErrorMessageObjectDescription::from_sys_id(&target_id, &catalog);
3240 warnings.push(AdapterNotice::NonApplicablePrivilegeTypes {
3241 non_applicable_privileges,
3242 object_description,
3243 })
3244 }
3245 }
3246
3247 if let SystemObjectId::Object(object_id) = &target_id {
3248 self.catalog()
3249 .ensure_not_reserved_object(object_id, session.conn_id())?;
3250 }
3251
3252 let privileges = self
3253 .catalog()
3254 .get_privileges(&target_id, session.conn_id())
3255 .ok_or(AdapterError::Unsupported(
3258 "GRANTs/REVOKEs on an object type with no privileges",
3259 ))?;
3260
3261 let mut target_privileges = Vec::with_capacity(grantees.len());
3264 for grantee in &grantees {
3265 self.catalog().ensure_not_system_role(grantee)?;
3266 self.catalog().ensure_not_predefined_role(grantee)?;
3267 let existing_privilege = privileges
3268 .get_acl_item(grantee, &grantor)
3269 .map(Cow::Borrowed)
3270 .unwrap_or_else(|| Cow::Owned(MzAclItem::empty(*grantee, grantor)));
3271
3272 let changes = match variant {
3274 UpdatePrivilegeVariant::Grant => {
3275 !existing_privilege.acl_mode.contains(acl_mode)
3276 }
3277 UpdatePrivilegeVariant::Revoke => !existing_privilege
3278 .acl_mode
3279 .intersection(acl_mode)
3280 .is_empty(),
3281 };
3282 if changes {
3283 target_privileges.push(MzAclItem {
3284 grantee: *grantee,
3285 grantor,
3286 acl_mode,
3287 });
3288 }
3289 }
3290 if !target_privileges.is_empty() {
3291 ops.push(catalog::Op::UpdatePrivilege {
3292 target_id: target_id.clone(),
3293 privileges: target_privileges,
3294 variant,
3295 });
3296 }
3297 }
3298
3299 if ops.is_empty() {
3300 session.add_notices(warnings);
3301 return Ok(variant.into());
3302 }
3303
3304 let res = self
3305 .catalog_transact(Some(session), ops)
3306 .await
3307 .map(|_| match variant {
3308 UpdatePrivilegeVariant::Grant => ExecuteResponse::GrantedPrivilege,
3309 UpdatePrivilegeVariant::Revoke => ExecuteResponse::RevokedPrivilege,
3310 });
3311 if res.is_ok() {
3312 session.add_notices(warnings);
3313 }
3314 res
3315 }
3316
3317 #[instrument]
3318 pub(super) async fn sequence_alter_default_privileges(
3319 &mut self,
3320 session: &Session,
3321 plan::AlterDefaultPrivilegesPlan {
3322 privilege_objects,
3323 privilege_acl_items,
3324 is_grant,
3325 }: plan::AlterDefaultPrivilegesPlan,
3326 ) -> Result<ExecuteResponse, AdapterError> {
3327 let mut ops = Vec::with_capacity(privilege_objects.len() * privilege_acl_items.len());
3328 let variant = if is_grant {
3329 UpdatePrivilegeVariant::Grant
3330 } else {
3331 UpdatePrivilegeVariant::Revoke
3332 };
3333 for privilege_object in &privilege_objects {
3334 self.catalog()
3335 .ensure_not_system_role(&privilege_object.role_id)?;
3336 self.catalog()
3337 .ensure_not_predefined_role(&privilege_object.role_id)?;
3338 if let Some(database_id) = privilege_object.database_id {
3339 self.catalog()
3340 .ensure_not_reserved_object(&database_id.into(), session.conn_id())?;
3341 }
3342 if let Some(schema_id) = privilege_object.schema_id {
3343 let database_spec: ResolvedDatabaseSpecifier = privilege_object.database_id.into();
3344 let schema_spec: SchemaSpecifier = schema_id.into();
3345
3346 self.catalog().ensure_not_reserved_object(
3347 &(database_spec, schema_spec).into(),
3348 session.conn_id(),
3349 )?;
3350 }
3351 for privilege_acl_item in &privilege_acl_items {
3352 self.catalog()
3353 .ensure_not_system_role(&privilege_acl_item.grantee)?;
3354 self.catalog()
3355 .ensure_not_predefined_role(&privilege_acl_item.grantee)?;
3356 ops.push(catalog::Op::UpdateDefaultPrivilege {
3357 privilege_object: privilege_object.clone(),
3358 privilege_acl_item: privilege_acl_item.clone(),
3359 variant,
3360 })
3361 }
3362 }
3363
3364 self.catalog_transact(Some(session), ops).await?;
3365 Ok(ExecuteResponse::AlteredDefaultPrivileges)
3366 }
3367
3368 #[instrument]
3369 pub(super) async fn sequence_grant_role(
3370 &mut self,
3371 session: &Session,
3372 plan::GrantRolePlan {
3373 role_ids,
3374 member_ids,
3375 grantor_id,
3376 }: plan::GrantRolePlan,
3377 ) -> Result<ExecuteResponse, AdapterError> {
3378 let catalog = self.catalog();
3379 let mut ops = Vec::with_capacity(role_ids.len() * member_ids.len());
3380 for role_id in role_ids {
3381 for member_id in &member_ids {
3382 let member_membership: BTreeSet<_> =
3383 catalog.get_role(member_id).membership().keys().collect();
3384 if member_membership.contains(&role_id) {
3385 let role_name = catalog.get_role(&role_id).name().to_string();
3386 let member_name = catalog.get_role(member_id).name().to_string();
3387 catalog.ensure_not_reserved_role(member_id)?;
3389 catalog.ensure_grantable_role(&role_id)?;
3390 session.add_notice(AdapterNotice::RoleMembershipAlreadyExists {
3391 role_name,
3392 member_name,
3393 });
3394 } else {
3395 ops.push(catalog::Op::GrantRole {
3396 role_id,
3397 member_id: *member_id,
3398 grantor_id,
3399 });
3400 }
3401 }
3402 }
3403
3404 if ops.is_empty() {
3405 return Ok(ExecuteResponse::GrantedRole);
3406 }
3407
3408 self.catalog_transact(Some(session), ops)
3409 .await
3410 .map(|_| ExecuteResponse::GrantedRole)
3411 }
3412
3413 #[instrument]
3414 pub(super) async fn sequence_revoke_role(
3415 &mut self,
3416 session: &Session,
3417 plan::RevokeRolePlan {
3418 role_ids,
3419 member_ids,
3420 grantor_id,
3421 }: plan::RevokeRolePlan,
3422 ) -> Result<ExecuteResponse, AdapterError> {
3423 let catalog = self.catalog();
3424 let mut ops = Vec::with_capacity(role_ids.len() * member_ids.len());
3425 for role_id in role_ids {
3426 for member_id in &member_ids {
3427 let member_membership: BTreeSet<_> =
3428 catalog.get_role(member_id).membership().keys().collect();
3429 if !member_membership.contains(&role_id) {
3430 let role_name = catalog.get_role(&role_id).name().to_string();
3431 let member_name = catalog.get_role(member_id).name().to_string();
3432 catalog.ensure_not_reserved_role(member_id)?;
3434 catalog.ensure_grantable_role(&role_id)?;
3435 session.add_notice(AdapterNotice::RoleMembershipDoesNotExists {
3436 role_name,
3437 member_name,
3438 });
3439 } else {
3440 ops.push(catalog::Op::RevokeRole {
3441 role_id,
3442 member_id: *member_id,
3443 grantor_id,
3444 });
3445 }
3446 }
3447 }
3448
3449 if ops.is_empty() {
3450 return Ok(ExecuteResponse::RevokedRole);
3451 }
3452
3453 self.catalog_transact(Some(session), ops)
3454 .await
3455 .map(|_| ExecuteResponse::RevokedRole)
3456 }
3457
3458 #[instrument]
3459 pub(super) async fn sequence_alter_owner(
3460 &mut self,
3461 session: &Session,
3462 plan::AlterOwnerPlan {
3463 id,
3464 object_type,
3465 new_owner,
3466 }: plan::AlterOwnerPlan,
3467 ) -> Result<ExecuteResponse, AdapterError> {
3468 let mut ops = vec![catalog::Op::UpdateOwner {
3469 id: id.clone(),
3470 new_owner,
3471 }];
3472
3473 match &id {
3474 ObjectId::Item(global_id) => {
3475 let entry = self.catalog().get_entry(global_id);
3476
3477 if entry.is_index() {
3479 let name = self
3480 .catalog()
3481 .resolve_full_name(entry.name(), Some(session.conn_id()))
3482 .to_string();
3483 session.add_notice(AdapterNotice::AlterIndexOwner { name });
3484 return Ok(ExecuteResponse::AlteredObject(object_type));
3485 }
3486
3487 let dependent_index_ops = entry
3489 .used_by()
3490 .into_iter()
3491 .filter(|id| self.catalog().get_entry(id).is_index())
3492 .map(|id| catalog::Op::UpdateOwner {
3493 id: ObjectId::Item(*id),
3494 new_owner,
3495 });
3496 ops.extend(dependent_index_ops);
3497
3498 let dependent_subsources =
3500 entry
3501 .progress_id()
3502 .into_iter()
3503 .map(|item_id| catalog::Op::UpdateOwner {
3504 id: ObjectId::Item(item_id),
3505 new_owner,
3506 });
3507 ops.extend(dependent_subsources);
3508 }
3509 ObjectId::Cluster(cluster_id) => {
3510 let cluster = self.catalog().get_cluster(*cluster_id);
3511 let managed_cluster_replica_ops =
3513 cluster.replicas().map(|replica| catalog::Op::UpdateOwner {
3514 id: ObjectId::ClusterReplica((cluster.id(), replica.replica_id())),
3515 new_owner,
3516 });
3517 ops.extend(managed_cluster_replica_ops);
3518 }
3519 _ => {}
3520 }
3521
3522 self.catalog_transact(Some(session), ops)
3523 .await
3524 .map(|_| ExecuteResponse::AlteredObject(object_type))
3525 }
3526
3527 #[instrument]
3528 pub(super) async fn sequence_reassign_owned(
3529 &mut self,
3530 session: &Session,
3531 plan::ReassignOwnedPlan {
3532 old_roles,
3533 new_role,
3534 reassign_ids,
3535 }: plan::ReassignOwnedPlan,
3536 ) -> Result<ExecuteResponse, AdapterError> {
3537 for role_id in old_roles.iter().chain(iter::once(&new_role)) {
3538 self.catalog().ensure_not_reserved_role(role_id)?;
3539 }
3540
3541 let ops = reassign_ids
3542 .into_iter()
3543 .map(|id| catalog::Op::UpdateOwner {
3544 id,
3545 new_owner: new_role,
3546 })
3547 .collect();
3548
3549 self.catalog_transact(Some(session), ops)
3550 .await
3551 .map(|_| ExecuteResponse::ReassignOwned)
3552 }
3553
3554 #[instrument]
3555 pub(crate) async fn handle_deferred_statement(&mut self) {
3556 let Some(deferred) = self.serialized_ddl.pop_front() else {
3560 return;
3561 };
3562 match deferred {
3563 DeferredDdl::Statement { ctx, stmt, params } => {
3564 self.handle_execute_inner(stmt, params, ctx).await;
3565 }
3566 DeferredDdl::Commit(pending) => {
3567 Box::pin(self.sequence_ddl_commit(pending)).await;
3568 }
3569 }
3570 }
3571
3572 #[instrument]
3573 #[allow(clippy::unused_async)]
3575 pub(super) async fn sequence_alter_table(
3576 &mut self,
3577 ctx: &mut ExecuteContext,
3578 plan: plan::AlterTablePlan,
3579 ) -> Result<ExecuteResponse, AdapterError> {
3580 let plan::AlterTablePlan {
3581 relation_id,
3582 column_name,
3583 column_type,
3584 raw_sql_type,
3585 } = plan;
3586
3587 let (_, new_global_id) = self.allocate_user_id().await?;
3589 let ops = vec![catalog::Op::AlterAddColumn {
3590 id: relation_id,
3591 new_global_id,
3592 name: column_name,
3593 typ: column_type,
3594 sql: raw_sql_type,
3595 }];
3596
3597 self.catalog_transact_with_context(None, Some(ctx), ops)
3598 .await?;
3599
3600 Ok(ExecuteResponse::AlteredObject(ObjectType::Table))
3601 }
3602
3603 #[instrument]
3605 pub(super) async fn sequence_alter_materialized_view_apply_replacement_prepare(
3606 &mut self,
3607 ctx: ExecuteContext,
3608 plan: AlterMaterializedViewApplyReplacementPlan,
3609 ) {
3610 let AlterMaterializedViewApplyReplacementPlan { id, replacement_id } = plan.clone();
3620
3621 let plan_validity = PlanValidity::new(
3622 self.catalog(),
3623 BTreeSet::from_iter([id, replacement_id]),
3624 None,
3625 None,
3626 ctx.session().role_metadata().clone(),
3627 );
3628
3629 let target = self.catalog.get_entry(&id);
3630 let target_gid = target.latest_global_id();
3631
3632 let replacement = self.catalog.get_entry(&replacement_id);
3633 let replacement_gid = replacement.latest_global_id();
3634
3635 let target_upper = self
3636 .controller
3637 .storage_collections
3638 .collection_frontiers(target_gid)
3639 .expect("target MV exists")
3640 .write_frontier;
3641 let replacement_upper = self
3642 .controller
3643 .compute
3644 .collection_frontiers(replacement_gid, replacement.cluster_id())
3645 .expect("replacement MV exists")
3646 .write_frontier;
3647
3648 info!(
3649 %id, %replacement_id, ?target_upper, ?replacement_upper,
3650 "preparing materialized view replacement application",
3651 );
3652
3653 let replacement_upper_ts = match replacement_upper.into_option() {
3662 Some(ts) if !self.materialized_view_sealed(target) => ts,
3663 _ => {
3664 ctx.retire(Err(AdapterError::ReplaceMaterializedViewSealed {
3665 name: target.name().item.clone(),
3666 }));
3667 return;
3668 }
3669 };
3670
3671 let replacement_upper_ts = replacement_upper_ts.step_back().unwrap_or(Timestamp::MIN);
3675
3676 self.install_storage_watch_set(
3680 ctx.session().conn_id().clone(),
3681 BTreeSet::from_iter([target_gid]),
3682 replacement_upper_ts,
3683 WatchSetResponse::AlterMaterializedViewReady(AlterMaterializedViewReadyContext {
3684 ctx: Some(ctx),
3685 otel_ctx: OpenTelemetryContext::obtain(),
3686 plan,
3687 plan_validity,
3688 }),
3689 )
3690 .expect("target collection exists");
3691 }
3692
3693 #[instrument]
3695 pub async fn sequence_alter_materialized_view_apply_replacement_finish(
3696 &mut self,
3697 mut ctx: AlterMaterializedViewReadyContext,
3698 ) {
3699 ctx.otel_ctx.attach_as_parent();
3700
3701 let AlterMaterializedViewApplyReplacementPlan { id, replacement_id } = ctx.plan;
3702
3703 if let Err(err) = ctx.plan_validity.check(self.catalog()) {
3708 ctx.retire(Err(err));
3709 return;
3710 }
3711
3712 let target = self.catalog().get_entry(&id);
3718 if self.materialized_view_sealed(target) {
3719 let name = target.name().item.clone();
3720 ctx.retire(Err(AdapterError::ReplaceMaterializedViewSealed { name }));
3721 return;
3722 }
3723
3724 info!(
3725 %id, %replacement_id,
3726 "finishing materialized view replacement application",
3727 );
3728
3729 let invalidate_ids = self.catalog().get_entry(&id).global_ids().collect();
3735 self.catalog()
3736 .update_expression_cache(Vec::new(), Vec::new(), invalidate_ids)
3737 .await;
3738
3739 let ops = vec![catalog::Op::AlterMaterializedViewApplyReplacement { id, replacement_id }];
3740 match self
3741 .catalog_transact(Some(ctx.ctx().session_mut()), ops)
3742 .await
3743 {
3744 Ok(()) => ctx.retire(Ok(ExecuteResponse::AlteredObject(
3745 ObjectType::MaterializedView,
3746 ))),
3747 Err(err) => ctx.retire(Err(err)),
3748 }
3749 }
3750
3751 fn materialized_view_sealed(&self, entry: &CatalogEntry) -> bool {
3756 let gid = entry.latest_global_id();
3757 let compute_upper = self
3758 .controller
3759 .compute
3760 .collection_frontiers(gid, entry.cluster_id())
3761 .expect("materialized view exists")
3762 .write_frontier;
3763 let storage_upper = self
3764 .controller
3765 .storage_collections
3766 .collection_frontiers(gid)
3767 .expect("materialized view exists")
3768 .write_frontier;
3769 compute_upper.is_empty() || storage_upper.is_empty()
3770 }
3771}
3772
3773impl Coordinator {
3774 pub(crate) fn emit_raw_optimizer_notices_to_user(
3782 &self,
3783 ctx: &ExecuteContext,
3784 notices: &[RawOptimizerNotice],
3785 ) {
3786 emit_optimizer_notices(&*self.catalog, ctx.session(), notices);
3787 }
3788
3789 fn render_create_item_notices(
3799 &self,
3800 name: &QualifiedItemName,
3801 global_id: GlobalId,
3802 source_desc: &RelationDesc,
3803 raw_df_meta: &DataflowMetainfo,
3804 ) -> DataflowMetainfo<Arc<OptimizerNotice>> {
3805 let notice_ids = std::iter::repeat_with(|| self.allocate_transient_id())
3806 .map(|(_item_id, notice_id)| notice_id)
3807 .take(raw_df_meta.optimizer_notices.len())
3808 .collect::<Vec<_>>();
3809
3810 let system_catalog = self.catalog().for_system_session();
3811 let full_name = self.catalog().resolve_full_name(name, None);
3812 let transient_items = btreemap! {
3813 global_id => TransientItem::new(
3814 Some(full_name.into_parts()),
3815 Some(source_desc.iter_names().map(|c| c.to_string()).collect()),
3816 )
3817 };
3818 let humanizer = ExprHumanizerExt::new(transient_items, &system_catalog);
3819 CatalogState::render_notices_core(
3820 &humanizer,
3821 (self.catalog().config().now)(),
3822 raw_df_meta,
3823 notice_ids,
3824 Some(global_id),
3825 )
3826 }
3827
3828 async fn ship_new_dataflow(
3835 &mut self,
3836 id_bundle: &CollectionIdBundle,
3837 mut df_desc: LirDataflowDescription,
3838 instance: ComputeInstanceId,
3839 notice_builtin_updates_fut: Option<BuiltinTableAppendNotify>,
3840 ) {
3841 let read_holds = self.acquire_read_holds(id_bundle);
3842 let since = read_holds.least_valid_read();
3843 df_desc.set_as_of(since);
3844
3845 self.ship_dataflow_and_notice_builtin_table_updates(
3846 df_desc,
3847 instance,
3848 notice_builtin_updates_fut,
3849 None,
3850 )
3851 .await;
3852
3853 drop(read_holds);
3854 }
3855
3856 fn persist_dataflow_metainfo(
3866 &mut self,
3867 df_meta: DataflowMetainfo<Arc<OptimizerNotice>>,
3868 export_id: GlobalId,
3869 ) -> Option<BuiltinTableAppendNotify> {
3870 if self.catalog().state().system_config().enable_mz_notices()
3873 && !df_meta.optimizer_notices.is_empty()
3874 {
3875 let mut builtin_table_updates = Vec::with_capacity(df_meta.optimizer_notices.len());
3876 self.catalog().state().pack_optimizer_notices(
3877 &mut builtin_table_updates,
3878 df_meta.optimizer_notices.iter(),
3879 Diff::ONE,
3880 );
3881
3882 self.catalog_mut().set_dataflow_metainfo(export_id, df_meta);
3884
3885 Some(self.builtin_table_update().execute(builtin_table_updates))
3886 } else {
3887 self.catalog_mut().set_dataflow_metainfo(export_id, df_meta);
3889
3890 None
3891 }
3892 }
3893}