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 itertools::Itertools;
20use maplit::btreemap;
21use mz_adapter_types::compaction::CompactionWindow;
22use mz_adapter_types::connection::ConnectionId;
23use mz_adapter_types::dyncfgs::{ENABLE_EXPRESSION_CACHE, ENABLE_PASSWORD_AUTH};
24use mz_catalog::memory::error::ErrorKind;
25use mz_catalog::memory::objects::{
26 CatalogItem, Connection, DataSourceDesc, Sink, Source, Table, TableDataSource, Type,
27};
28use mz_compute_types::ComputeInstanceId;
29use mz_compute_types::dataflows::LirDataflowDescription;
30use mz_expr::{MapFilterProject, ResultSpec};
31use mz_ore::cast::CastFrom;
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::{assert_none, instrument, soft_panic_or_log};
37use mz_repr::adt::jsonb::Jsonb;
38use mz_repr::adt::mz_acl_item::{MzAclItem, PrivilegeMap};
39use mz_repr::explain::json::json_string;
40use mz_repr::explain::{ExprHumanizer, ExprHumanizerExt, TransientItem};
41use mz_repr::role_id::RoleId;
42use mz_repr::{
43 CatalogItemId, Datum, Diff, GlobalId, RelationDesc, RelationVersion, RelationVersionSelector,
44 Row, Timestamp,
45};
46use mz_secrets::SecretsReader;
47use mz_sql::ast::{
48 AlterSourceAddSubsourceOption, CreateSinkOption, CreateSinkOptionName, CreateSourceOptionName,
49 CreateSubsourceOption, CreateSubsourceOptionName, SqlServerConfigOption,
50 SqlServerConfigOptionName,
51};
52use mz_sql::ast::{CreateSubsourceStatement, MySqlConfigOptionName, UnresolvedItemName};
53use mz_sql::catalog::{
54 CatalogCluster, CatalogClusterReplica, CatalogDatabase, CatalogError,
55 CatalogItem as SqlCatalogItem, CatalogRole, CatalogSchema, CatalogTypeDetails,
56 ErrorMessageObjectDescription, ObjectType, RoleAttributesRaw, RoleVars, SessionCatalog,
57};
58use mz_sql::names::{
59 Aug, ObjectId, QualifiedItemName, ResolvedDatabaseSpecifier, ResolvedIds, ResolvedItemName,
60 SchemaSpecifier, SystemObjectId,
61};
62use mz_sql::plan::{
63 AlterMaterializedViewApplyReplacementPlan, ConnectionDetails, NetworkPolicyRule, PlanError,
64 StatementContext,
65};
66use mz_sql::pure::{PurifiedSourceExport, generate_subsource_statements};
67use mz_storage_types::sinks::StorageSinkDesc;
68use mz_timestamp_oracle::TimestampOracle;
69use mz_sql::plan::{
71 AlterConnectionAction, AlterConnectionPlan, CreateSourcePlanBundle, ExplainSinkSchemaPlan,
72 Explainee, ExplaineeStatement, Params, Plan, PlannedAlterRoleOption, PlannedRoleVariable,
73 SideEffectingFunc, UpdatePrivilege, VariableValue,
74};
75use mz_sql::session::metadata::SessionMetadata;
76use mz_sql::session::user::UserKind;
77use mz_sql::session::vars::{
78 self, IsolationLevel, MAX_CONCURRENT_OCC_WRITES, NETWORK_POLICY, OwnedVarInput, SCHEMA_ALIAS,
79 TRANSACTION_ISOLATION_VAR_NAME, Var, VarError, VarInput,
80};
81use mz_sql::{plan, rbac};
82use mz_sql_parser::ast::display::AstDisplay;
83use mz_sql_parser::ast::{
84 ConnectionOption, ConnectionOptionName, CreateSourceConnection, DeferredItemName,
85 MySqlConfigOption, PgConfigOption, PgConfigOptionName, Statement, TransactionMode,
86 WithOptionValue,
87};
88use mz_ssh_util::keys::SshKeyPairSet;
89use mz_storage_client::controller::ExportDescription;
90use mz_storage_types::AlterCompatible;
91use mz_storage_types::connections::AwsPrivatelinkConnection;
92use mz_storage_types::connections::inline::IntoInlineConnection;
93use mz_storage_types::controller::StorageError;
94use mz_storage_types::sources::SourceConnection;
95use mz_transform::dataflow::DataflowMetainfo;
96use mz_transform::notice::{OptimizerNotice, RawOptimizerNotice};
97use smallvec::SmallVec;
98use timely::progress::Antichain;
99use tokio::sync::watch;
100use tracing::{Instrument, Span, info, warn};
101
102use crate::catalog::{
103 self, Catalog, CatalogState, ConnCatalog, DropObjectInfo, UpdatePrivilegeVariant,
104};
105use crate::command::ExecuteResponse;
106use crate::coord::appends::{BuiltinTableAppendNotify, PendingWriteTxn, UserWriteResponder};
107use crate::coord::sequencer::emit_optimizer_notices;
108use crate::coord::{
109 AlterConnectionValidationReady, AlterMaterializedViewReadyContext, AlterSinkReadyContext,
110 Coordinator, CreateConnectionValidationReady, DeferredPlanStatement, ExecuteContext, Message,
111 NetworkPolicyError, PendingReadTxn, PendingTxn, PendingTxnResponse, PlanValidity, StageResult,
112 Staged, StagedContext, TargetCluster, WatchSetResponse, validate_ip_with_policy_rules,
113};
114use crate::error::AdapterError;
115use crate::notice::{AdapterNotice, DroppedInUseIndex};
116use crate::session::{
117 EndTransactionAction, RequireLinearization, Session, TransactionOps, TransactionStatus, WriteOp,
118};
119use crate::util::{ResultExt, viewable_variables};
120use crate::{CollectionIdBundle, ReadHolds};
121
122type RtrTimestampFuture = BoxFuture<'static, Result<Timestamp, StorageError>>;
124
125mod cluster;
126mod copy_from;
127mod create_index;
128mod create_materialized_view;
129mod create_metric_sink;
130mod create_view;
131mod explain_timestamp;
132mod peek;
133mod secret;
134mod subscribe;
135
136macro_rules! return_if_err {
139 ($expr:expr, $ctx:expr) => {
140 match $expr {
141 Ok(v) => v,
142 Err(e) => return $ctx.retire(Err(e.into())),
143 }
144 };
145}
146
147pub(super) use return_if_err;
148
149fn spawn_linearized_read_ts<S>(
150 oracle: Option<Arc<dyn TimestampOracle<Timestamp> + Send + Sync>>,
151 name: &'static str,
152 build_stage: impl FnOnce(Option<Timestamp>) -> S + Send + 'static,
153) -> StageResult<Box<S>>
154where
155 S: Send + 'static,
156{
157 match oracle {
158 Some(oracle) => {
159 let span = Span::current();
160 StageResult::Handle(mz_ore::task::spawn(
161 move || name,
162 async move {
163 let oracle_read_ts = oracle.read_ts().await;
164 Ok(Box::new(build_stage(Some(oracle_read_ts))))
165 }
166 .instrument(span),
167 ))
168 }
169 None => StageResult::Immediate(Box::new(build_stage(None))),
170 }
171}
172
173fn check_connection_details(details: &ConnectionDetails) -> Result<(), PlanError> {
176 match details {
177 ConnectionDetails::AwsPrivatelink(privatelink) => {
178 AwsPrivatelinkConnection::check_service_name(&privatelink.service_name)
179 .map_err(PlanError::InvalidPrivatelinkServiceName)
180 }
181 _ => Ok(()),
182 }
183}
184
185struct DropOps {
186 ops: Vec<catalog::Op>,
187 dropped_active_db: bool,
188 dropped_active_cluster: bool,
189 dropped_in_use_indexes: Vec<DroppedInUseIndex>,
190}
191
192struct CreateSourceInner {
194 ops: Vec<catalog::Op>,
195 sources: Vec<(CatalogItemId, QualifiedItemName, Source)>,
196 if_not_exists_ids: BTreeMap<CatalogItemId, QualifiedItemName>,
197}
198
199impl Coordinator {
200 pub(crate) async fn sequence_staged<S>(
207 &mut self,
208 mut ctx: S::Ctx,
209 parent_span: Span,
210 mut stage: S,
211 ) where
212 S: Staged + 'static,
213 S::Ctx: Send + 'static,
214 {
215 return_if_err!(stage.validity().check(self.catalog()), ctx);
216 loop {
217 let mut cancel_enabled = stage.cancel_enabled();
218 if let Some(session) = ctx.session() {
219 if cancel_enabled {
220 if let Some((_prev_tx, prev_rx)) = self
223 .connection_cancel_watches
224 .insert(session.conn_id().clone(), watch::channel(false))
225 {
226 let was_canceled = *prev_rx.borrow();
227 if was_canceled {
228 ctx.retire(Err(AdapterError::Canceled));
229 return;
230 }
231 }
232 } else {
233 self.connection_cancel_watches.remove(session.conn_id());
236 }
237 } else {
238 cancel_enabled = false
239 };
240 let next = stage
241 .stage(self, &mut ctx)
242 .instrument(parent_span.clone())
243 .await;
244 let res = return_if_err!(next, ctx);
245 stage = match res {
246 StageResult::Handle(handle) => {
247 let internal_cmd_tx = self.internal_cmd_tx.clone();
248 self.handle_spawn(ctx, handle, cancel_enabled, move |ctx, next| {
249 let _ = internal_cmd_tx.send(next.message(ctx, parent_span));
250 });
251 return;
252 }
253 StageResult::HandleRetire(handle) => {
254 self.handle_spawn(ctx, handle, cancel_enabled, move |ctx, resp| {
255 ctx.retire(Ok(resp));
256 });
257 return;
258 }
259 StageResult::Response(resp) => {
260 ctx.retire(Ok(resp));
261 return;
262 }
263 StageResult::Immediate(stage) => *stage,
264 }
265 }
266 }
267
268 fn handle_spawn<C, T, F>(
271 &self,
272 ctx: C,
273 handle: JoinHandle<Result<T, AdapterError>>,
274 cancel_enabled: bool,
275 f: F,
276 ) where
277 C: StagedContext + Send + 'static,
278 T: Send + 'static,
279 F: FnOnce(C, T) + Send + 'static,
280 {
281 let rx: BoxFuture<()> = if let Some((_tx, rx)) = ctx
282 .session()
283 .and_then(|session| self.connection_cancel_watches.get(session.conn_id()))
284 {
285 let mut rx = rx.clone();
286 Box::pin(async move {
287 let _ = rx.wait_for(|v| *v).await;
289 ()
290 })
291 } else {
292 Box::pin(future::pending())
293 };
294 spawn(|| "sequence_staged", async move {
295 tokio::select! {
296 res = handle => {
297 let next = return_if_err!(res, ctx);
298 f(ctx, next);
299 }
300 _ = rx, if cancel_enabled => {
301 ctx.retire(Err(AdapterError::Canceled));
302 }
303 }
304 });
305 }
306
307 async fn create_source_inner(
308 &self,
309 session: &Session,
310 plans: Vec<plan::CreateSourcePlanBundle>,
311 ) -> Result<CreateSourceInner, AdapterError> {
312 let mut ops = vec![];
313 let mut sources = vec![];
314
315 let if_not_exists_ids = plans
316 .iter()
317 .filter_map(
318 |plan::CreateSourcePlanBundle {
319 item_id,
320 global_id: _,
321 plan,
322 resolved_ids: _,
323 available_source_references: _,
324 }| {
325 if plan.if_not_exists {
326 Some((*item_id, plan.name.clone()))
327 } else {
328 None
329 }
330 },
331 )
332 .collect::<BTreeMap<_, _>>();
333
334 for plan::CreateSourcePlanBundle {
335 item_id,
336 global_id,
337 mut plan,
338 resolved_ids,
339 available_source_references,
340 } in plans
341 {
342 let name = plan.name.clone();
343
344 if let mz_sql::plan::DataSourceDesc::Webhook {
346 validate_using: Some(validate),
347 ..
348 } = &mut plan.source.data_source
349 {
350 if let Err(reason) = validate.reduce_expression().await {
351 self.metrics
352 .webhook_validation_reduce_failures
353 .with_label_values(&[reason])
354 .inc();
355 return Err(AdapterError::Internal(format!(
356 "failed to reduce check expression, {reason}"
357 )));
358 }
359 }
360
361 let mut reference_ops = vec![];
364 if let Some(references) = &available_source_references {
365 reference_ops.push(catalog::Op::UpdateSourceReferences {
366 source_id: item_id,
367 references: references.clone().into(),
368 });
369 }
370
371 let source = Source::new(plan, global_id, resolved_ids, None, false);
372 ops.push(catalog::Op::CreateItem {
373 id: item_id,
374 name: name.clone(),
375 item: CatalogItem::Source(source.clone()),
376 owner_id: *session.current_role_id(),
377 });
378 sources.push((item_id, name, source));
379 ops.extend(reference_ops);
381 }
382
383 Ok(CreateSourceInner {
384 ops,
385 sources,
386 if_not_exists_ids,
387 })
388 }
389
390 pub(crate) fn plan_subsource(
398 &self,
399 session: &Session,
400 params: &mz_sql::plan::Params,
401 subsource_stmt: CreateSubsourceStatement<mz_sql::names::Aug>,
402 item_id: CatalogItemId,
403 global_id: GlobalId,
404 ) -> Result<CreateSourcePlanBundle, AdapterError> {
405 let catalog = self.catalog().for_session(session);
406 let resolved_ids = mz_sql::names::visit_dependencies(&catalog, &subsource_stmt);
407
408 let (plan, _sql_impl_ids) = self.plan_statement(
409 session,
410 Statement::CreateSubsource(subsource_stmt),
411 params,
412 &resolved_ids,
413 )?;
414 let plan = match plan {
415 Plan::CreateSource(plan) => plan,
416 _ => unreachable!(),
417 };
418 Ok(CreateSourcePlanBundle {
419 item_id,
420 global_id,
421 plan,
422 resolved_ids,
423 available_source_references: None,
424 })
425 }
426
427 pub(crate) async fn plan_purified_alter_source_add_subsource(
429 &mut self,
430 session: &Session,
431 params: Params,
432 source_name: ResolvedItemName,
433 options: Vec<AlterSourceAddSubsourceOption<Aug>>,
434 subsources: BTreeMap<UnresolvedItemName, PurifiedSourceExport>,
435 ) -> Result<(Plan, ResolvedIds), AdapterError> {
436 let mut subsource_plans = Vec::with_capacity(subsources.len());
437
438 let conn_catalog = self.catalog().for_system_session();
440 let pcx = plan::PlanContext::zero();
441 let scx = StatementContext::new(Some(&pcx), &conn_catalog);
442
443 let entry = self.catalog().get_entry(source_name.item_id());
444 let source = entry.source().ok_or_else(|| {
445 AdapterError::internal(
446 "plan alter source",
447 format!("expected Source found {entry:?}"),
448 )
449 })?;
450
451 let item_id = entry.id();
452 let ingestion_id = source.global_id();
453 let subsource_stmts = generate_subsource_statements(&scx, source_name, subsources)?;
454
455 let ids = self
456 .allocate_user_ids(u64::cast_from(subsource_stmts.len()))
457 .await?;
458 for (subsource_stmt, (item_id, global_id)) in subsource_stmts.into_iter().zip_eq(ids) {
459 let s = self.plan_subsource(session, ¶ms, subsource_stmt, item_id, global_id)?;
460 subsource_plans.push(s);
461 }
462
463 let action = mz_sql::plan::AlterSourceAction::AddSubsourceExports {
464 subsources: subsource_plans,
465 options,
466 };
467
468 Ok((
469 Plan::AlterSource(mz_sql::plan::AlterSourcePlan {
470 item_id,
471 ingestion_id,
472 action,
473 }),
474 ResolvedIds::empty(),
475 ))
476 }
477
478 pub(crate) fn plan_purified_alter_source_refresh_references(
480 &self,
481 _session: &Session,
482 _params: Params,
483 source_name: ResolvedItemName,
484 available_source_references: plan::SourceReferences,
485 ) -> Result<(Plan, ResolvedIds), AdapterError> {
486 let entry = self.catalog().get_entry(source_name.item_id());
487 let source = entry.source().ok_or_else(|| {
488 AdapterError::internal(
489 "plan alter source",
490 format!("expected Source found {entry:?}"),
491 )
492 })?;
493 let action = mz_sql::plan::AlterSourceAction::RefreshReferences {
494 references: available_source_references,
495 };
496
497 Ok((
498 Plan::AlterSource(mz_sql::plan::AlterSourcePlan {
499 item_id: entry.id(),
500 ingestion_id: source.global_id(),
501 action,
502 }),
503 ResolvedIds::empty(),
504 ))
505 }
506
507 pub(crate) async fn plan_purified_create_source(
511 &mut self,
512 ctx: &ExecuteContext,
513 params: Params,
514 progress_stmt: Option<CreateSubsourceStatement<Aug>>,
515 mut source_stmt: mz_sql::ast::CreateSourceStatement<Aug>,
516 subsources: BTreeMap<UnresolvedItemName, PurifiedSourceExport>,
517 available_source_references: plan::SourceReferences,
518 ) -> Result<(Plan, ResolvedIds), AdapterError> {
519 let mut create_source_plans = Vec::with_capacity(subsources.len() + 2);
520
521 if let Some(progress_stmt) = progress_stmt {
523 assert_none!(progress_stmt.of_source);
528 let (item_id, global_id) = self.allocate_user_id().await?;
529 let progress_plan =
530 self.plan_subsource(ctx.session(), ¶ms, progress_stmt, item_id, global_id)?;
531 let progress_full_name = self
532 .catalog()
533 .resolve_full_name(&progress_plan.plan.name, None);
534 let progress_subsource = ResolvedItemName::Item {
535 id: progress_plan.item_id,
536 qualifiers: progress_plan.plan.name.qualifiers.clone(),
537 full_name: progress_full_name,
538 print_id: true,
539 version: RelationVersionSelector::Latest,
540 };
541
542 create_source_plans.push(progress_plan);
543
544 source_stmt.progress_subsource = Some(DeferredItemName::Named(progress_subsource));
545 }
546
547 let catalog = self.catalog().for_session(ctx.session());
548 let resolved_ids = mz_sql::names::visit_dependencies(&catalog, &source_stmt);
549
550 let propagated_with_options: Vec<_> = source_stmt
551 .with_options
552 .iter()
553 .filter_map(|opt| match opt.name {
554 CreateSourceOptionName::TimestampInterval => None,
555 CreateSourceOptionName::RetainHistory => Some(CreateSubsourceOption {
556 name: CreateSubsourceOptionName::RetainHistory,
557 value: opt.value.clone(),
558 }),
559 })
560 .collect();
561
562 let source_plan = match self.plan_statement(
564 ctx.session(),
565 Statement::CreateSource(source_stmt),
566 ¶ms,
567 &resolved_ids,
568 )? {
569 (Plan::CreateSource(plan), _sql_impl_ids) => plan,
570 (p, _) => unreachable!("s must be CreateSourcePlan but got {:?}", p),
571 };
572
573 let (item_id, global_id) = self.allocate_user_id().await?;
574
575 let source_full_name = self.catalog().resolve_full_name(&source_plan.name, None);
576 let of_source = ResolvedItemName::Item {
577 id: item_id,
578 qualifiers: source_plan.name.qualifiers.clone(),
579 full_name: source_full_name,
580 print_id: true,
581 version: RelationVersionSelector::Latest,
582 };
583
584 let conn_catalog = self.catalog().for_system_session();
586 let pcx = plan::PlanContext::zero();
587 let scx = StatementContext::new(Some(&pcx), &conn_catalog);
588
589 let mut subsource_stmts = generate_subsource_statements(&scx, of_source, subsources)?;
590
591 for subsource_stmt in subsource_stmts.iter_mut() {
592 subsource_stmt
593 .with_options
594 .extend(propagated_with_options.iter().cloned())
595 }
596
597 create_source_plans.push(CreateSourcePlanBundle {
598 item_id,
599 global_id,
600 plan: source_plan,
601 resolved_ids: resolved_ids.clone(),
602 available_source_references: Some(available_source_references),
603 });
604
605 let ids = self
607 .allocate_user_ids(u64::cast_from(subsource_stmts.len()))
608 .await?;
609 for (stmt, (item_id, global_id)) in subsource_stmts.into_iter().zip_eq(ids) {
610 let plan = self.plan_subsource(ctx.session(), ¶ms, stmt, item_id, global_id)?;
611 create_source_plans.push(plan);
612 }
613
614 Ok((
615 Plan::CreateSources(create_source_plans),
616 ResolvedIds::empty(),
617 ))
618 }
619
620 #[instrument]
621 pub(super) async fn sequence_create_source(
622 &mut self,
623 ctx: &mut ExecuteContext,
624 plans: Vec<plan::CreateSourcePlanBundle>,
625 ) -> Result<ExecuteResponse, AdapterError> {
626 let CreateSourceInner {
627 ops,
628 sources,
629 if_not_exists_ids,
630 } = self.create_source_inner(ctx.session(), plans).await?;
631
632 let transact_result = self
633 .catalog_transact_with_ddl_transaction(ctx, ops, |_, _| Box::pin(async {}))
634 .await;
635
636 for (item_id, _name, source) in &sources {
638 if matches!(source.data_source, DataSourceDesc::Webhook { .. }) {
639 if let Some(url) = self.catalog().state().try_get_webhook_url(item_id) {
640 ctx.session()
641 .add_notice(AdapterNotice::WebhookSourceCreated { url });
642 }
643 }
644 }
645
646 match transact_result {
647 Ok(()) => {
648 for (_item_id, name, source) in &sources {
654 let cluster_id = match &source.data_source {
655 DataSourceDesc::Ingestion { desc, cluster_id }
656 | DataSourceDesc::OldSyntaxIngestion {
657 desc, cluster_id, ..
658 } if desc.connection.prefers_single_replica() => cluster_id,
659 _ => continue,
660 };
661 let cluster = self.catalog().get_cluster(*cluster_id);
662 self.notify_single_replica_sources(ctx.session(), cluster, Some(name));
663 }
664 Ok(ExecuteResponse::CreatedSource)
665 }
666 Err(AdapterError::Catalog(mz_catalog::memory::error::Error {
667 kind: ErrorKind::Sql(CatalogError::ItemAlreadyExists(id, _)),
668 })) if if_not_exists_ids.contains_key(&id) => {
669 ctx.session()
670 .add_notice(AdapterNotice::ObjectAlreadyExists {
671 name: if_not_exists_ids[&id].item.clone(),
672 ty: "source",
673 });
674 Ok(ExecuteResponse::CreatedSource)
675 }
676 Err(err) => Err(err),
677 }
678 }
679
680 async fn check_connection_secret_content_guards(
684 &self,
685 details: &ConnectionDetails,
686 ) -> Result<(), AdapterError> {
687 for (secret_id, guard) in details.secret_content_guards() {
688 let contents = self.caching_secrets_reader.read_string(secret_id).await?;
689 guard(&contents)?;
690 }
691 Ok(())
692 }
693
694 pub(super) fn check_secret_content_guards_of_dependents(
699 &self,
700 secret_id: CatalogItemId,
701 contents: &str,
702 ) -> Result<(), AdapterError> {
703 for dependent_id in self.catalog().get_entry(&secret_id).used_by() {
704 if let CatalogItem::Connection(conn) = self.catalog().get_entry(dependent_id).item() {
705 for (guarded_id, guard) in conn.details.secret_content_guards() {
706 if guarded_id == secret_id {
707 guard(contents)?;
708 }
709 }
710 }
711 }
712 Ok(())
713 }
714
715 #[instrument]
716 pub(super) async fn sequence_create_connection(
717 &mut self,
718 mut ctx: ExecuteContext,
719 plan: plan::CreateConnectionPlan,
720 resolved_ids: ResolvedIds,
721 ) {
722 if let Err(err) = check_connection_details(&plan.connection.details) {
723 return ctx.retire(Err(AdapterError::PlanError(err)));
724 }
725
726 let (connection_id, connection_gid) = match self.allocate_user_id().await {
727 Ok(item_id) => item_id,
728 Err(err) => return ctx.retire(Err(err)),
729 };
730
731 match &plan.connection.details {
732 ConnectionDetails::Ssh { key_1, key_2, .. } => {
733 let key_1 = match key_1.as_key_pair() {
734 Some(key_1) => key_1.clone(),
735 None => {
736 return ctx.retire(Err(AdapterError::Unstructured(anyhow!(
737 "the PUBLIC KEY 1 option cannot be explicitly specified"
738 ))));
739 }
740 };
741
742 let key_2 = match key_2.as_key_pair() {
743 Some(key_2) => key_2.clone(),
744 None => {
745 return ctx.retire(Err(AdapterError::Unstructured(anyhow!(
746 "the PUBLIC KEY 2 option cannot be explicitly specified"
747 ))));
748 }
749 };
750
751 let key_set = SshKeyPairSet::from_parts(key_1, key_2);
752 let secret = key_set.to_bytes();
753 if let Err(err) = self.secrets_controller.ensure(connection_id, &secret).await {
754 return ctx.retire(Err(err.into()));
755 }
756 self.caching_secrets_reader.invalidate(connection_id);
757 }
758 _ => (),
759 };
760
761 if let Err(err) = self
764 .check_connection_secret_content_guards(&plan.connection.details)
765 .await
766 {
767 return ctx.retire(Err(err));
768 }
769
770 if plan.validate {
771 let internal_cmd_tx = self.internal_cmd_tx.clone();
772 let catalog = self.owned_catalog();
773 let conn_id = ctx.session().conn_id().clone();
774 let otel_ctx = OpenTelemetryContext::obtain();
775 let role_metadata = ctx.session().role_metadata().clone();
776
777 let connection = plan
778 .connection
779 .details
780 .to_connection()
781 .into_inline_connection(self.catalog().state());
782
783 let current_storage_parameters = self.controller.storage.config().clone();
784 task::spawn(|| format!("validate_connection:{conn_id}"), async move {
785 let result = match std::panic::AssertUnwindSafe(
786 connection.validate(connection_id, ¤t_storage_parameters),
787 )
788 .ore_catch_unwind()
789 .await
790 {
791 Ok(Ok(())) => Ok(plan),
792 Ok(Err(err)) => Err(err.into()),
793 Err(_panic) => {
794 tracing::error!("connection validation panicked");
795 Err(AdapterError::Internal(
796 "connection validation panicked".into(),
797 ))
798 }
799 };
800
801 let result = internal_cmd_tx.send(Message::CreateConnectionValidationReady(
803 CreateConnectionValidationReady {
804 ctx,
805 result,
806 connection_id,
807 connection_gid,
808 plan_validity: PlanValidity::new(
809 &catalog,
810 resolved_ids.items().copied().collect(),
811 None,
812 None,
813 role_metadata,
814 ),
815 otel_ctx,
816 resolved_ids: resolved_ids.clone(),
817 },
818 ));
819 if let Err(e) = result {
820 tracing::warn!("internal_cmd_rx dropped before we could send: {:?}", e);
821 }
822 });
823 } else {
824 let result = self
825 .sequence_create_connection_stage_finish(
826 &mut ctx,
827 connection_id,
828 connection_gid,
829 plan,
830 resolved_ids,
831 )
832 .await;
833 ctx.retire(result);
834 }
835 }
836
837 #[instrument]
838 pub(crate) async fn sequence_create_connection_stage_finish(
839 &mut self,
840 ctx: &mut ExecuteContext,
841 connection_id: CatalogItemId,
842 connection_gid: GlobalId,
843 plan: plan::CreateConnectionPlan,
844 resolved_ids: ResolvedIds,
845 ) -> Result<ExecuteResponse, AdapterError> {
846 let ops = vec![catalog::Op::CreateItem {
847 id: connection_id,
848 name: plan.name.clone(),
849 item: CatalogItem::Connection(Connection {
850 create_sql: plan.connection.create_sql,
851 global_id: connection_gid,
852 details: plan.connection.details.clone(),
853 resolved_ids,
854 }),
855 owner_id: *ctx.session().current_role_id(),
856 }];
857
858 let conn_id = ctx.session().conn_id().clone();
861 let transact_result = self
862 .catalog_transact_with_context(Some(&conn_id), Some(ctx), ops)
863 .await;
864
865 match transact_result {
866 Ok(_) => Ok(ExecuteResponse::CreatedConnection),
867 Err(AdapterError::Catalog(mz_catalog::memory::error::Error {
868 kind: ErrorKind::Sql(CatalogError::ItemAlreadyExists(_, _)),
869 })) if plan.if_not_exists => {
870 if matches!(plan.connection.details, ConnectionDetails::Ssh { .. }) {
873 if let Err(e) = self.secrets_controller.delete(connection_id).await {
874 tracing::warn!(
875 "Dropping SSH secret for existing connection has encountered an error: {}",
876 e
877 );
878 } else {
879 self.caching_secrets_reader.invalidate(connection_id);
880 }
881 }
882 ctx.session()
883 .add_notice(AdapterNotice::ObjectAlreadyExists {
884 name: plan.name.item,
885 ty: "connection",
886 });
887 Ok(ExecuteResponse::CreatedConnection)
888 }
889 Err(err) => {
890 if matches!(plan.connection.details, ConnectionDetails::Ssh { .. }) {
893 if let Err(e) = self.secrets_controller.delete(connection_id).await {
894 tracing::warn!(
895 "Dropping SSH secret for failed connection has encountered an error: {}",
896 e
897 );
898 } else {
899 self.caching_secrets_reader.invalidate(connection_id);
900 }
901 }
902 Err(err)
903 }
904 }
905 }
906
907 #[instrument]
908 pub(super) async fn sequence_create_database(
909 &mut self,
910 session: &Session,
911 plan: plan::CreateDatabasePlan,
912 ) -> Result<ExecuteResponse, AdapterError> {
913 let ops = vec![catalog::Op::CreateDatabase {
914 name: plan.name.clone(),
915 owner_id: *session.current_role_id(),
916 }];
917 match self.catalog_transact(Some(session), ops).await {
918 Ok(_) => Ok(ExecuteResponse::CreatedDatabase),
919 Err(AdapterError::Catalog(mz_catalog::memory::error::Error {
920 kind: ErrorKind::Sql(CatalogError::DatabaseAlreadyExists(_)),
921 })) if plan.if_not_exists => {
922 session.add_notice(AdapterNotice::DatabaseAlreadyExists { name: plan.name });
923 Ok(ExecuteResponse::CreatedDatabase)
924 }
925 Err(err) => Err(err),
926 }
927 }
928
929 #[instrument]
930 pub(super) async fn sequence_create_schema(
931 &mut self,
932 session: &Session,
933 plan: plan::CreateSchemaPlan,
934 ) -> Result<ExecuteResponse, AdapterError> {
935 let op = catalog::Op::CreateSchema {
936 database_id: plan.database_spec,
937 schema_name: plan.schema_name.clone(),
938 owner_id: *session.current_role_id(),
939 };
940 match self.catalog_transact(Some(session), vec![op]).await {
941 Ok(_) => Ok(ExecuteResponse::CreatedSchema),
942 Err(AdapterError::Catalog(mz_catalog::memory::error::Error {
943 kind: ErrorKind::Sql(CatalogError::SchemaAlreadyExists(_)),
944 })) if plan.if_not_exists => {
945 session.add_notice(AdapterNotice::SchemaAlreadyExists {
946 name: plan.schema_name,
947 });
948 Ok(ExecuteResponse::CreatedSchema)
949 }
950 Err(err) => Err(err),
951 }
952 }
953
954 fn validate_role_attributes(&self, attributes: &RoleAttributesRaw) -> Result<(), AdapterError> {
956 if !ENABLE_PASSWORD_AUTH.get(self.catalog().system_config().dyncfgs()) {
957 if attributes.superuser.is_some() || attributes.password.is_some() {
958 return Err(AdapterError::UnavailableFeature {
959 feature: "SUPERUSER and PASSWORD attributes".to_string(),
960 docs: Some("https://materialize.com/docs/sql/create-role/#details".to_string()),
961 });
962 }
963 }
964 Ok(())
965 }
966
967 #[instrument]
968 pub(super) async fn sequence_create_role(
969 &mut self,
970 conn_id: Option<&ConnectionId>,
971 plan::CreateRolePlan { name, attributes }: plan::CreateRolePlan,
972 ) -> Result<ExecuteResponse, AdapterError> {
973 self.validate_role_attributes(&attributes.clone())?;
974 let op = catalog::Op::CreateRole { name, attributes };
975 self.catalog_transact_with_context(conn_id, None, vec![op])
976 .await
977 .map(|_| ExecuteResponse::CreatedRole)
978 }
979
980 #[instrument]
981 pub(super) async fn sequence_create_network_policy(
982 &mut self,
983 session: &Session,
984 plan::CreateNetworkPolicyPlan { name, rules }: plan::CreateNetworkPolicyPlan,
985 ) -> Result<ExecuteResponse, AdapterError> {
986 let op = catalog::Op::CreateNetworkPolicy {
987 rules,
988 name,
989 owner_id: *session.current_role_id(),
990 };
991 self.catalog_transact_with_context(Some(session.conn_id()), None, vec![op])
992 .await
993 .map(|_| ExecuteResponse::CreatedNetworkPolicy)
994 }
995
996 #[instrument]
997 pub(super) async fn sequence_alter_network_policy(
998 &mut self,
999 session: &Session,
1000 plan::AlterNetworkPolicyPlan { id, name, rules }: plan::AlterNetworkPolicyPlan,
1001 ) -> Result<ExecuteResponse, AdapterError> {
1002 let current_network_policy_name =
1004 self.catalog().system_config().default_network_policy_name();
1005 if current_network_policy_name == name {
1007 self.validate_alter_network_policy(session, &rules)?;
1008 }
1009
1010 let op = catalog::Op::AlterNetworkPolicy {
1011 id,
1012 rules,
1013 name,
1014 owner_id: *session.current_role_id(),
1015 };
1016 self.catalog_transact_with_context(Some(session.conn_id()), None, vec![op])
1017 .await
1018 .map(|_| ExecuteResponse::AlteredObject(ObjectType::NetworkPolicy))
1019 }
1020
1021 #[instrument]
1022 pub(super) async fn sequence_create_table(
1023 &mut self,
1024 ctx: &mut ExecuteContext,
1025 plan: plan::CreateTablePlan,
1026 resolved_ids: ResolvedIds,
1027 ) -> Result<ExecuteResponse, AdapterError> {
1028 let plan::CreateTablePlan {
1029 name,
1030 table,
1031 if_not_exists,
1032 } = plan;
1033
1034 let conn_id = if table.temporary {
1035 Some(ctx.session().conn_id())
1036 } else {
1037 None
1038 };
1039 let (table_id, global_id) = self.allocate_user_id().await?;
1040 let collections = [(RelationVersion::root(), global_id)].into_iter().collect();
1041
1042 let data_source = match table.data_source {
1043 plan::TableDataSource::TableWrites { defaults } => {
1044 TableDataSource::TableWrites { defaults }
1045 }
1046 plan::TableDataSource::DataSource {
1047 desc: data_source_plan,
1048 timeline,
1049 } => match data_source_plan {
1050 plan::DataSourceDesc::IngestionExport {
1051 ingestion_id,
1052 external_reference,
1053 details,
1054 data_config,
1055 } => TableDataSource::DataSource {
1056 desc: DataSourceDesc::IngestionExport {
1057 ingestion_id,
1058 external_reference,
1059 details,
1060 data_config,
1061 },
1062 timeline,
1063 },
1064 plan::DataSourceDesc::Webhook {
1065 validate_using,
1066 body_format,
1067 headers,
1068 cluster_id,
1069 } => TableDataSource::DataSource {
1070 desc: DataSourceDesc::Webhook {
1071 validate_using,
1072 body_format,
1073 headers,
1074 cluster_id: cluster_id.expect("Webhook Tables must have cluster_id set"),
1075 },
1076 timeline,
1077 },
1078 o => {
1079 unreachable!("CREATE TABLE data source got {:?}", o)
1080 }
1081 },
1082 };
1083
1084 let is_webhook = if let TableDataSource::DataSource {
1085 desc: DataSourceDesc::Webhook { .. },
1086 timeline: _,
1087 } = &data_source
1088 {
1089 true
1090 } else {
1091 false
1092 };
1093
1094 let table = Table {
1095 create_sql: Some(table.create_sql),
1096 desc: table.desc,
1097 collections,
1098 conn_id: conn_id.cloned(),
1099 resolved_ids,
1100 custom_logical_compaction_window: table.compaction_window,
1101 is_retained_metrics_object: false,
1102 data_source,
1103 };
1104 let ops = vec![catalog::Op::CreateItem {
1105 id: table_id,
1106 name: name.clone(),
1107 item: CatalogItem::Table(table.clone()),
1108 owner_id: *ctx.session().current_role_id(),
1109 }];
1110
1111 let catalog_result = self
1112 .catalog_transact_with_ddl_transaction(ctx, ops, |_, _| Box::pin(async {}))
1113 .await;
1114
1115 if is_webhook {
1116 if let Some(url) = self.catalog().state().try_get_webhook_url(&table_id) {
1119 ctx.session()
1120 .add_notice(AdapterNotice::WebhookSourceCreated { url })
1121 }
1122 }
1123
1124 match catalog_result {
1125 Ok(()) => Ok(ExecuteResponse::CreatedTable),
1126 Err(AdapterError::Catalog(mz_catalog::memory::error::Error {
1127 kind: ErrorKind::Sql(CatalogError::ItemAlreadyExists(_, _)),
1128 })) if if_not_exists => {
1129 ctx.session_mut()
1130 .add_notice(AdapterNotice::ObjectAlreadyExists {
1131 name: name.item,
1132 ty: "table",
1133 });
1134 Ok(ExecuteResponse::CreatedTable)
1135 }
1136 Err(err) => Err(err),
1137 }
1138 }
1139
1140 #[instrument]
1141 pub(super) async fn sequence_create_sink(
1142 &mut self,
1143 ctx: ExecuteContext,
1144 plan: plan::CreateSinkPlan,
1145 resolved_ids: ResolvedIds,
1146 ) {
1147 let plan::CreateSinkPlan {
1148 name,
1149 sink,
1150 with_snapshot,
1151 if_not_exists,
1152 in_cluster,
1153 } = plan;
1154
1155 let (item_id, global_id) = return_if_err!(self.allocate_user_id().await, ctx);
1157
1158 let catalog_sink = Sink {
1159 create_sql: sink.create_sql,
1160 global_id,
1161 from: sink.from,
1162 connection: sink.connection,
1163 envelope: sink.envelope,
1164 version: sink.version,
1165 with_snapshot,
1166 resolved_ids,
1167 cluster_id: in_cluster,
1168 commit_interval: sink.commit_interval,
1169 };
1170
1171 let ops = vec![catalog::Op::CreateItem {
1172 id: item_id,
1173 name: name.clone(),
1174 item: CatalogItem::Sink(catalog_sink.clone()),
1175 owner_id: *ctx.session().current_role_id(),
1176 }];
1177
1178 let result = self.catalog_transact(Some(ctx.session()), ops).await;
1179
1180 match result {
1181 Ok(()) => {}
1182 Err(AdapterError::Catalog(mz_catalog::memory::error::Error {
1183 kind: ErrorKind::Sql(CatalogError::ItemAlreadyExists(_, _)),
1184 })) if if_not_exists => {
1185 ctx.session()
1186 .add_notice(AdapterNotice::ObjectAlreadyExists {
1187 name: name.item,
1188 ty: "sink",
1189 });
1190 ctx.retire(Ok(ExecuteResponse::CreatedSink));
1191 return;
1192 }
1193 Err(e) => {
1194 ctx.retire(Err(e));
1195 return;
1196 }
1197 };
1198
1199 self.create_storage_export(global_id, &catalog_sink)
1200 .await
1201 .unwrap_or_terminate("cannot fail to create exports");
1202
1203 self.initialize_storage_read_policies([item_id].into(), CompactionWindow::Default)
1204 .await;
1205
1206 ctx.retire(Ok(ExecuteResponse::CreatedSink))
1207 }
1208
1209 pub(super) fn validate_system_column_references(
1232 &self,
1233 uses_ambiguous_columns: bool,
1234 depends_on: &BTreeSet<GlobalId>,
1235 ) -> Result<(), AdapterError> {
1236 if uses_ambiguous_columns
1237 && depends_on
1238 .iter()
1239 .any(|id| id.is_system() && self.catalog().get_entry_by_global_id(id).is_relation())
1240 {
1241 Err(AdapterError::AmbiguousSystemColumnReference)
1242 } else {
1243 Ok(())
1244 }
1245 }
1246
1247 #[instrument]
1248 pub(super) async fn sequence_create_type(
1249 &mut self,
1250 session: &Session,
1251 plan: plan::CreateTypePlan,
1252 resolved_ids: ResolvedIds,
1253 ) -> Result<ExecuteResponse, AdapterError> {
1254 let (item_id, global_id) = self.allocate_user_id().await?;
1255 plan.typ
1257 .inner
1258 .desc(&self.catalog().for_session(session))
1259 .map_err(AdapterError::from)?;
1260 let typ = Type {
1261 create_sql: Some(plan.typ.create_sql),
1262 global_id,
1263 details: CatalogTypeDetails {
1264 array_id: None,
1265 typ: plan.typ.inner,
1266 pg_metadata: None,
1267 },
1268 resolved_ids,
1269 };
1270 let op = catalog::Op::CreateItem {
1271 id: item_id,
1272 name: plan.name,
1273 item: CatalogItem::Type(typ),
1274 owner_id: *session.current_role_id(),
1275 };
1276 match self.catalog_transact(Some(session), vec![op]).await {
1277 Ok(()) => Ok(ExecuteResponse::CreatedType),
1278 Err(err) => Err(err),
1279 }
1280 }
1281
1282 #[instrument]
1283 pub(super) async fn sequence_comment_on(
1284 &mut self,
1285 session: &Session,
1286 plan: plan::CommentPlan,
1287 ) -> Result<ExecuteResponse, AdapterError> {
1288 let op = catalog::Op::Comment {
1289 object_id: plan.object_id,
1290 sub_component: plan.sub_component,
1291 comment: plan.comment,
1292 };
1293 self.catalog_transact(Some(session), vec![op]).await?;
1294 Ok(ExecuteResponse::Comment)
1295 }
1296
1297 #[instrument]
1298 pub(super) async fn sequence_drop_objects(
1299 &mut self,
1300 ctx: &mut ExecuteContext,
1301 plan::DropObjectsPlan {
1302 drop_ids,
1303 object_type,
1304 referenced_ids,
1305 }: plan::DropObjectsPlan,
1306 ) -> Result<ExecuteResponse, AdapterError> {
1307 let referenced_ids_hashset = referenced_ids.iter().collect::<HashSet<_>>();
1308 let mut objects = Vec::new();
1309 for obj_id in &drop_ids {
1310 if !referenced_ids_hashset.contains(obj_id) {
1311 let object_info = ErrorMessageObjectDescription::from_id(
1312 obj_id,
1313 &self.catalog().for_session(ctx.session()),
1314 )
1315 .to_string();
1316 objects.push(object_info);
1317 }
1318 }
1319
1320 if !objects.is_empty() {
1321 ctx.session()
1322 .add_notice(AdapterNotice::CascadeDroppedObject { objects });
1323 }
1324
1325 let expr_cache_invalidate_ids: BTreeSet<_> = drop_ids
1327 .iter()
1328 .filter_map(|id| match id {
1329 ObjectId::Item(item_id) => Some(self.catalog().get_entry(item_id).global_ids()),
1330 _ => None,
1331 })
1332 .flatten()
1333 .collect();
1334
1335 let DropOps {
1336 ops,
1337 dropped_active_db,
1338 dropped_active_cluster,
1339 dropped_in_use_indexes,
1340 } = self.sequence_drop_common(ctx.session(), drop_ids)?;
1341
1342 self.catalog_transact_with_context(None, Some(ctx), ops)
1343 .await?;
1344
1345 if !expr_cache_invalidate_ids.is_empty() {
1347 let _fut = self.catalog().update_expression_cache(
1348 Default::default(),
1349 Default::default(),
1350 expr_cache_invalidate_ids,
1351 );
1352 }
1353
1354 fail::fail_point!("after_sequencer_drop_replica");
1355
1356 if dropped_active_db {
1357 ctx.session()
1358 .add_notice(AdapterNotice::DroppedActiveDatabase {
1359 name: ctx.session().vars().database().to_string(),
1360 });
1361 }
1362 if dropped_active_cluster {
1363 ctx.session()
1364 .add_notice(AdapterNotice::DroppedActiveCluster {
1365 name: ctx.session().vars().cluster().to_string(),
1366 });
1367 }
1368 for dropped_in_use_index in dropped_in_use_indexes {
1369 ctx.session()
1370 .add_notice(AdapterNotice::DroppedInUseIndex(dropped_in_use_index));
1371 self.metrics
1372 .optimization_notices
1373 .with_label_values(&["DroppedInUseIndex"])
1374 .inc_by(1);
1375 }
1376 Ok(ExecuteResponse::DroppedObject(object_type))
1377 }
1378
1379 fn validate_dropped_role_ownership(
1380 &self,
1381 session: &Session,
1382 dropped_roles: &BTreeMap<RoleId, &str>,
1383 ) -> Result<(), AdapterError> {
1384 fn privilege_check(
1385 privileges: &PrivilegeMap,
1386 dropped_roles: &BTreeMap<RoleId, &str>,
1387 dependent_objects: &mut BTreeMap<String, Vec<String>>,
1388 object_id: &SystemObjectId,
1389 catalog: &ConnCatalog,
1390 ) {
1391 for privilege in privileges.all_values() {
1392 if let Some(role_name) = dropped_roles.get(&privilege.grantee) {
1393 let grantor_name = catalog.get_role(&privilege.grantor).name();
1394 let object_description =
1395 ErrorMessageObjectDescription::from_sys_id(object_id, catalog);
1396 dependent_objects
1397 .entry(role_name.to_string())
1398 .or_default()
1399 .push(format!(
1400 "privileges on {object_description} granted by {grantor_name}",
1401 ));
1402 }
1403 if let Some(role_name) = dropped_roles.get(&privilege.grantor) {
1404 let grantee_name = catalog.get_role(&privilege.grantee).name();
1405 let object_description =
1406 ErrorMessageObjectDescription::from_sys_id(object_id, catalog);
1407 dependent_objects
1408 .entry(role_name.to_string())
1409 .or_default()
1410 .push(format!(
1411 "privileges granted on {object_description} to {grantee_name}"
1412 ));
1413 }
1414 }
1415 }
1416
1417 let catalog = self.catalog().for_session(session);
1418 let mut dependent_objects: BTreeMap<_, Vec<_>> = BTreeMap::new();
1419 for entry in self.catalog.entries() {
1420 let id = SystemObjectId::Object(entry.id().into());
1421 if let Some(role_name) = dropped_roles.get(entry.owner_id()) {
1422 let object_description = ErrorMessageObjectDescription::from_sys_id(&id, &catalog);
1423 dependent_objects
1424 .entry(role_name.to_string())
1425 .or_default()
1426 .push(format!("owner of {object_description}"));
1427 }
1428 privilege_check(
1429 entry.privileges(),
1430 dropped_roles,
1431 &mut dependent_objects,
1432 &id,
1433 &catalog,
1434 );
1435 }
1436 for database in self.catalog.databases() {
1437 let database_id = SystemObjectId::Object(database.id().into());
1438 if let Some(role_name) = dropped_roles.get(&database.owner_id) {
1439 let object_description =
1440 ErrorMessageObjectDescription::from_sys_id(&database_id, &catalog);
1441 dependent_objects
1442 .entry(role_name.to_string())
1443 .or_default()
1444 .push(format!("owner of {object_description}"));
1445 }
1446 privilege_check(
1447 &database.privileges,
1448 dropped_roles,
1449 &mut dependent_objects,
1450 &database_id,
1451 &catalog,
1452 );
1453 for schema in database.schemas_by_id.values() {
1454 let schema_id = SystemObjectId::Object(
1455 (ResolvedDatabaseSpecifier::Id(database.id()), *schema.id()).into(),
1456 );
1457 if let Some(role_name) = dropped_roles.get(&schema.owner_id) {
1458 let object_description =
1459 ErrorMessageObjectDescription::from_sys_id(&schema_id, &catalog);
1460 dependent_objects
1461 .entry(role_name.to_string())
1462 .or_default()
1463 .push(format!("owner of {object_description}"));
1464 }
1465 privilege_check(
1466 &schema.privileges,
1467 dropped_roles,
1468 &mut dependent_objects,
1469 &schema_id,
1470 &catalog,
1471 );
1472 }
1473 }
1474 for cluster in self.catalog.clusters() {
1475 let cluster_id = SystemObjectId::Object(cluster.id().into());
1476 if let Some(role_name) = dropped_roles.get(&cluster.owner_id) {
1477 let object_description =
1478 ErrorMessageObjectDescription::from_sys_id(&cluster_id, &catalog);
1479 dependent_objects
1480 .entry(role_name.to_string())
1481 .or_default()
1482 .push(format!("owner of {object_description}"));
1483 }
1484 privilege_check(
1485 &cluster.privileges,
1486 dropped_roles,
1487 &mut dependent_objects,
1488 &cluster_id,
1489 &catalog,
1490 );
1491 for replica in cluster.replicas() {
1492 if let Some(role_name) = dropped_roles.get(&replica.owner_id) {
1493 let replica_id =
1494 SystemObjectId::Object((replica.cluster_id(), replica.replica_id()).into());
1495 let object_description =
1496 ErrorMessageObjectDescription::from_sys_id(&replica_id, &catalog);
1497 dependent_objects
1498 .entry(role_name.to_string())
1499 .or_default()
1500 .push(format!("owner of {object_description}"));
1501 }
1502 }
1503 }
1504 privilege_check(
1505 self.catalog().system_privileges(),
1506 dropped_roles,
1507 &mut dependent_objects,
1508 &SystemObjectId::System,
1509 &catalog,
1510 );
1511 for (default_privilege_object, default_privilege_acl_items) in
1512 self.catalog.default_privileges()
1513 {
1514 if let Some(role_name) = dropped_roles.get(&default_privilege_object.role_id) {
1515 dependent_objects
1516 .entry(role_name.to_string())
1517 .or_default()
1518 .push(format!(
1519 "default privileges on {}S created by {}",
1520 default_privilege_object.object_type, role_name
1521 ));
1522 }
1523 for default_privilege_acl_item in default_privilege_acl_items {
1524 if let Some(role_name) = dropped_roles.get(&default_privilege_acl_item.grantee) {
1525 dependent_objects
1526 .entry(role_name.to_string())
1527 .or_default()
1528 .push(format!(
1529 "default privileges on {}S granted to {}",
1530 default_privilege_object.object_type, role_name
1531 ));
1532 }
1533 }
1534 }
1535
1536 if !dependent_objects.is_empty() {
1537 Err(AdapterError::DependentObject(dependent_objects))
1538 } else {
1539 Ok(())
1540 }
1541 }
1542
1543 #[instrument]
1544 pub(super) async fn sequence_drop_owned(
1545 &mut self,
1546 session: &Session,
1547 plan: plan::DropOwnedPlan,
1548 ) -> Result<ExecuteResponse, AdapterError> {
1549 for role_id in &plan.role_ids {
1550 self.catalog().ensure_not_reserved_role(role_id)?;
1551 }
1552
1553 let mut privilege_revokes = plan.privilege_revokes;
1554
1555 let session_catalog = self.catalog().for_session(session);
1557 if rbac::is_rbac_enabled_for_session(session_catalog.system_vars(), session)
1558 && !session.is_superuser()
1559 {
1560 let role_membership =
1562 session_catalog.collect_role_membership(session.current_role_id());
1563 let invalid_revokes: BTreeSet<_> = privilege_revokes
1564 .extract_if(.., |(_, privilege)| {
1565 !role_membership.contains(&privilege.grantor)
1566 })
1567 .map(|(object_id, _)| object_id)
1568 .collect();
1569 for invalid_revoke in invalid_revokes {
1570 let object_description =
1571 ErrorMessageObjectDescription::from_sys_id(&invalid_revoke, &session_catalog);
1572 session.add_notice(AdapterNotice::CannotRevoke { object_description });
1573 }
1574 }
1575
1576 let mut privilege_revokes_by_target: BTreeMap<SystemObjectId, Vec<MzAclItem>> =
1578 BTreeMap::new();
1579 for (object_id, privilege) in privilege_revokes {
1580 privilege_revokes_by_target
1581 .entry(object_id)
1582 .or_default()
1583 .push(privilege);
1584 }
1585 let privilege_revoke_ops =
1586 privilege_revokes_by_target
1587 .into_iter()
1588 .map(|(target_id, privileges)| catalog::Op::UpdatePrivilege {
1589 target_id,
1590 privileges,
1591 variant: UpdatePrivilegeVariant::Revoke,
1592 });
1593 let default_privilege_revoke_ops = plan.default_privilege_revokes.into_iter().map(
1594 |(privilege_object, privilege_acl_item)| catalog::Op::UpdateDefaultPrivilege {
1595 privilege_object,
1596 privilege_acl_item,
1597 variant: UpdatePrivilegeVariant::Revoke,
1598 },
1599 );
1600 let DropOps {
1601 ops: drop_ops,
1602 dropped_active_db,
1603 dropped_active_cluster,
1604 dropped_in_use_indexes,
1605 } = self.sequence_drop_common(session, plan.drop_ids)?;
1606
1607 let ops = privilege_revoke_ops
1608 .chain(default_privilege_revoke_ops)
1609 .chain(drop_ops.into_iter())
1610 .collect();
1611
1612 self.catalog_transact(Some(session), ops).await?;
1613
1614 if dropped_active_db {
1615 session.add_notice(AdapterNotice::DroppedActiveDatabase {
1616 name: session.vars().database().to_string(),
1617 });
1618 }
1619 if dropped_active_cluster {
1620 session.add_notice(AdapterNotice::DroppedActiveCluster {
1621 name: session.vars().cluster().to_string(),
1622 });
1623 }
1624 for dropped_in_use_index in dropped_in_use_indexes {
1625 session.add_notice(AdapterNotice::DroppedInUseIndex(dropped_in_use_index));
1626 }
1627 Ok(ExecuteResponse::DroppedOwned)
1628 }
1629
1630 fn sequence_drop_common(
1631 &self,
1632 session: &Session,
1633 ids: Vec<ObjectId>,
1634 ) -> Result<DropOps, AdapterError> {
1635 let mut dropped_active_db = false;
1636 let mut dropped_active_cluster = false;
1637 let mut dropped_in_use_indexes = Vec::new();
1638 let mut dropped_roles = BTreeMap::new();
1639 let mut dropped_databases = BTreeSet::new();
1640 let mut dropped_schemas = BTreeSet::new();
1641 let mut role_revokes = BTreeSet::new();
1645 let mut default_privilege_revokes = BTreeSet::new();
1648
1649 let mut clusters_to_drop = BTreeSet::new();
1651
1652 let ids_set = ids.iter().collect::<BTreeSet<_>>();
1653 for id in &ids {
1654 match id {
1655 ObjectId::Database(id) => {
1656 let name = self.catalog().get_database(id).name();
1657 if name == session.vars().database() {
1658 dropped_active_db = true;
1659 }
1660 dropped_databases.insert(id);
1661 }
1662 ObjectId::Schema((_, spec)) => {
1663 if let SchemaSpecifier::Id(id) = spec {
1664 dropped_schemas.insert(id);
1665 }
1666 }
1667 ObjectId::Cluster(id) => {
1668 clusters_to_drop.insert(*id);
1669 if let Some(active_id) = self
1670 .catalog()
1671 .active_cluster(session)
1672 .ok()
1673 .map(|cluster| cluster.id())
1674 {
1675 if id == &active_id {
1676 dropped_active_cluster = true;
1677 }
1678 }
1679 }
1680 ObjectId::Role(id) => {
1681 let role = self.catalog().get_role(id);
1682 let name = role.name();
1683 dropped_roles.insert(*id, name);
1684 for (group_id, grantor_id) in &role.membership.map {
1686 role_revokes.insert((*group_id, *id, *grantor_id));
1687 }
1688 }
1689 ObjectId::Item(id) => {
1690 if let Some(index) = self.catalog().get_entry(id).index() {
1691 let humanizer = self.catalog().for_session(session);
1692 let dependants = self
1693 .controller
1694 .compute
1695 .collection_reverse_dependencies(index.cluster_id, index.global_id())
1696 .ok()
1697 .into_iter()
1698 .flatten()
1699 .filter(|dependant_id| {
1700 if dependant_id.is_transient() {
1707 return false;
1708 }
1709 let Some(dependent_id) = humanizer
1711 .try_get_item_by_global_id(dependant_id)
1712 .map(|item| item.id())
1713 else {
1714 return false;
1715 };
1716 !ids_set.contains(&ObjectId::Item(dependent_id))
1719 })
1720 .flat_map(|dependant_id| {
1721 humanizer.humanize_id(dependant_id)
1725 })
1726 .collect_vec();
1727 if !dependants.is_empty() {
1728 dropped_in_use_indexes.push(DroppedInUseIndex {
1729 index_name: humanizer
1730 .humanize_id(index.global_id())
1731 .unwrap_or_else(|| id.to_string()),
1732 dependant_objects: dependants,
1733 });
1734 }
1735 }
1736 }
1737 _ => {}
1738 }
1739 }
1740
1741 for id in &ids {
1742 match id {
1743 ObjectId::ClusterReplica((cluster_id, replica_id)) => {
1747 if !clusters_to_drop.contains(cluster_id) {
1748 let cluster = self.catalog.get_cluster(*cluster_id);
1749 if cluster.is_managed() {
1750 let replica =
1751 cluster.replica(*replica_id).expect("Catalog out of sync");
1752 if !replica.config.location.internal() {
1753 coord_bail!("cannot drop replica of managed cluster");
1754 }
1755 }
1756 }
1757 }
1758 _ => {}
1759 }
1760 }
1761
1762 for role_id in dropped_roles.keys() {
1763 self.catalog().ensure_not_reserved_role(role_id)?;
1764 }
1765 self.validate_dropped_role_ownership(session, &dropped_roles)?;
1766 let dropped_role_ids: BTreeSet<_> = dropped_roles.keys().collect();
1768 for role in self.catalog().user_roles() {
1769 for dropped_role_id in
1770 dropped_role_ids.intersection(&role.membership.map.keys().collect())
1771 {
1772 role_revokes.insert((
1773 **dropped_role_id,
1774 role.id(),
1775 *role
1776 .membership
1777 .map
1778 .get(*dropped_role_id)
1779 .expect("included in keys above"),
1780 ));
1781 }
1782 }
1783
1784 for (default_privilege_object, default_privilege_acls) in
1785 self.catalog().default_privileges()
1786 {
1787 if matches!(
1788 &default_privilege_object.database_id,
1789 Some(database_id) if dropped_databases.contains(database_id),
1790 ) || matches!(
1791 &default_privilege_object.schema_id,
1792 Some(schema_id) if dropped_schemas.contains(schema_id),
1793 ) {
1794 for default_privilege_acl in default_privilege_acls {
1795 default_privilege_revokes.insert((
1796 default_privilege_object.clone(),
1797 default_privilege_acl.clone(),
1798 ));
1799 }
1800 }
1801 }
1802
1803 let ops = role_revokes
1804 .into_iter()
1805 .map(|(role_id, member_id, grantor_id)| catalog::Op::RevokeRole {
1806 role_id,
1807 member_id,
1808 grantor_id,
1809 })
1810 .chain(default_privilege_revokes.into_iter().map(
1811 |(privilege_object, privilege_acl_item)| catalog::Op::UpdateDefaultPrivilege {
1812 privilege_object,
1813 privilege_acl_item,
1814 variant: UpdatePrivilegeVariant::Revoke,
1815 },
1816 ))
1817 .chain(iter::once(catalog::Op::DropObjects(
1818 ids.into_iter()
1819 .map(DropObjectInfo::manual_drop_from_object_id)
1820 .collect(),
1821 )))
1822 .collect();
1823
1824 Ok(DropOps {
1825 ops,
1826 dropped_active_db,
1827 dropped_active_cluster,
1828 dropped_in_use_indexes,
1829 })
1830 }
1831
1832 pub(super) fn sequence_explain_schema(
1833 &self,
1834 ExplainSinkSchemaPlan { json_schema, .. }: ExplainSinkSchemaPlan,
1835 ) -> Result<ExecuteResponse, AdapterError> {
1836 let json_value: serde_json::Value = serde_json::from_str(&json_schema).map_err(|e| {
1837 AdapterError::Explain(mz_repr::explain::ExplainError::SerdeJsonError(e))
1838 })?;
1839
1840 let json_string = json_string(&json_value);
1841 let row = Row::pack_slice(&[Datum::String(&json_string)]);
1842 Ok(Self::send_immediate_rows(row))
1843 }
1844
1845 pub(super) fn sequence_show_all_variables(
1846 &self,
1847 session: &Session,
1848 ) -> Result<ExecuteResponse, AdapterError> {
1849 let mut rows = viewable_variables(self.catalog().state(), session)
1850 .map(|v| (v.name(), v.value(), v.description()))
1851 .collect::<Vec<_>>();
1852 rows.sort_by_cached_key(|(name, _, _)| name.to_lowercase());
1853
1854 let rows: Vec<_> = rows
1856 .into_iter()
1857 .map(|(name, val, desc)| {
1858 Row::pack_slice(&[
1859 Datum::String(name),
1860 Datum::String(&val),
1861 Datum::String(desc),
1862 ])
1863 })
1864 .collect();
1865 Ok(Self::send_immediate_rows(rows))
1866 }
1867
1868 pub(super) fn sequence_show_variable(
1869 &self,
1870 session: &Session,
1871 plan: plan::ShowVariablePlan,
1872 ) -> Result<ExecuteResponse, AdapterError> {
1873 if &plan.name == SCHEMA_ALIAS {
1874 let schemas = self.catalog.resolve_search_path(session);
1875 let schema = schemas.first();
1876 return match schema {
1877 Some((database_spec, schema_spec)) => {
1878 let schema_name = &self
1879 .catalog
1880 .get_schema(database_spec, schema_spec, session.conn_id())
1881 .name()
1882 .schema;
1883 let row = Row::pack_slice(&[Datum::String(schema_name)]);
1884 Ok(Self::send_immediate_rows(row))
1885 }
1886 None => {
1887 if session.vars().current_object_missing_warnings() {
1888 session.add_notice(AdapterNotice::NoResolvableSearchPathSchema {
1889 search_path: session
1890 .vars()
1891 .search_path()
1892 .into_iter()
1893 .map(|schema| schema.to_string())
1894 .collect(),
1895 });
1896 }
1897 Ok(Self::send_immediate_rows(Row::pack_slice(&[Datum::Null])))
1898 }
1899 };
1900 }
1901
1902 let variable = session
1903 .vars()
1904 .get(self.catalog().system_config(), &plan.name)
1905 .or_else(|_| self.catalog().system_config().get(&plan.name))?;
1906
1907 variable.visible(session.user(), self.catalog().system_config())?;
1910
1911 let row = Row::pack_slice(&[Datum::String(&variable.value())]);
1912 if variable.name() == vars::DATABASE.name()
1913 && matches!(
1914 self.catalog().resolve_database(&variable.value()),
1915 Err(CatalogError::UnknownDatabase(_))
1916 )
1917 && session.vars().current_object_missing_warnings()
1918 {
1919 let name = variable.value();
1920 session.add_notice(AdapterNotice::DatabaseDoesNotExist { name });
1921 } else if variable.name() == vars::CLUSTER.name()
1922 && matches!(
1923 self.catalog().resolve_cluster(&variable.value()),
1924 Err(CatalogError::UnknownCluster(_))
1925 )
1926 && session.vars().current_object_missing_warnings()
1927 {
1928 let name = variable.value();
1929 session.add_notice(AdapterNotice::ClusterDoesNotExist { name });
1930 }
1931 Ok(Self::send_immediate_rows(row))
1932 }
1933
1934 #[instrument]
1935 pub(super) async fn sequence_inspect_shard(
1936 &self,
1937 session: &Session,
1938 plan: plan::InspectShardPlan,
1939 ) -> Result<ExecuteResponse, AdapterError> {
1940 if !session.user().is_internal() {
1943 return Err(AdapterError::Unauthorized(
1944 rbac::UnauthorizedError::MzSystem {
1945 action: "inspect".into(),
1946 },
1947 ));
1948 }
1949 let state = self
1950 .controller
1951 .storage
1952 .inspect_persist_state(plan.id)
1953 .await?;
1954 let jsonb = Jsonb::from_serde_json(state)?;
1955 Ok(Self::send_immediate_rows(jsonb.into_row()))
1956 }
1957
1958 #[instrument]
1959 pub(super) fn sequence_set_variable(
1960 &self,
1961 session: &mut Session,
1962 plan: plan::SetVariablePlan,
1963 ) -> Result<ExecuteResponse, AdapterError> {
1964 let (name, local) = (plan.name, plan.local);
1965 if &name == TRANSACTION_ISOLATION_VAR_NAME {
1966 self.validate_set_isolation_level(session)?;
1967 }
1968 if &name == vars::CLUSTER.name() {
1969 self.validate_set_cluster(session)?;
1970 }
1971
1972 let vars = session.vars_mut();
1973 let values = match plan.value {
1974 plan::VariableValue::Default => None,
1975 plan::VariableValue::Values(values) => Some(values),
1976 };
1977
1978 match values {
1979 Some(values) => {
1980 vars.set(
1981 self.catalog().system_config(),
1982 &name,
1983 VarInput::SqlSet(&values),
1984 local,
1985 )?;
1986
1987 let vars = session.vars();
1988
1989 if name == vars::OLD_AUTO_ROUTE_CATALOG_QUERIES {
1992 session.add_notice(AdapterNotice::AutoRouteIntrospectionQueriesUsage);
1993 } else if name == vars::CLUSTER.name()
1994 && values[0] == vars::OLD_CATALOG_SERVER_CLUSTER
1995 {
1996 session.add_notice(AdapterNotice::IntrospectionClusterUsage);
1997 }
1998
1999 if name.as_str() == vars::DATABASE.name()
2001 && matches!(
2002 self.catalog().resolve_database(vars.database()),
2003 Err(CatalogError::UnknownDatabase(_))
2004 )
2005 && session.vars().current_object_missing_warnings()
2006 {
2007 let name = vars.database().to_string();
2008 session.add_notice(AdapterNotice::DatabaseDoesNotExist { name });
2009 } else if name.as_str() == vars::CLUSTER.name()
2010 && matches!(
2011 self.catalog().resolve_cluster(vars.cluster()),
2012 Err(CatalogError::UnknownCluster(_))
2013 )
2014 && session.vars().current_object_missing_warnings()
2015 {
2016 let name = vars.cluster().to_string();
2017 session.add_notice(AdapterNotice::ClusterDoesNotExist { name });
2018 } else if name.as_str() == TRANSACTION_ISOLATION_VAR_NAME {
2019 let v = values.into_first().to_lowercase();
2020 if v == IsolationLevel::ReadUncommitted.as_variant_str()
2021 || v == IsolationLevel::ReadCommitted.as_variant_str()
2022 || v == IsolationLevel::RepeatableRead.as_variant_str()
2023 {
2024 session.add_notice(AdapterNotice::UnimplementedIsolationLevel {
2025 isolation_level: v,
2026 });
2027 } else if v == IsolationLevel::StrongSessionSerializable.as_variant_str() {
2028 session.add_notice(AdapterNotice::StrongSessionSerializable);
2029 }
2030 }
2031
2032 if (name.as_str() == TRANSACTION_ISOLATION_VAR_NAME
2036 || name.as_str() == vars::REAL_TIME_RECENCY.name())
2037 && session
2038 .vars()
2039 .transaction_isolation()
2040 .is_bounded_staleness()
2041 && session.vars().real_time_recency()
2042 {
2043 return Err(AdapterError::BoundedStalenessRealTimeRecencyConflict);
2044 }
2045 }
2046 None => vars.reset(self.catalog().system_config(), &name, local)?,
2047 }
2048
2049 Ok(ExecuteResponse::SetVariable { name, reset: false })
2050 }
2051
2052 pub(super) fn sequence_reset_variable(
2053 &self,
2054 session: &mut Session,
2055 plan: plan::ResetVariablePlan,
2056 ) -> Result<ExecuteResponse, AdapterError> {
2057 let name = plan.name;
2058 if &name == TRANSACTION_ISOLATION_VAR_NAME {
2059 self.validate_set_isolation_level(session)?;
2060 }
2061 if &name == vars::CLUSTER.name() {
2062 self.validate_set_cluster(session)?;
2063 }
2064 session
2065 .vars_mut()
2066 .reset(self.catalog().system_config(), &name, false)?;
2067 Ok(ExecuteResponse::SetVariable { name, reset: true })
2068 }
2069
2070 pub(super) fn sequence_set_transaction(
2071 &self,
2072 session: &mut Session,
2073 plan: plan::SetTransactionPlan,
2074 ) -> Result<ExecuteResponse, AdapterError> {
2075 for mode in plan.modes {
2077 match mode {
2078 TransactionMode::AccessMode(_) => {
2079 return Err(AdapterError::Unsupported("SET TRANSACTION <access-mode>"));
2080 }
2081 TransactionMode::IsolationLevel(isolation_level) => {
2082 self.validate_set_isolation_level(session)?;
2083
2084 session.vars_mut().set(
2085 self.catalog().system_config(),
2086 TRANSACTION_ISOLATION_VAR_NAME,
2087 VarInput::Flat(&isolation_level.to_ast_string_stable()),
2088 plan.local,
2089 )?
2090 }
2091 }
2092 }
2093 Ok(ExecuteResponse::SetVariable {
2094 name: TRANSACTION_ISOLATION_VAR_NAME.to_string(),
2095 reset: false,
2096 })
2097 }
2098
2099 fn validate_set_isolation_level(&self, session: &Session) -> Result<(), AdapterError> {
2100 if session.transaction().contains_ops() {
2101 Err(AdapterError::InvalidSetIsolationLevel)
2102 } else {
2103 Ok(())
2104 }
2105 }
2106
2107 fn validate_set_cluster(&self, session: &Session) -> Result<(), AdapterError> {
2108 if session.transaction().contains_ops() {
2109 Err(AdapterError::InvalidSetCluster)
2110 } else {
2111 Ok(())
2112 }
2113 }
2114
2115 #[instrument]
2116 pub(super) async fn sequence_end_transaction(
2117 &mut self,
2118 mut ctx: ExecuteContext,
2119 mut action: EndTransactionAction,
2120 ) {
2121 if let (EndTransactionAction::Commit, TransactionStatus::Failed(_)) =
2123 (&action, ctx.session().transaction())
2124 {
2125 action = EndTransactionAction::Rollback;
2126 }
2127 let response = match action {
2128 EndTransactionAction::Commit => Ok(PendingTxnResponse::Committed {
2129 params: BTreeMap::new(),
2130 }),
2131 EndTransactionAction::Rollback => Ok(PendingTxnResponse::Rolledback {
2132 params: BTreeMap::new(),
2133 }),
2134 };
2135
2136 let result = self.sequence_end_transaction_inner(&mut ctx, action).await;
2137
2138 let (response, action) = match result {
2139 Ok(Some(TransactionOps::Writes(writes))) if writes.is_empty() => (response, action),
2140 Ok(Some(TransactionOps::Writes(writes))) => {
2141 let mut collected_writes: BTreeMap<CatalogItemId, SmallVec<_>> = BTreeMap::new();
2142 for WriteOp { id, rows } in writes {
2143 let total_rows = collected_writes.entry(id).or_default();
2144 total_rows.push(rows);
2145 }
2146
2147 self.submit_write(PendingWriteTxn::User {
2148 span: Span::current(),
2149 writes: collected_writes,
2150 responder: UserWriteResponder::Session(PendingTxn {
2151 ctx,
2152 response,
2153 action,
2154 }),
2155 });
2156 return;
2157 }
2158 Ok(Some(TransactionOps::Peeks {
2159 determination,
2160 requires_linearization: RequireLinearization::Required,
2161 ..
2162 })) if ctx.session().vars().transaction_isolation()
2163 == &IsolationLevel::StrictSerializable =>
2164 {
2165 let conn_id = ctx.session().conn_id().clone();
2166 let pending_read_txn = PendingReadTxn {
2167 txn: PendingTxn {
2168 ctx,
2169 response,
2170 action,
2171 },
2172 timestamp_context: determination.timestamp_context,
2173 created: Instant::now(),
2174 num_requeues: 0,
2175 otel_ctx: OpenTelemetryContext::obtain(),
2176 };
2177 self.strict_serializable_reads_tx
2178 .send((conn_id, pending_read_txn))
2179 .expect("sending to strict_serializable_reads_tx cannot fail");
2180 return;
2181 }
2182 Ok(Some(TransactionOps::Peeks {
2183 determination,
2184 requires_linearization: RequireLinearization::Required,
2185 ..
2186 })) if ctx.session().vars().transaction_isolation()
2187 == &IsolationLevel::StrongSessionSerializable =>
2188 {
2189 if let Some((timeline, ts)) = determination.timestamp_context.timeline_timestamp() {
2190 ctx.session_mut()
2191 .ensure_timestamp_oracle(timeline.clone())
2192 .apply_write(*ts);
2193 }
2194 (response, action)
2195 }
2196 Ok(Some(TransactionOps::SingleStatement { stmt, params })) => {
2197 self.internal_cmd_tx
2198 .send(Message::ExecuteSingleStatementTransaction {
2199 ctx,
2200 otel_ctx: OpenTelemetryContext::obtain(),
2201 stmt,
2202 params,
2203 })
2204 .expect("must send");
2205 return;
2206 }
2207 Ok(_) => (response, action),
2208 Err(err) => (Err(err), EndTransactionAction::Rollback),
2209 };
2210 let changed = ctx.session_mut().vars_mut().end_transaction(action);
2211 let response = response.map(|mut r| {
2213 r.extend_params(changed);
2214 ExecuteResponse::from(r)
2215 });
2216
2217 ctx.retire(response);
2218 }
2219
2220 #[instrument]
2221 async fn sequence_end_transaction_inner(
2222 &mut self,
2223 ctx: &mut ExecuteContext,
2224 action: EndTransactionAction,
2225 ) -> Result<Option<TransactionOps>, AdapterError> {
2226 let (txn, retire_notify) = self.clear_transaction(ctx.session_mut()).await;
2227 ctx.delay_response_until(retire_notify);
2228
2229 if let EndTransactionAction::Commit = action {
2230 if let Some(mut ops) = txn.into_ops() {
2231 match &mut ops {
2232 TransactionOps::Writes(writes) => {
2233 for WriteOp { id, .. } in &mut writes.iter() {
2234 let _ = self.catalog().try_get_entry(id).ok_or_else(|| {
2236 AdapterError::Catalog(mz_catalog::memory::error::Error {
2237 kind: ErrorKind::Sql(CatalogError::UnknownItem(id.to_string())),
2238 })
2239 })?;
2240 }
2241
2242 writes.retain(|WriteOp { rows, .. }| !rows.is_empty());
2244 }
2245 TransactionOps::DDL {
2246 ops,
2247 state: _,
2248 side_effects,
2249 revision,
2250 snapshot: _,
2251 } => {
2252 if *revision != self.catalog().transient_revision() {
2254 return Err(AdapterError::DDLTransactionRace);
2255 }
2256 let ops = std::mem::take(ops);
2258 let side_effects = std::mem::take(side_effects);
2259 self.catalog_transact_with_side_effects(
2260 Some(ctx),
2261 ops,
2262 move |a, mut ctx| {
2263 Box::pin(async move {
2264 for side_effect in side_effects {
2265 side_effect(a, ctx.as_mut().map(|ctx| &mut **ctx)).await;
2266 }
2267 })
2268 },
2269 )
2270 .await?;
2271 }
2272 _ => (),
2273 }
2274 return Ok(Some(ops));
2275 }
2276 }
2277
2278 Ok(None)
2279 }
2280
2281 pub(crate) async fn execute_side_effecting_func(
2289 &mut self,
2290 plan: SideEffectingFunc,
2291 conn_id: ConnectionId,
2292 ) -> Result<ExecuteResponse, AdapterError> {
2293 match plan {
2294 SideEffectingFunc::PgCancelBackend { connection_id } => {
2295 let Some(connection_id) = connection_id else {
2296 return Ok(Self::send_immediate_rows(Row::pack_slice(&[Datum::Null])));
2299 };
2300
2301 if conn_id.unhandled() == connection_id {
2302 return Err(AdapterError::Canceled);
2306 }
2307
2308 if let Some((id_handle, _conn_meta)) =
2313 self.active_conns.get_key_value(&connection_id)
2314 {
2315 self.handle_privileged_cancel(id_handle.clone()).await;
2316 Ok(Self::send_immediate_rows(Row::pack_slice(&[Datum::True])))
2317 } else {
2318 Ok(Self::send_immediate_rows(Row::pack_slice(&[Datum::False])))
2320 }
2321 }
2322 }
2323 }
2324
2325 pub(crate) async fn determine_real_time_recent_timestamp(
2329 &self,
2330 source_ids: impl Iterator<Item = GlobalId>,
2331 real_time_recency_timeout: Duration,
2332 ) -> Result<Option<RtrTimestampFuture>, AdapterError> {
2333 let item_ids = source_ids
2334 .map(|gid| {
2335 self.catalog
2336 .try_resolve_item_id(&gid)
2337 .ok_or_else(|| AdapterError::RtrDropFailure(gid.to_string()))
2338 })
2339 .collect::<Result<Vec<_>, _>>()?;
2340
2341 let mut to_visit = VecDeque::from_iter(item_ids.into_iter().filter(CatalogItemId::is_user));
2347 if to_visit.is_empty() {
2350 return Ok(None);
2351 }
2352
2353 let mut timestamp_objects = BTreeSet::new();
2354
2355 while let Some(id) = to_visit.pop_front() {
2356 timestamp_objects.insert(id);
2357 to_visit.extend(
2358 self.catalog()
2359 .get_entry(&id)
2360 .uses()
2361 .into_iter()
2362 .filter(|id| !timestamp_objects.contains(id) && id.is_user()),
2363 );
2364 }
2365 let timestamp_objects = timestamp_objects
2366 .into_iter()
2367 .flat_map(|item_id| self.catalog().get_entry(&item_id).global_ids())
2368 .collect();
2369
2370 let r = self
2371 .controller
2372 .determine_real_time_recent_timestamp(timestamp_objects, real_time_recency_timeout)
2373 .await?;
2374
2375 Ok(Some(r))
2376 }
2377
2378 pub(crate) async fn await_real_time_recent_timestamp<F>(
2379 catalog: Arc<Catalog>,
2380 fut: F,
2381 ) -> Result<Timestamp, AdapterError>
2382 where
2383 F: Future<Output = Result<Timestamp, StorageError>>,
2384 {
2385 fut.await
2386 .map_err(|error| Self::real_time_recent_timestamp_error(&catalog, error))
2387 }
2388
2389 fn real_time_recent_timestamp_error(catalog: &Catalog, error: StorageError) -> AdapterError {
2390 let rtr_name = |id: &GlobalId| {
2391 catalog
2392 .try_get_entry_by_global_id(id)
2393 .map(|e| e.name().item.clone())
2394 .unwrap_or_else(|| id.to_string())
2395 };
2396
2397 match error {
2398 StorageError::RtrTimeout(id) => AdapterError::RtrTimeout(rtr_name(&id)),
2399 StorageError::RtrDropFailure(id) => AdapterError::RtrDropFailure(rtr_name(&id)),
2400 error => error.into(),
2401 }
2402 }
2403
2404 pub(crate) async fn determine_real_time_recent_timestamp_if_needed(
2407 &self,
2408 session: &Session,
2409 source_ids: impl Iterator<Item = GlobalId>,
2410 ) -> Result<Option<RtrTimestampFuture>, AdapterError> {
2411 let vars = session.vars();
2412
2413 if vars.real_time_recency()
2414 && vars.transaction_isolation() == &IsolationLevel::StrictSerializable
2415 && !session.contains_read_timestamp()
2416 {
2417 self.determine_real_time_recent_timestamp(source_ids, *vars.real_time_recency_timeout())
2418 .await
2419 } else {
2420 Ok(None)
2421 }
2422 }
2423
2424 #[instrument]
2425 pub(super) async fn sequence_explain_plan(
2426 &mut self,
2427 ctx: ExecuteContext,
2428 plan: plan::ExplainPlanPlan,
2429 target_cluster: TargetCluster,
2430 ) {
2431 match &plan.explainee {
2432 plan::Explainee::Statement(stmt) => match stmt {
2433 plan::ExplaineeStatement::CreateView { .. } => {
2434 self.explain_create_view(ctx, plan).await;
2435 }
2436 plan::ExplaineeStatement::CreateMaterializedView { .. } => {
2437 self.explain_create_materialized_view(ctx, plan).await;
2438 }
2439 plan::ExplaineeStatement::CreateIndex { .. } => {
2440 self.explain_create_index(ctx, plan).await;
2441 }
2442 stmt @ plan::ExplaineeStatement::Select { .. } => {
2443 let msg = format!(
2444 "EXPLAIN of a {} reached the coordinator despite frontend routing",
2445 plan::ExplaineeStatementKind::from(stmt)
2446 );
2447 soft_panic_or_log!("{msg}");
2448 ctx.retire(Err(AdapterError::Internal(msg)));
2449 }
2450 plan::ExplaineeStatement::Subscribe { .. } => {
2451 self.explain_subscribe(ctx, plan, target_cluster).await;
2452 }
2453 },
2454 plan::Explainee::View(_) => {
2455 let result = self.explain_view(&ctx, plan);
2456 ctx.retire(result);
2457 }
2458 plan::Explainee::MaterializedView(_) => {
2459 let result = self.explain_materialized_view(&ctx, plan);
2460 ctx.retire(result);
2461 }
2462 plan::Explainee::Index(_) => {
2463 let result = self.explain_index(&ctx, plan);
2464 ctx.retire(result);
2465 }
2466 plan::Explainee::ReplanView(_) => {
2467 self.explain_replan_view(ctx, plan).await;
2468 }
2469 plan::Explainee::ReplanMaterializedView(_) => {
2470 self.explain_replan_materialized_view(ctx, plan).await;
2471 }
2472 plan::Explainee::ReplanIndex(_) => {
2473 self.explain_replan_index(ctx, plan).await;
2474 }
2475 };
2476 }
2477
2478 pub(super) async fn sequence_explain_pushdown(
2479 &self,
2480 ctx: ExecuteContext,
2481 plan: plan::ExplainPushdownPlan,
2482 ) {
2483 match plan.explainee {
2484 Explainee::Statement(ExplaineeStatement::Select { broken: false, .. }) => {
2485 let msg = "EXPLAIN FILTER PUSHDOWN of a SELECT reached the coordinator despite \
2486 frontend routing";
2487 soft_panic_or_log!("{msg}");
2488 ctx.retire(Err(AdapterError::Internal(msg.into())));
2489 }
2490 Explainee::MaterializedView(item_id) => {
2491 self.explain_pushdown_materialized_view(ctx, item_id).await;
2492 }
2493 _ => {
2494 ctx.retire(Err(AdapterError::Unsupported(
2495 "EXPLAIN FILTER PUSHDOWN queries for this explainee type",
2496 )));
2497 }
2498 };
2499 }
2500
2501 async fn execute_explain_pushdown_with_read_holds(
2503 &self,
2504 ctx: ExecuteContext,
2505 as_of: Antichain<Timestamp>,
2506 mz_now: ResultSpec<'static>,
2507 read_holds: Option<ReadHolds>,
2508 imports: impl IntoIterator<Item = (GlobalId, MapFilterProject)> + 'static,
2509 ) {
2510 let fut = self
2511 .explain_pushdown_future(ctx.session(), as_of, mz_now, imports)
2512 .await;
2513 task::spawn(|| "render explain pushdown", async move {
2514 let _read_holds = read_holds;
2516 let res = fut.await;
2517 ctx.retire(res);
2518 });
2519 }
2520
2521 async fn explain_pushdown_future<I: IntoIterator<Item = (GlobalId, MapFilterProject)>>(
2523 &self,
2524 session: &Session,
2525 as_of: Antichain<Timestamp>,
2526 mz_now: ResultSpec<'static>,
2527 imports: I,
2528 ) -> impl Future<Output = Result<ExecuteResponse, AdapterError>> + use<I> {
2529 super::explain_pushdown_future_inner(
2531 session,
2532 &self.catalog,
2533 &self.controller.storage_collections,
2534 as_of,
2535 mz_now,
2536 imports,
2537 )
2538 .await
2539 }
2540
2541 #[instrument]
2542 pub(super) async fn sequence_alter_item_rename(
2543 &mut self,
2544 ctx: &mut ExecuteContext,
2545 plan: plan::AlterItemRenamePlan,
2546 ) -> Result<ExecuteResponse, AdapterError> {
2547 let op = catalog::Op::RenameItem {
2548 id: plan.id,
2549 current_full_name: plan.current_full_name,
2550 to_name: plan.to_name,
2551 };
2552 match self
2553 .catalog_transact_with_ddl_transaction(ctx, vec![op], |_, _| Box::pin(async {}))
2554 .await
2555 {
2556 Ok(()) => Ok(ExecuteResponse::AlteredObject(plan.object_type)),
2557 Err(err) => Err(err),
2558 }
2559 }
2560
2561 #[instrument]
2562 pub(super) async fn sequence_alter_retain_history(
2563 &mut self,
2564 ctx: &mut ExecuteContext,
2565 plan: plan::AlterRetainHistoryPlan,
2566 ) -> Result<ExecuteResponse, AdapterError> {
2567 let ops = vec![catalog::Op::AlterRetainHistory {
2568 id: plan.id,
2569 value: plan.value,
2570 window: plan.window,
2571 }];
2572 self.catalog_transact_with_context(None, Some(ctx), ops)
2573 .await?;
2574 Ok(ExecuteResponse::AlteredObject(plan.object_type))
2575 }
2576
2577 #[instrument]
2578 pub(super) async fn sequence_alter_source_timestamp_interval(
2579 &mut self,
2580 ctx: &mut ExecuteContext,
2581 plan: plan::AlterSourceTimestampIntervalPlan,
2582 ) -> Result<ExecuteResponse, AdapterError> {
2583 let ops = vec![catalog::Op::AlterSourceTimestampInterval {
2584 id: plan.id,
2585 value: plan.value,
2586 interval: plan.interval,
2587 }];
2588 self.catalog_transact_with_context(None, Some(ctx), ops)
2589 .await?;
2590 Ok(ExecuteResponse::AlteredObject(ObjectType::Source))
2591 }
2592
2593 #[instrument]
2594 pub(super) async fn sequence_alter_schema_rename(
2595 &mut self,
2596 ctx: &mut ExecuteContext,
2597 plan: plan::AlterSchemaRenamePlan,
2598 ) -> Result<ExecuteResponse, AdapterError> {
2599 let (database_spec, schema_spec) = plan.cur_schema_spec;
2600 let op = catalog::Op::RenameSchema {
2601 database_spec,
2602 schema_spec,
2603 new_name: plan.new_schema_name,
2604 check_reserved_names: true,
2605 };
2606 match self
2607 .catalog_transact_with_ddl_transaction(ctx, vec![op], |_, _| Box::pin(async {}))
2608 .await
2609 {
2610 Ok(()) => Ok(ExecuteResponse::AlteredObject(ObjectType::Schema)),
2611 Err(err) => Err(err),
2612 }
2613 }
2614
2615 #[instrument]
2616 pub(super) async fn sequence_alter_schema_swap(
2617 &mut self,
2618 ctx: &mut ExecuteContext,
2619 plan: plan::AlterSchemaSwapPlan,
2620 ) -> Result<ExecuteResponse, AdapterError> {
2621 let plan::AlterSchemaSwapPlan {
2622 schema_a_spec: (schema_a_db, schema_a),
2623 schema_a_name,
2624 schema_b_spec: (schema_b_db, schema_b),
2625 schema_b_name,
2626 name_temp,
2627 } = plan;
2628
2629 let op_a = catalog::Op::RenameSchema {
2630 database_spec: schema_a_db,
2631 schema_spec: schema_a,
2632 new_name: name_temp,
2633 check_reserved_names: false,
2634 };
2635 let op_b = catalog::Op::RenameSchema {
2636 database_spec: schema_b_db,
2637 schema_spec: schema_b,
2638 new_name: schema_a_name,
2639 check_reserved_names: false,
2640 };
2641 let op_c = catalog::Op::RenameSchema {
2642 database_spec: schema_a_db,
2643 schema_spec: schema_a,
2644 new_name: schema_b_name,
2645 check_reserved_names: false,
2646 };
2647
2648 match self
2649 .catalog_transact_with_ddl_transaction(ctx, vec![op_a, op_b, op_c], |_, _| {
2650 Box::pin(async {})
2651 })
2652 .await
2653 {
2654 Ok(()) => Ok(ExecuteResponse::AlteredObject(ObjectType::Schema)),
2655 Err(err) => Err(err),
2656 }
2657 }
2658
2659 #[instrument]
2660 pub(super) async fn sequence_alter_role(
2661 &mut self,
2662 session: &Session,
2663 plan::AlterRolePlan { id, name, option }: plan::AlterRolePlan,
2664 ) -> Result<ExecuteResponse, AdapterError> {
2665 let catalog = self.catalog().for_session(session);
2666 let role = catalog.get_role(&id);
2667
2668 let mut notices = vec![];
2670
2671 let mut attributes: RoleAttributesRaw = role.attributes().clone().into();
2673 let mut vars = role.vars().clone();
2674
2675 let mut nopassword = false;
2678
2679 match option {
2681 PlannedAlterRoleOption::Attributes(attrs) => {
2682 self.validate_role_attributes(&attrs.clone().into())?;
2683
2684 if let Some(inherit) = attrs.inherit {
2685 attributes.inherit = inherit;
2686 }
2687
2688 if let Some(password) = attrs.password {
2689 attributes.password = Some(password);
2690 attributes.scram_iterations =
2691 Some(self.catalog().system_config().scram_iterations())
2692 }
2693
2694 if let Some(superuser) = attrs.superuser {
2695 attributes.superuser = Some(superuser);
2696 }
2697
2698 if let Some(login) = attrs.login {
2699 attributes.login = Some(login);
2700 }
2701
2702 if attrs.nopassword.unwrap_or(false) {
2703 nopassword = true;
2704 }
2705
2706 if let Some(notice) = self.should_emit_rbac_notice(session) {
2707 notices.push(notice);
2708 }
2709 }
2710 PlannedAlterRoleOption::Variable(variable) => {
2711 let session_var = session.vars().inspect(variable.name())?;
2713 session_var.visible(session.user(), catalog.system_vars())?;
2715
2716 if variable.name() == vars::OLD_AUTO_ROUTE_CATALOG_QUERIES {
2719 notices.push(AdapterNotice::AutoRouteIntrospectionQueriesUsage);
2720 } else if let PlannedRoleVariable::Set {
2721 name,
2722 value: VariableValue::Values(vals),
2723 } = &variable
2724 {
2725 if name == vars::CLUSTER.name() && vals[0] == vars::OLD_CATALOG_SERVER_CLUSTER {
2726 notices.push(AdapterNotice::IntrospectionClusterUsage);
2727 }
2728 }
2729
2730 let var_name = match variable {
2731 PlannedRoleVariable::Set { name, value } => {
2732 match &value {
2734 VariableValue::Default => {
2735 vars.remove(&name);
2736 }
2737 VariableValue::Values(vals) => {
2738 let var = match &vals[..] {
2739 [val] => OwnedVarInput::Flat(val.clone()),
2740 vals => OwnedVarInput::SqlSet(vals.to_vec()),
2741 };
2742 session_var.check(var.borrow())?;
2744
2745 vars.insert(name.clone(), var);
2746 }
2747 };
2748 name
2749 }
2750 PlannedRoleVariable::Reset { name } => {
2751 vars.remove(&name);
2753 name
2754 }
2755 };
2756
2757 notices.push(AdapterNotice::VarDefaultUpdated {
2759 role: Some(name.clone()),
2760 var_name: Some(var_name),
2761 });
2762 }
2763 }
2764
2765 let op = catalog::Op::AlterRole {
2766 id,
2767 name,
2768 attributes,
2769 nopassword,
2770 vars: RoleVars { map: vars },
2771 };
2772 let response = self
2773 .catalog_transact(Some(session), vec![op])
2774 .await
2775 .map(|_| ExecuteResponse::AlteredRole)?;
2776
2777 session.add_notices(notices);
2779
2780 Ok(response)
2781 }
2782
2783 #[instrument]
2784 pub(super) async fn sequence_alter_sink_prepare(
2785 &mut self,
2786 ctx: ExecuteContext,
2787 plan: plan::AlterSinkPlan,
2788 ) {
2789 let id_bundle = crate::CollectionIdBundle {
2791 storage_ids: BTreeSet::from_iter([plan.sink.from]),
2792 compute_ids: BTreeMap::new(),
2793 };
2794 let read_hold = self.acquire_read_holds(&id_bundle);
2795
2796 let Some(read_ts) = read_hold.least_valid_read().into_option() else {
2797 ctx.retire(Err(AdapterError::UnreadableSinkCollection));
2798 return;
2799 };
2800
2801 let otel_ctx = OpenTelemetryContext::obtain();
2802 let from_item_id = self.catalog().resolve_item_id(&plan.sink.from);
2803
2804 let plan_validity = PlanValidity::new(
2805 self.catalog(),
2806 BTreeSet::from_iter([plan.item_id, from_item_id]),
2807 Some(plan.in_cluster),
2808 None,
2809 ctx.session().role_metadata().clone(),
2810 );
2811
2812 info!(
2813 "preparing alter sink for {}: frontiers={:?} export={:?}",
2814 plan.global_id,
2815 self.controller
2816 .storage_collections
2817 .collections_frontiers(vec![plan.global_id, plan.sink.from]),
2818 self.controller.storage.export(plan.global_id)
2819 );
2820
2821 self.install_storage_watch_set(
2827 ctx.session().conn_id().clone(),
2828 BTreeSet::from_iter([plan.global_id]),
2829 read_ts,
2830 WatchSetResponse::AlterSinkReady(AlterSinkReadyContext {
2831 ctx: Some(ctx),
2832 otel_ctx,
2833 plan,
2834 plan_validity,
2835 read_hold,
2836 }),
2837 ).expect("plan validity verified above; we are on the coordinator main task, so they couldn't have gone away since then");
2838 }
2839
2840 #[instrument]
2841 pub async fn sequence_alter_sink_finish(&mut self, mut ctx: AlterSinkReadyContext) {
2842 ctx.otel_ctx.attach_as_parent();
2843
2844 let plan::AlterSinkPlan {
2845 item_id,
2846 global_id,
2847 sink: sink_plan,
2848 with_snapshot,
2849 in_cluster,
2850 set_options,
2851 reset_options,
2852 } = ctx.plan.clone();
2853
2854 match ctx.plan_validity.check(self.catalog()) {
2863 Ok(()) => {}
2864 Err(err) => {
2865 ctx.retire(Err(err));
2866 return;
2867 }
2868 }
2869
2870 let entry = self.catalog().get_entry(&item_id);
2871 let CatalogItem::Sink(old_sink) = entry.item() else {
2872 panic!("invalid item kind for `AlterSinkPlan`");
2873 };
2874
2875 if sink_plan.version != old_sink.version + 1 {
2876 ctx.retire(Err(AdapterError::ChangedPlan(
2877 "sink was altered concurrently".into(),
2878 )));
2879 return;
2880 }
2881
2882 info!(
2883 "finishing alter sink for {global_id}: frontiers={:?} export={:?}",
2884 self.controller
2885 .storage_collections
2886 .collections_frontiers(vec![global_id, sink_plan.from]),
2887 self.controller.storage.export(global_id),
2888 );
2889
2890 let write_frontier = &self
2893 .controller
2894 .storage
2895 .export(global_id)
2896 .expect("sink known to exist")
2897 .write_frontier;
2898 let as_of = ctx.read_hold.least_valid_read();
2899 assert!(
2900 write_frontier.iter().all(|t| as_of.less_than(t)),
2901 "{:?} should be strictly less than {:?}",
2902 &*as_of,
2903 &**write_frontier
2904 );
2905
2906 let create_sql = &old_sink.create_sql;
2912 let parsed = mz_sql::parse::parse(create_sql).expect("valid create_sql");
2913 let Statement::CreateSink(mut stmt) = parsed.into_element().ast else {
2914 unreachable!("invalid statement kind for sink");
2915 };
2916
2917 plan::apply_sink_option_edits(
2919 &mut stmt.with_options,
2920 &[CreateSinkOption {
2921 name: CreateSinkOptionName::Version,
2922 value: Some(WithOptionValue::Value(mz_sql::ast::Value::Number(
2923 sink_plan.version.to_string(),
2924 ))),
2925 }],
2926 &[],
2927 );
2928
2929 let conn_catalog = self.catalog().for_system_session();
2930 let (mut stmt, resolved_ids) =
2931 mz_sql::names::resolve(&conn_catalog, stmt).expect("resolvable create_sql");
2932
2933 plan::apply_sink_option_edits(&mut stmt.with_options, &set_options, &reset_options);
2935
2936 let from_entry = self.catalog().get_entry_by_global_id(&sink_plan.from);
2938 let full_name = self.catalog().resolve_full_name(from_entry.name(), None);
2939 stmt.from = ResolvedItemName::Item {
2940 id: from_entry.id(),
2941 qualifiers: from_entry.name.qualifiers.clone(),
2942 full_name,
2943 print_id: true,
2944 version: from_entry.version,
2945 };
2946
2947 let mut resolved_ids = resolved_ids;
2954 resolved_ids.remove_item(&self.catalog().resolve_item_id(&old_sink.from));
2955 resolved_ids.add_item(from_entry.id());
2956
2957 let new_sink = Sink {
2958 create_sql: stmt.to_ast_string_stable(),
2959 global_id,
2960 from: sink_plan.from,
2961 connection: sink_plan.connection.clone(),
2962 envelope: sink_plan.envelope,
2963 version: sink_plan.version,
2964 with_snapshot,
2965 resolved_ids,
2966 cluster_id: in_cluster,
2967 commit_interval: sink_plan.commit_interval,
2968 };
2969
2970 let ops = vec![catalog::Op::UpdateItem {
2971 id: item_id,
2972 name: entry.name().clone(),
2973 to_item: CatalogItem::Sink(new_sink),
2974 }];
2975
2976 match self
2977 .catalog_transact(Some(ctx.ctx().session_mut()), ops)
2978 .await
2979 {
2980 Ok(()) => {}
2981 Err(err) => {
2982 ctx.retire(Err(err));
2983 return;
2984 }
2985 }
2986
2987 let storage_sink_desc = StorageSinkDesc {
2988 from: sink_plan.from,
2989 from_desc: from_entry
2990 .relation_desc()
2991 .expect("sinks can only be built on items with descs")
2992 .into_owned(),
2993 connection: sink_plan
2994 .connection
2995 .clone()
2996 .into_inline_connection(self.catalog().state()),
2997 envelope: sink_plan.envelope,
2998 as_of,
2999 with_snapshot,
3000 version: sink_plan.version,
3001 from_storage_metadata: (),
3002 to_storage_metadata: (),
3003 commit_interval: sink_plan.commit_interval,
3004 };
3005
3006 self.controller
3007 .storage
3008 .alter_export(
3009 global_id,
3010 ExportDescription {
3011 sink: storage_sink_desc,
3012 instance_id: in_cluster,
3013 },
3014 )
3015 .await
3016 .unwrap_or_terminate("cannot fail to alter source desc");
3017
3018 ctx.retire(Ok(ExecuteResponse::AlteredObject(ObjectType::Sink)));
3019 }
3020
3021 #[instrument]
3022 pub(super) async fn sequence_alter_connection(
3023 &mut self,
3024 ctx: ExecuteContext,
3025 AlterConnectionPlan { id, action }: AlterConnectionPlan,
3026 ) {
3027 match action {
3028 AlterConnectionAction::RotateKeys => {
3029 self.sequence_rotate_keys(ctx, id).await;
3030 }
3031 AlterConnectionAction::AlterOptions {
3032 set_options,
3033 drop_options,
3034 validate,
3035 } => {
3036 self.sequence_alter_connection_options(ctx, id, set_options, drop_options, validate)
3037 .await
3038 }
3039 }
3040 }
3041
3042 #[instrument]
3043 async fn sequence_alter_connection_options(
3044 &mut self,
3045 mut ctx: ExecuteContext,
3046 id: CatalogItemId,
3047 set_options: BTreeMap<ConnectionOptionName, Option<WithOptionValue<mz_sql::names::Aug>>>,
3048 drop_options: BTreeSet<ConnectionOptionName>,
3049 validate: bool,
3050 ) {
3051 let cur_entry = self.catalog().get_entry(&id);
3052 let cur_conn = cur_entry.connection().expect("known to be connection");
3053 let connection_gid = cur_conn.global_id();
3054
3055 let inner = || -> Result<Connection, AdapterError> {
3056 let create_conn_stmt = match mz_sql::parse::parse(&cur_conn.create_sql)
3058 .expect("invalid create sql persisted to catalog")
3059 .into_element()
3060 .ast
3061 {
3062 Statement::CreateConnection(stmt) => stmt,
3063 _ => unreachable!("proved type is source"),
3064 };
3065
3066 let catalog = self.catalog().for_system_session();
3067
3068 let (mut create_conn_stmt, resolved_ids) =
3070 mz_sql::names::resolve(&catalog, create_conn_stmt)
3071 .map_err(|e| AdapterError::internal("ALTER CONNECTION", e))?;
3072
3073 create_conn_stmt
3075 .values
3076 .retain(|o| !set_options.contains_key(&o.name) && !drop_options.contains(&o.name));
3077
3078 create_conn_stmt.values.extend(
3080 set_options
3081 .into_iter()
3082 .map(|(name, value)| ConnectionOption { name, value }),
3083 );
3084
3085 let mut catalog = self.catalog().for_system_session();
3088 catalog.mark_id_unresolvable_for_replanning(id);
3089
3090 let plan = match mz_sql::plan::plan(
3092 None,
3093 &catalog,
3094 Statement::CreateConnection(create_conn_stmt),
3095 &Params::empty(),
3096 &resolved_ids,
3097 )
3098 .map_err(|e| AdapterError::InvalidAlter("CONNECTION", e))?
3099 {
3100 (Plan::CreateConnection(plan), _sql_impl_ids) => plan,
3101 (p, _) => {
3102 unreachable!("create connection plan is only valid response, got {:?}", p)
3103 }
3104 };
3105
3106 let create_conn_stmt = match mz_sql::parse::parse(&plan.connection.create_sql)
3108 .expect("invalid create sql persisted to catalog")
3109 .into_element()
3110 .ast
3111 {
3112 Statement::CreateConnection(stmt) => stmt,
3113 _ => unreachable!("proved type is source"),
3114 };
3115
3116 let catalog = self.catalog().for_system_session();
3117
3118 let (_, new_deps) = mz_sql::names::resolve(&catalog, create_conn_stmt)
3120 .map_err(|e| AdapterError::internal("ALTER CONNECTION", e))?;
3121
3122 Ok(Connection {
3123 create_sql: plan.connection.create_sql,
3124 global_id: cur_conn.global_id,
3125 details: plan.connection.details,
3126 resolved_ids: new_deps,
3127 })
3128 };
3129
3130 let conn = match inner() {
3131 Ok(conn) => conn,
3132 Err(e) => {
3133 return ctx.retire(Err(e));
3134 }
3135 };
3136
3137 let usage_check = {
3141 let catalog = self.catalog().for_session(ctx.session());
3142 rbac::check_usage(
3143 &catalog,
3144 ctx.session(),
3145 &conn.resolved_ids,
3146 &rbac::CREATE_ITEM_USAGE,
3147 )
3148 };
3149 if let Err(err) = usage_check {
3150 return ctx.retire(Err(err.into()));
3151 }
3152
3153 if let Err(err) = check_connection_details(&conn.details) {
3156 return ctx.retire(Err(AdapterError::InvalidAlter("CONNECTION", err)));
3157 }
3158
3159 if let Err(err) = self
3162 .check_connection_secret_content_guards(&conn.details)
3163 .await
3164 {
3165 return ctx.retire(Err(err));
3166 }
3167
3168 if validate {
3169 let connection = conn
3170 .details
3171 .to_connection()
3172 .into_inline_connection(self.catalog().state());
3173
3174 let internal_cmd_tx = self.internal_cmd_tx.clone();
3175 let catalog = self.owned_catalog();
3176 let conn_id = ctx.session().conn_id().clone();
3177 let otel_ctx = OpenTelemetryContext::obtain();
3178 let role_metadata = ctx.session().role_metadata().clone();
3179 let current_storage_parameters = self.controller.storage.config().clone();
3180
3181 task::spawn(
3182 || format!("validate_alter_connection:{conn_id}"),
3183 async move {
3184 let resolved_ids = conn.resolved_ids.clone();
3185 let dependency_ids: BTreeSet<_> = resolved_ids.items().copied().collect();
3186 let result = match std::panic::AssertUnwindSafe(
3187 connection.validate(id, ¤t_storage_parameters),
3188 )
3189 .ore_catch_unwind()
3190 .await
3191 {
3192 Ok(Ok(())) => Ok(conn),
3193 Ok(Err(err)) => Err(err.into()),
3194 Err(_panic) => {
3195 tracing::error!("alter connection validation panicked");
3196 Err(AdapterError::Internal(
3197 "connection validation panicked".into(),
3198 ))
3199 }
3200 };
3201
3202 let result = internal_cmd_tx.send(Message::AlterConnectionValidationReady(
3204 AlterConnectionValidationReady {
3205 ctx,
3206 result,
3207 connection_id: id,
3208 connection_gid,
3209 plan_validity: PlanValidity::new(
3210 &catalog,
3211 dependency_ids.clone(),
3212 None,
3213 None,
3214 role_metadata,
3215 ),
3216 otel_ctx,
3217 resolved_ids,
3218 },
3219 ));
3220 if let Err(e) = result {
3221 tracing::warn!("internal_cmd_rx dropped before we could send: {:?}", e);
3222 }
3223 },
3224 );
3225 } else {
3226 let result = self
3227 .sequence_alter_connection_stage_finish(ctx.session_mut(), id, conn)
3228 .await;
3229 ctx.retire(result);
3230 }
3231 }
3232
3233 #[instrument]
3234 pub(crate) async fn sequence_alter_connection_stage_finish(
3235 &mut self,
3236 session: &Session,
3237 id: CatalogItemId,
3238 connection: Connection,
3239 ) -> Result<ExecuteResponse, AdapterError> {
3240 match self.catalog.get_entry(&id).item() {
3241 CatalogItem::Connection(curr_conn) => {
3242 curr_conn
3243 .details
3244 .to_connection()
3245 .alter_compatible(curr_conn.global_id, &connection.details.to_connection())
3246 .map_err(StorageError::from)?;
3247 }
3248 _ => unreachable!("known to be a connection"),
3249 };
3250
3251 let ops = vec![catalog::Op::UpdateItem {
3252 id,
3253 name: self.catalog.get_entry(&id).name().clone(),
3254 to_item: CatalogItem::Connection(connection.clone()),
3255 }];
3256
3257 self.catalog_transact(Some(session), ops).await?;
3258
3259 Ok(ExecuteResponse::AlteredObject(ObjectType::Connection))
3266 }
3267
3268 #[instrument]
3269 pub(super) async fn sequence_alter_source(
3270 &mut self,
3271 session: &Session,
3272 plan::AlterSourcePlan {
3273 item_id,
3274 ingestion_id,
3275 action,
3276 }: plan::AlterSourcePlan,
3277 ) -> Result<ExecuteResponse, AdapterError> {
3278 let cur_entry = self.catalog().get_entry(&item_id);
3279 let cur_source = cur_entry.source().expect("known to be source");
3280
3281 let create_sql_to_stmt_deps = |coord: &Coordinator, err_cx, create_source_sql| {
3282 let create_source_stmt = match mz_sql::parse::parse(create_source_sql)
3284 .expect("invalid create sql persisted to catalog")
3285 .into_element()
3286 .ast
3287 {
3288 Statement::CreateSource(stmt) => stmt,
3289 _ => unreachable!("proved type is source"),
3290 };
3291
3292 let catalog = coord.catalog().for_system_session();
3293
3294 mz_sql::names::resolve(&catalog, create_source_stmt)
3296 .map_err(|e| AdapterError::internal(err_cx, e))
3297 };
3298
3299 match action {
3300 plan::AlterSourceAction::AddSubsourceExports {
3301 subsources,
3302 options,
3303 } => {
3304 const ALTER_SOURCE: &str = "ALTER SOURCE...ADD SUBSOURCES";
3305
3306 let mz_sql::plan::AlterSourceAddSubsourceOptionExtracted {
3307 text_columns: mut new_text_columns,
3308 exclude_columns: mut new_exclude_columns,
3309 ..
3310 } = options.try_into()?;
3311
3312 let (mut create_source_stmt, resolved_ids) =
3314 create_sql_to_stmt_deps(self, ALTER_SOURCE, cur_entry.create_sql())?;
3315
3316 let catalog = self.catalog();
3318 let curr_references: BTreeSet<_> = catalog
3319 .get_entry(&item_id)
3320 .used_by()
3321 .into_iter()
3322 .filter_map(|subsource| {
3323 catalog
3324 .get_entry(subsource)
3325 .subsource_details()
3326 .map(|(_id, reference, _details)| reference)
3327 })
3328 .collect();
3329
3330 let purification_err =
3333 || AdapterError::internal(ALTER_SOURCE, "error in subsource purification");
3334
3335 match &mut create_source_stmt.connection {
3339 CreateSourceConnection::Postgres {
3340 options: curr_options,
3341 ..
3342 } => {
3343 let mz_sql::plan::PgConfigOptionExtracted {
3344 mut text_columns, ..
3345 } = curr_options.clone().try_into()?;
3346
3347 curr_options.retain(|o| !matches!(o.name, PgConfigOptionName::TextColumns));
3350
3351 text_columns.retain(|column_qualified_reference| {
3353 mz_ore::soft_assert_eq_or_log!(
3354 column_qualified_reference.0.len(),
3355 4,
3356 "all TEXT COLUMNS values must be column-qualified references"
3357 );
3358 let mut table = column_qualified_reference.clone();
3359 table.0.truncate(3);
3360 curr_references.contains(&table)
3361 });
3362
3363 new_text_columns.extend(text_columns);
3365
3366 if !new_text_columns.is_empty() {
3368 new_text_columns.sort();
3369 let new_text_columns = new_text_columns
3370 .into_iter()
3371 .map(WithOptionValue::UnresolvedItemName)
3372 .collect();
3373
3374 curr_options.push(PgConfigOption {
3375 name: PgConfigOptionName::TextColumns,
3376 value: Some(WithOptionValue::Sequence(new_text_columns)),
3377 });
3378 }
3379 }
3380 CreateSourceConnection::MySql {
3381 options: curr_options,
3382 ..
3383 } => {
3384 let mz_sql::plan::MySqlConfigOptionExtracted {
3385 mut text_columns,
3386 mut exclude_columns,
3387 ..
3388 } = curr_options.clone().try_into()?;
3389
3390 curr_options.retain(|o| {
3393 !matches!(
3394 o.name,
3395 MySqlConfigOptionName::TextColumns
3396 | MySqlConfigOptionName::ExcludeColumns
3397 )
3398 });
3399
3400 let column_referenced =
3402 |column_qualified_reference: &UnresolvedItemName| {
3403 mz_ore::soft_assert_eq_or_log!(
3404 column_qualified_reference.0.len(),
3405 3,
3406 "all TEXT COLUMNS & EXCLUDE COLUMNS values must be column-qualified references"
3407 );
3408 let mut table = column_qualified_reference.clone();
3409 table.0.truncate(2);
3410 curr_references.contains(&table)
3411 };
3412 text_columns.retain(column_referenced);
3413 exclude_columns.retain(column_referenced);
3414
3415 new_text_columns.extend(text_columns);
3417 new_exclude_columns.extend(exclude_columns);
3418
3419 if !new_text_columns.is_empty() {
3421 new_text_columns.sort();
3422 let new_text_columns = new_text_columns
3423 .into_iter()
3424 .map(WithOptionValue::UnresolvedItemName)
3425 .collect();
3426
3427 curr_options.push(MySqlConfigOption {
3428 name: MySqlConfigOptionName::TextColumns,
3429 value: Some(WithOptionValue::Sequence(new_text_columns)),
3430 });
3431 }
3432 if !new_exclude_columns.is_empty() {
3434 new_exclude_columns.sort();
3435 let new_exclude_columns = new_exclude_columns
3436 .into_iter()
3437 .map(WithOptionValue::UnresolvedItemName)
3438 .collect();
3439
3440 curr_options.push(MySqlConfigOption {
3441 name: MySqlConfigOptionName::ExcludeColumns,
3442 value: Some(WithOptionValue::Sequence(new_exclude_columns)),
3443 });
3444 }
3445 }
3446 CreateSourceConnection::SqlServer {
3447 options: curr_options,
3448 ..
3449 } => {
3450 let mz_sql::plan::SqlServerConfigOptionExtracted {
3451 mut text_columns,
3452 mut exclude_columns,
3453 ..
3454 } = curr_options.clone().try_into()?;
3455
3456 curr_options.retain(|o| {
3459 !matches!(
3460 o.name,
3461 SqlServerConfigOptionName::TextColumns
3462 | SqlServerConfigOptionName::ExcludeColumns
3463 )
3464 });
3465
3466 let column_referenced =
3472 |column_qualified_reference: &UnresolvedItemName| {
3473 mz_ore::soft_assert_eq_or_log!(
3474 column_qualified_reference.0.len(),
3475 3,
3476 "all TEXT COLUMNS & EXCLUDE COLUMNS values must be column-qualified references"
3477 );
3478 let mut table = column_qualified_reference.clone();
3479 table.0.truncate(2);
3480 curr_references.iter().any(|r| r.0.ends_with(&table.0))
3481 };
3482 text_columns.retain(column_referenced);
3483 exclude_columns.retain(column_referenced);
3484
3485 new_text_columns.extend(text_columns);
3487 new_exclude_columns.extend(exclude_columns);
3488
3489 if !new_text_columns.is_empty() {
3491 new_text_columns.sort();
3492 let new_text_columns = new_text_columns
3493 .into_iter()
3494 .map(WithOptionValue::UnresolvedItemName)
3495 .collect();
3496
3497 curr_options.push(SqlServerConfigOption {
3498 name: SqlServerConfigOptionName::TextColumns,
3499 value: Some(WithOptionValue::Sequence(new_text_columns)),
3500 });
3501 }
3502 if !new_exclude_columns.is_empty() {
3504 new_exclude_columns.sort();
3505 let new_exclude_columns = new_exclude_columns
3506 .into_iter()
3507 .map(WithOptionValue::UnresolvedItemName)
3508 .collect();
3509
3510 curr_options.push(SqlServerConfigOption {
3511 name: SqlServerConfigOptionName::ExcludeColumns,
3512 value: Some(WithOptionValue::Sequence(new_exclude_columns)),
3513 });
3514 }
3515 }
3516 _ => return Err(purification_err()),
3517 };
3518
3519 let mut catalog = self.catalog().for_system_session();
3520 catalog.mark_id_unresolvable_for_replanning(cur_entry.id());
3521
3522 let planned = mz_sql::plan::plan(
3524 None,
3525 &catalog,
3526 Statement::CreateSource(create_source_stmt),
3527 &Params::empty(),
3528 &resolved_ids,
3529 )
3530 .map_err(|e| AdapterError::internal(ALTER_SOURCE, e))?;
3531 let plan = match planned {
3532 (Plan::CreateSource(plan), _sql_impl_ids) => plan,
3533 (p, _) => {
3534 unreachable!("create source plan is only valid response, got {:?}", p)
3535 }
3536 };
3537
3538 let source = Source::new(
3542 plan,
3543 cur_source.global_id,
3544 resolved_ids,
3545 cur_source.custom_logical_compaction_window,
3546 cur_source.is_retained_metrics_object,
3547 );
3548
3549 let desc = match &source.data_source {
3551 DataSourceDesc::Ingestion { desc, .. }
3552 | DataSourceDesc::OldSyntaxIngestion { desc, .. } => {
3553 desc.clone().into_inline_connection(self.catalog().state())
3554 }
3555 _ => unreachable!("already verified of type ingestion"),
3556 };
3557
3558 self.controller
3559 .storage
3560 .check_alter_ingestion_source_desc(ingestion_id, &desc)
3561 .map_err(|e| AdapterError::internal(ALTER_SOURCE, e))?;
3562
3563 let mut ops = vec![catalog::Op::UpdateItem {
3566 id: item_id,
3567 name: self.catalog.get_entry(&item_id).name().clone(),
3570 to_item: CatalogItem::Source(source),
3571 }];
3572
3573 let CreateSourceInner {
3574 ops: new_ops,
3575 sources: _,
3576 if_not_exists_ids,
3577 } = self.create_source_inner(session, subsources).await?;
3578
3579 ops.extend(new_ops.into_iter());
3580
3581 assert!(
3582 if_not_exists_ids.is_empty(),
3583 "IF NOT EXISTS not supported for ALTER SOURCE...ADD SUBSOURCES"
3584 );
3585
3586 self.catalog_transact(Some(session), ops).await?;
3587 }
3588 plan::AlterSourceAction::RefreshReferences { references } => {
3589 self.catalog_transact(
3590 Some(session),
3591 vec![catalog::Op::UpdateSourceReferences {
3592 source_id: item_id,
3593 references: references.into(),
3594 }],
3595 )
3596 .await?;
3597 }
3598 }
3599
3600 Ok(ExecuteResponse::AlteredObject(ObjectType::Source))
3601 }
3602
3603 #[instrument]
3604 pub(super) async fn sequence_alter_system_set(
3605 &mut self,
3606 session: &Session,
3607 plan::AlterSystemSetPlan { name, value }: plan::AlterSystemSetPlan,
3608 ) -> Result<ExecuteResponse, AdapterError> {
3609 self.is_user_allowed_to_alter_system(session, Some(&name))?;
3610 if NETWORK_POLICY.name.to_string().to_lowercase() == name.clone().to_lowercase() {
3612 self.validate_alter_system_network_policy(session, &value)?;
3613 }
3614
3615 let op = match value {
3616 plan::VariableValue::Values(values) => catalog::Op::UpdateSystemConfiguration {
3617 name: name.clone(),
3618 value: OwnedVarInput::SqlSet(values),
3619 },
3620 plan::VariableValue::Default => {
3621 catalog::Op::ResetSystemConfiguration { name: name.clone() }
3622 }
3623 };
3624 self.catalog_transact(Some(session), vec![op]).await?;
3625
3626 Self::notice_if_startup_only(session, &name);
3627 session.add_notice(AdapterNotice::VarDefaultUpdated {
3628 role: None,
3629 var_name: Some(name),
3630 });
3631 Ok(ExecuteResponse::AlteredSystemConfiguration)
3632 }
3633
3634 #[instrument]
3635 pub(super) async fn sequence_alter_system_reset(
3636 &mut self,
3637 session: &Session,
3638 plan::AlterSystemResetPlan { name }: plan::AlterSystemResetPlan,
3639 ) -> Result<ExecuteResponse, AdapterError> {
3640 self.is_user_allowed_to_alter_system(session, Some(&name))?;
3641 let op = catalog::Op::ResetSystemConfiguration { name: name.clone() };
3642 self.catalog_transact(Some(session), vec![op]).await?;
3643 Self::notice_if_startup_only(session, &name);
3644 session.add_notice(AdapterNotice::VarDefaultUpdated {
3645 role: None,
3646 var_name: Some(name),
3647 });
3648 Ok(ExecuteResponse::AlteredSystemConfiguration)
3649 }
3650
3651 #[instrument]
3652 pub(super) async fn sequence_alter_system_reset_all(
3653 &mut self,
3654 session: &Session,
3655 _: plan::AlterSystemResetAllPlan,
3656 ) -> Result<ExecuteResponse, AdapterError> {
3657 self.is_user_allowed_to_alter_system(session, None)?;
3658 let startup_only_changed = self.startup_only_vars_changed_by_reset_all();
3661 let op = catalog::Op::ResetAllSystemConfiguration;
3662 self.catalog_transact(Some(session), vec![op]).await?;
3663 for name in startup_only_changed {
3664 session.add_notice(AdapterNotice::StartupOnlyVarUpdated {
3665 var_name: name.to_string(),
3666 });
3667 }
3668 session.add_notice(AdapterNotice::VarDefaultUpdated {
3669 role: None,
3670 var_name: None,
3671 });
3672 Ok(ExecuteResponse::AlteredSystemConfiguration)
3673 }
3674
3675 fn startup_only_vars() -> [&'static str; 2] {
3685 [
3686 MAX_CONCURRENT_OCC_WRITES.name(),
3687 ENABLE_EXPRESSION_CACHE.name(),
3688 ]
3689 }
3690
3691 fn notice_if_startup_only(session: &Session, name: &str) {
3694 if Self::startup_only_vars()
3695 .iter()
3696 .any(|n| n.eq_ignore_ascii_case(name))
3697 {
3698 session.add_notice(AdapterNotice::StartupOnlyVarUpdated {
3699 var_name: name.to_string(),
3700 });
3701 }
3702 }
3703
3704 fn startup_only_vars_changed_by_reset_all(&self) -> Vec<&'static str> {
3708 let config = self.catalog().system_config();
3712 let defaults = config.defaults();
3713 Self::startup_only_vars()
3714 .into_iter()
3715 .filter(|name| {
3716 let current = config
3719 .get(name)
3720 .expect("startup-only parameter is a registered system var")
3721 .value();
3722 defaults
3723 .get(*name)
3724 .is_some_and(|default| default != ¤t)
3725 })
3726 .collect()
3727 }
3728
3729 fn is_user_allowed_to_alter_system(
3731 &self,
3732 session: &Session,
3733 var_name: Option<&str>,
3734 ) -> Result<(), AdapterError> {
3735 match (session.user().kind(), var_name) {
3736 (UserKind::Superuser, None) if session.user().is_internal() => Ok(()),
3738 (UserKind::Superuser, Some(name))
3740 if session.user().is_internal()
3741 || self.catalog().system_config().user_modifiable(name) =>
3742 {
3743 let var = self.catalog().system_config().get(name)?;
3746 var.visible(session.user(), self.catalog().system_config())?;
3747 Ok(())
3748 }
3749 (UserKind::Regular, Some(name))
3752 if self.catalog().system_config().user_modifiable(name) =>
3753 {
3754 Err(AdapterError::Unauthorized(
3755 rbac::UnauthorizedError::Superuser {
3756 action: format!("toggle the '{name}' system configuration parameter"),
3757 },
3758 ))
3759 }
3760 _ => Err(AdapterError::Unauthorized(
3761 rbac::UnauthorizedError::MzSystem {
3762 action: "alter system".into(),
3763 },
3764 )),
3765 }
3766 }
3767
3768 fn validate_alter_system_network_policy(
3769 &self,
3770 session: &Session,
3771 policy_value: &plan::VariableValue,
3772 ) -> Result<(), AdapterError> {
3773 let policy_name = match &policy_value {
3774 plan::VariableValue::Default => Some(NETWORK_POLICY.default_value().format()),
3776 plan::VariableValue::Values(values) if values.len() == 1 => {
3777 values.iter().next().cloned()
3778 }
3779 plan::VariableValue::Values(values) => {
3780 tracing::warn!(?values, "can't set multiple network policies at once");
3781 None
3782 }
3783 };
3784 let maybe_network_policy = policy_name
3785 .as_ref()
3786 .and_then(|name| self.catalog.get_network_policy_by_name(name));
3787 let Some(network_policy) = maybe_network_policy else {
3788 return Err(AdapterError::PlanError(plan::PlanError::VarError(
3789 VarError::InvalidParameterValue {
3790 name: NETWORK_POLICY.name(),
3791 invalid_values: vec![policy_name.unwrap_or_else(|| "<none>".to_string())],
3792 reason: "no network policy with such name exists".to_string(),
3793 },
3794 )));
3795 };
3796 self.validate_alter_network_policy(session, &network_policy.rules)
3797 }
3798
3799 fn validate_alter_network_policy(
3804 &self,
3805 session: &Session,
3806 policy_rules: &Vec<NetworkPolicyRule>,
3807 ) -> Result<(), AdapterError> {
3808 if session.user().is_internal() {
3811 return Ok(());
3812 }
3813 if let Some(ip) = session.meta().client_ip() {
3814 validate_ip_with_policy_rules(ip, policy_rules)
3815 .map_err(|_| AdapterError::PlanError(plan::PlanError::NetworkPolicyLockoutError))?;
3816 } else {
3817 return Err(AdapterError::NetworkPolicyDenied(
3820 NetworkPolicyError::MissingIp,
3821 ));
3822 }
3823 Ok(())
3824 }
3825
3826 #[instrument]
3828 pub(super) fn sequence_execute(
3829 &self,
3830 session: &mut Session,
3831 plan: plan::ExecutePlan,
3832 ) -> Result<String, AdapterError> {
3833 Self::verify_prepared_statement(self.catalog(), session, &plan.name)?;
3835 let ps = session
3836 .get_prepared_statement_unverified(&plan.name)
3837 .expect("known to exist");
3838 let stmt = ps.stmt().cloned();
3839 let desc = ps.desc().clone();
3840 let state_revision = ps.state_revision;
3841 let logging = Arc::clone(ps.logging());
3842 session.create_new_portal(stmt, logging, desc, plan.params, Vec::new(), state_revision)
3843 }
3844
3845 #[instrument]
3846 pub(super) async fn sequence_grant_privileges(
3847 &mut self,
3848 session: &Session,
3849 plan::GrantPrivilegesPlan {
3850 update_privileges,
3851 grantees,
3852 }: plan::GrantPrivilegesPlan,
3853 ) -> Result<ExecuteResponse, AdapterError> {
3854 self.sequence_update_privileges(
3855 session,
3856 update_privileges,
3857 grantees,
3858 UpdatePrivilegeVariant::Grant,
3859 )
3860 .await
3861 }
3862
3863 #[instrument]
3864 pub(super) async fn sequence_revoke_privileges(
3865 &mut self,
3866 session: &Session,
3867 plan::RevokePrivilegesPlan {
3868 update_privileges,
3869 revokees,
3870 }: plan::RevokePrivilegesPlan,
3871 ) -> Result<ExecuteResponse, AdapterError> {
3872 self.sequence_update_privileges(
3873 session,
3874 update_privileges,
3875 revokees,
3876 UpdatePrivilegeVariant::Revoke,
3877 )
3878 .await
3879 }
3880
3881 #[instrument]
3882 async fn sequence_update_privileges(
3883 &mut self,
3884 session: &Session,
3885 update_privileges: Vec<UpdatePrivilege>,
3886 grantees: Vec<RoleId>,
3887 variant: UpdatePrivilegeVariant,
3888 ) -> Result<ExecuteResponse, AdapterError> {
3889 let mut ops = Vec::with_capacity(update_privileges.len());
3890 let mut warnings = Vec::new();
3891 let catalog = self.catalog().for_session(session);
3892
3893 for UpdatePrivilege {
3894 acl_mode,
3895 target_id,
3896 grantor,
3897 acl_from_all,
3898 } in update_privileges
3899 {
3900 let actual_object_type = catalog.get_system_object_type(&target_id);
3901 if actual_object_type.is_relation() && !acl_from_all {
3907 let applicable_privileges = rbac::all_object_privileges(actual_object_type);
3908 let non_applicable_privileges = acl_mode.difference(applicable_privileges);
3909 if !non_applicable_privileges.is_empty() {
3910 let object_description =
3911 ErrorMessageObjectDescription::from_sys_id(&target_id, &catalog);
3912 warnings.push(AdapterNotice::NonApplicablePrivilegeTypes {
3913 non_applicable_privileges,
3914 object_description,
3915 })
3916 }
3917 }
3918
3919 if let SystemObjectId::Object(object_id) = &target_id {
3920 self.catalog()
3921 .ensure_not_reserved_object(object_id, session.conn_id())?;
3922 }
3923
3924 let privileges = self
3925 .catalog()
3926 .get_privileges(&target_id, session.conn_id())
3927 .ok_or(AdapterError::Unsupported(
3930 "GRANTs/REVOKEs on an object type with no privileges",
3931 ))?;
3932
3933 let mut target_privileges = Vec::with_capacity(grantees.len());
3936 for grantee in &grantees {
3937 self.catalog().ensure_not_system_role(grantee)?;
3938 self.catalog().ensure_not_predefined_role(grantee)?;
3939 let existing_privilege = privileges
3940 .get_acl_item(grantee, &grantor)
3941 .map(Cow::Borrowed)
3942 .unwrap_or_else(|| Cow::Owned(MzAclItem::empty(*grantee, grantor)));
3943
3944 let changes = match variant {
3946 UpdatePrivilegeVariant::Grant => {
3947 !existing_privilege.acl_mode.contains(acl_mode)
3948 }
3949 UpdatePrivilegeVariant::Revoke => !existing_privilege
3950 .acl_mode
3951 .intersection(acl_mode)
3952 .is_empty(),
3953 };
3954 if changes {
3955 target_privileges.push(MzAclItem {
3956 grantee: *grantee,
3957 grantor,
3958 acl_mode,
3959 });
3960 }
3961 }
3962 if !target_privileges.is_empty() {
3963 ops.push(catalog::Op::UpdatePrivilege {
3964 target_id: target_id.clone(),
3965 privileges: target_privileges,
3966 variant,
3967 });
3968 }
3969 }
3970
3971 if ops.is_empty() {
3972 session.add_notices(warnings);
3973 return Ok(variant.into());
3974 }
3975
3976 let res = self
3977 .catalog_transact(Some(session), ops)
3978 .await
3979 .map(|_| match variant {
3980 UpdatePrivilegeVariant::Grant => ExecuteResponse::GrantedPrivilege,
3981 UpdatePrivilegeVariant::Revoke => ExecuteResponse::RevokedPrivilege,
3982 });
3983 if res.is_ok() {
3984 session.add_notices(warnings);
3985 }
3986 res
3987 }
3988
3989 #[instrument]
3990 pub(super) async fn sequence_alter_default_privileges(
3991 &mut self,
3992 session: &Session,
3993 plan::AlterDefaultPrivilegesPlan {
3994 privilege_objects,
3995 privilege_acl_items,
3996 is_grant,
3997 }: plan::AlterDefaultPrivilegesPlan,
3998 ) -> Result<ExecuteResponse, AdapterError> {
3999 let mut ops = Vec::with_capacity(privilege_objects.len() * privilege_acl_items.len());
4000 let variant = if is_grant {
4001 UpdatePrivilegeVariant::Grant
4002 } else {
4003 UpdatePrivilegeVariant::Revoke
4004 };
4005 for privilege_object in &privilege_objects {
4006 self.catalog()
4007 .ensure_not_system_role(&privilege_object.role_id)?;
4008 self.catalog()
4009 .ensure_not_predefined_role(&privilege_object.role_id)?;
4010 if let Some(database_id) = privilege_object.database_id {
4011 self.catalog()
4012 .ensure_not_reserved_object(&database_id.into(), session.conn_id())?;
4013 }
4014 if let Some(schema_id) = privilege_object.schema_id {
4015 let database_spec: ResolvedDatabaseSpecifier = privilege_object.database_id.into();
4016 let schema_spec: SchemaSpecifier = schema_id.into();
4017
4018 self.catalog().ensure_not_reserved_object(
4019 &(database_spec, schema_spec).into(),
4020 session.conn_id(),
4021 )?;
4022 }
4023 for privilege_acl_item in &privilege_acl_items {
4024 self.catalog()
4025 .ensure_not_system_role(&privilege_acl_item.grantee)?;
4026 self.catalog()
4027 .ensure_not_predefined_role(&privilege_acl_item.grantee)?;
4028 ops.push(catalog::Op::UpdateDefaultPrivilege {
4029 privilege_object: privilege_object.clone(),
4030 privilege_acl_item: privilege_acl_item.clone(),
4031 variant,
4032 })
4033 }
4034 }
4035
4036 self.catalog_transact(Some(session), ops).await?;
4037 Ok(ExecuteResponse::AlteredDefaultPrivileges)
4038 }
4039
4040 #[instrument]
4041 pub(super) async fn sequence_grant_role(
4042 &mut self,
4043 session: &Session,
4044 plan::GrantRolePlan {
4045 role_ids,
4046 member_ids,
4047 grantor_id,
4048 }: plan::GrantRolePlan,
4049 ) -> Result<ExecuteResponse, AdapterError> {
4050 let catalog = self.catalog();
4051 let mut ops = Vec::with_capacity(role_ids.len() * member_ids.len());
4052 for role_id in role_ids {
4053 for member_id in &member_ids {
4054 let member_membership: BTreeSet<_> =
4055 catalog.get_role(member_id).membership().keys().collect();
4056 if member_membership.contains(&role_id) {
4057 let role_name = catalog.get_role(&role_id).name().to_string();
4058 let member_name = catalog.get_role(member_id).name().to_string();
4059 catalog.ensure_not_reserved_role(member_id)?;
4061 catalog.ensure_grantable_role(&role_id)?;
4062 session.add_notice(AdapterNotice::RoleMembershipAlreadyExists {
4063 role_name,
4064 member_name,
4065 });
4066 } else {
4067 ops.push(catalog::Op::GrantRole {
4068 role_id,
4069 member_id: *member_id,
4070 grantor_id,
4071 });
4072 }
4073 }
4074 }
4075
4076 if ops.is_empty() {
4077 return Ok(ExecuteResponse::GrantedRole);
4078 }
4079
4080 self.catalog_transact(Some(session), ops)
4081 .await
4082 .map(|_| ExecuteResponse::GrantedRole)
4083 }
4084
4085 #[instrument]
4086 pub(super) async fn sequence_revoke_role(
4087 &mut self,
4088 session: &Session,
4089 plan::RevokeRolePlan {
4090 role_ids,
4091 member_ids,
4092 grantor_id,
4093 }: plan::RevokeRolePlan,
4094 ) -> Result<ExecuteResponse, AdapterError> {
4095 let catalog = self.catalog();
4096 let mut ops = Vec::with_capacity(role_ids.len() * member_ids.len());
4097 for role_id in role_ids {
4098 for member_id in &member_ids {
4099 let member_membership: BTreeSet<_> =
4100 catalog.get_role(member_id).membership().keys().collect();
4101 if !member_membership.contains(&role_id) {
4102 let role_name = catalog.get_role(&role_id).name().to_string();
4103 let member_name = catalog.get_role(member_id).name().to_string();
4104 catalog.ensure_not_reserved_role(member_id)?;
4106 catalog.ensure_grantable_role(&role_id)?;
4107 session.add_notice(AdapterNotice::RoleMembershipDoesNotExists {
4108 role_name,
4109 member_name,
4110 });
4111 } else {
4112 ops.push(catalog::Op::RevokeRole {
4113 role_id,
4114 member_id: *member_id,
4115 grantor_id,
4116 });
4117 }
4118 }
4119 }
4120
4121 if ops.is_empty() {
4122 return Ok(ExecuteResponse::RevokedRole);
4123 }
4124
4125 self.catalog_transact(Some(session), ops)
4126 .await
4127 .map(|_| ExecuteResponse::RevokedRole)
4128 }
4129
4130 #[instrument]
4131 pub(super) async fn sequence_alter_owner(
4132 &mut self,
4133 session: &Session,
4134 plan::AlterOwnerPlan {
4135 id,
4136 object_type,
4137 new_owner,
4138 }: plan::AlterOwnerPlan,
4139 ) -> Result<ExecuteResponse, AdapterError> {
4140 let mut ops = vec![catalog::Op::UpdateOwner {
4141 id: id.clone(),
4142 new_owner,
4143 }];
4144
4145 match &id {
4146 ObjectId::Item(global_id) => {
4147 let entry = self.catalog().get_entry(global_id);
4148
4149 if entry.is_index() {
4151 let name = self
4152 .catalog()
4153 .resolve_full_name(entry.name(), Some(session.conn_id()))
4154 .to_string();
4155 session.add_notice(AdapterNotice::AlterIndexOwner { name });
4156 return Ok(ExecuteResponse::AlteredObject(object_type));
4157 }
4158
4159 let dependent_index_ops = entry
4161 .used_by()
4162 .into_iter()
4163 .filter(|id| self.catalog().get_entry(id).is_index())
4164 .map(|id| catalog::Op::UpdateOwner {
4165 id: ObjectId::Item(*id),
4166 new_owner,
4167 });
4168 ops.extend(dependent_index_ops);
4169
4170 let dependent_subsources =
4172 entry
4173 .progress_id()
4174 .into_iter()
4175 .map(|item_id| catalog::Op::UpdateOwner {
4176 id: ObjectId::Item(item_id),
4177 new_owner,
4178 });
4179 ops.extend(dependent_subsources);
4180 }
4181 ObjectId::Cluster(cluster_id) => {
4182 let cluster = self.catalog().get_cluster(*cluster_id);
4183 let managed_cluster_replica_ops =
4185 cluster.replicas().map(|replica| catalog::Op::UpdateOwner {
4186 id: ObjectId::ClusterReplica((cluster.id(), replica.replica_id())),
4187 new_owner,
4188 });
4189 ops.extend(managed_cluster_replica_ops);
4190 }
4191 _ => {}
4192 }
4193
4194 self.catalog_transact(Some(session), ops)
4195 .await
4196 .map(|_| ExecuteResponse::AlteredObject(object_type))
4197 }
4198
4199 #[instrument]
4200 pub(super) async fn sequence_reassign_owned(
4201 &mut self,
4202 session: &Session,
4203 plan::ReassignOwnedPlan {
4204 old_roles,
4205 new_role,
4206 reassign_ids,
4207 }: plan::ReassignOwnedPlan,
4208 ) -> Result<ExecuteResponse, AdapterError> {
4209 for role_id in old_roles.iter().chain(iter::once(&new_role)) {
4210 self.catalog().ensure_not_reserved_role(role_id)?;
4211 }
4212
4213 let ops = reassign_ids
4214 .into_iter()
4215 .map(|id| catalog::Op::UpdateOwner {
4216 id,
4217 new_owner: new_role,
4218 })
4219 .collect();
4220
4221 self.catalog_transact(Some(session), ops)
4222 .await
4223 .map(|_| ExecuteResponse::ReassignOwned)
4224 }
4225
4226 #[instrument]
4227 pub(crate) async fn handle_deferred_statement(&mut self) {
4228 let Some(DeferredPlanStatement { ctx, ps }) = self.serialized_ddl.pop_front() else {
4232 return;
4233 };
4234 match ps {
4235 crate::coord::PlanStatement::Statement { stmt, params } => {
4236 self.handle_execute_inner(stmt, params, ctx).await;
4237 }
4238 crate::coord::PlanStatement::Plan {
4239 plan,
4240 resolved_ids,
4241 sql_impl_resolved_ids,
4242 } => {
4243 self.sequence_plan(ctx, plan, resolved_ids, sql_impl_resolved_ids)
4244 .await;
4245 }
4246 }
4247 }
4248
4249 #[instrument]
4250 #[allow(clippy::unused_async)]
4252 pub(super) async fn sequence_alter_table(
4253 &mut self,
4254 ctx: &mut ExecuteContext,
4255 plan: plan::AlterTablePlan,
4256 ) -> Result<ExecuteResponse, AdapterError> {
4257 let plan::AlterTablePlan {
4258 relation_id,
4259 column_name,
4260 column_type,
4261 raw_sql_type,
4262 } = plan;
4263
4264 let (_, new_global_id) = self.allocate_user_id().await?;
4266 let ops = vec![catalog::Op::AlterAddColumn {
4267 id: relation_id,
4268 new_global_id,
4269 name: column_name,
4270 typ: column_type,
4271 sql: raw_sql_type,
4272 }];
4273
4274 self.catalog_transact_with_context(None, Some(ctx), ops)
4275 .await?;
4276
4277 Ok(ExecuteResponse::AlteredObject(ObjectType::Table))
4278 }
4279
4280 #[instrument]
4282 pub(super) async fn sequence_alter_materialized_view_apply_replacement_prepare(
4283 &mut self,
4284 ctx: ExecuteContext,
4285 plan: AlterMaterializedViewApplyReplacementPlan,
4286 ) {
4287 let AlterMaterializedViewApplyReplacementPlan { id, replacement_id } = plan.clone();
4297
4298 let plan_validity = PlanValidity::new(
4299 self.catalog(),
4300 BTreeSet::from_iter([id, replacement_id]),
4301 None,
4302 None,
4303 ctx.session().role_metadata().clone(),
4304 );
4305
4306 let target = self.catalog.get_entry(&id);
4307 let target_gid = target.latest_global_id();
4308
4309 let replacement = self.catalog.get_entry(&replacement_id);
4310 let replacement_gid = replacement.latest_global_id();
4311
4312 let target_upper = self
4313 .controller
4314 .storage_collections
4315 .collection_frontiers(target_gid)
4316 .expect("target MV exists")
4317 .write_frontier;
4318 let replacement_upper = self
4319 .controller
4320 .compute
4321 .collection_frontiers(replacement_gid, replacement.cluster_id())
4322 .expect("replacement MV exists")
4323 .write_frontier;
4324
4325 info!(
4326 %id, %replacement_id, ?target_upper, ?replacement_upper,
4327 "preparing materialized view replacement application",
4328 );
4329
4330 let Some(replacement_upper_ts) = replacement_upper.into_option() else {
4331 ctx.retire(Err(AdapterError::ReplaceMaterializedViewSealed {
4340 name: target.name().item.clone(),
4341 }));
4342 return;
4343 };
4344
4345 let replacement_upper_ts = replacement_upper_ts.step_back().unwrap_or(Timestamp::MIN);
4349
4350 self.install_storage_watch_set(
4354 ctx.session().conn_id().clone(),
4355 BTreeSet::from_iter([target_gid]),
4356 replacement_upper_ts,
4357 WatchSetResponse::AlterMaterializedViewReady(AlterMaterializedViewReadyContext {
4358 ctx: Some(ctx),
4359 otel_ctx: OpenTelemetryContext::obtain(),
4360 plan,
4361 plan_validity,
4362 }),
4363 )
4364 .expect("target collection exists");
4365 }
4366
4367 #[instrument]
4369 pub async fn sequence_alter_materialized_view_apply_replacement_finish(
4370 &mut self,
4371 mut ctx: AlterMaterializedViewReadyContext,
4372 ) {
4373 ctx.otel_ctx.attach_as_parent();
4374
4375 let AlterMaterializedViewApplyReplacementPlan { id, replacement_id } = ctx.plan;
4376
4377 if let Err(err) = ctx.plan_validity.check(self.catalog()) {
4382 ctx.retire(Err(err));
4383 return;
4384 }
4385
4386 info!(
4387 %id, %replacement_id,
4388 "finishing materialized view replacement application",
4389 );
4390
4391 let invalidate_ids = self.catalog().get_entry(&id).global_ids().collect();
4397 self.catalog()
4398 .update_expression_cache(Vec::new(), Vec::new(), invalidate_ids)
4399 .await;
4400
4401 let ops = vec![catalog::Op::AlterMaterializedViewApplyReplacement { id, replacement_id }];
4402 match self
4403 .catalog_transact(Some(ctx.ctx().session_mut()), ops)
4404 .await
4405 {
4406 Ok(()) => ctx.retire(Ok(ExecuteResponse::AlteredObject(
4407 ObjectType::MaterializedView,
4408 ))),
4409 Err(err) => ctx.retire(Err(err)),
4410 }
4411 }
4412}
4413
4414impl Coordinator {
4415 pub(crate) fn emit_raw_optimizer_notices_to_user(
4423 &self,
4424 ctx: &ExecuteContext,
4425 notices: &[RawOptimizerNotice],
4426 ) {
4427 emit_optimizer_notices(&*self.catalog, ctx.session(), notices);
4428 }
4429
4430 fn render_create_item_notices(
4440 &self,
4441 name: &QualifiedItemName,
4442 global_id: GlobalId,
4443 source_desc: &RelationDesc,
4444 raw_df_meta: &DataflowMetainfo,
4445 ) -> DataflowMetainfo<Arc<OptimizerNotice>> {
4446 let notice_ids = std::iter::repeat_with(|| self.allocate_transient_id())
4447 .map(|(_item_id, notice_id)| notice_id)
4448 .take(raw_df_meta.optimizer_notices.len())
4449 .collect::<Vec<_>>();
4450
4451 let system_catalog = self.catalog().for_system_session();
4452 let full_name = self.catalog().resolve_full_name(name, None);
4453 let transient_items = btreemap! {
4454 global_id => TransientItem::new(
4455 Some(full_name.into_parts()),
4456 Some(source_desc.iter_names().map(|c| c.to_string()).collect()),
4457 )
4458 };
4459 let humanizer = ExprHumanizerExt::new(transient_items, &system_catalog);
4460 CatalogState::render_notices_core(
4461 &humanizer,
4462 (self.catalog().config().now)(),
4463 raw_df_meta,
4464 notice_ids,
4465 Some(global_id),
4466 )
4467 }
4468
4469 async fn ship_new_dataflow(
4476 &mut self,
4477 id_bundle: &CollectionIdBundle,
4478 mut df_desc: LirDataflowDescription,
4479 instance: ComputeInstanceId,
4480 notice_builtin_updates_fut: Option<BuiltinTableAppendNotify>,
4481 ) {
4482 let read_holds = self.acquire_read_holds(id_bundle);
4483 let since = read_holds.least_valid_read();
4484 df_desc.set_as_of(since);
4485
4486 self.ship_dataflow_and_notice_builtin_table_updates(
4487 df_desc,
4488 instance,
4489 notice_builtin_updates_fut,
4490 None,
4491 )
4492 .await;
4493
4494 drop(read_holds);
4495 }
4496
4497 fn persist_dataflow_metainfo(
4507 &mut self,
4508 df_meta: DataflowMetainfo<Arc<OptimizerNotice>>,
4509 export_id: GlobalId,
4510 ) -> Option<BuiltinTableAppendNotify> {
4511 if self.catalog().state().system_config().enable_mz_notices()
4514 && !df_meta.optimizer_notices.is_empty()
4515 {
4516 let mut builtin_table_updates = Vec::with_capacity(df_meta.optimizer_notices.len());
4517 self.catalog().state().pack_optimizer_notices(
4518 &mut builtin_table_updates,
4519 df_meta.optimizer_notices.iter(),
4520 Diff::ONE,
4521 );
4522
4523 self.catalog_mut().set_dataflow_metainfo(export_id, df_meta);
4525
4526 Some(self.builtin_table_update().execute(builtin_table_updates))
4527 } else {
4528 self.catalog_mut().set_dataflow_metainfo(export_id, df_meta);
4530
4531 None
4532 }
4533 }
4534}