1use std::borrow::Cow;
11use std::collections::{BTreeMap, BTreeSet, VecDeque};
12use std::iter;
13use std::num::{NonZeroI64, NonZeroUsize};
14use std::sync::Arc;
15use std::time::{Duration, Instant};
16
17use anyhow::anyhow;
18use futures::future::{BoxFuture, FutureExt};
19use futures::{Future, StreamExt, future};
20use itertools::Itertools;
21use mz_adapter_types::compaction::CompactionWindow;
22use mz_adapter_types::connection::ConnectionId;
23use mz_adapter_types::dyncfgs::{ENABLE_PASSWORD_AUTH, READ_THEN_WRITE_MAX_DEPENDENCIES};
24use mz_catalog::memory::error::ErrorKind;
25use mz_catalog::memory::objects::{
26 CatalogItem, Connection, DataSourceDesc, Sink, Source, Table, TableDataSource, Type,
27};
28use mz_expr::{
29 CollectionPlan, Eval, MapFilterProject, OptimizedMirRelationExpr, ResultSpec, RowSetFinishing,
30};
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};
37use mz_repr::adt::jsonb::Jsonb;
38use mz_repr::adt::mz_acl_item::{MzAclItem, PrivilegeMap};
39use mz_repr::explain::ExprHumanizer;
40use mz_repr::explain::json::json_string;
41use mz_repr::role_id::RoleId;
42use mz_repr::{
43 CatalogItemId, Datum, Diff, GlobalId, RelationVersion, RelationVersionSelector, Row, RowArena,
44 RowIterator, 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,
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, MutationKind, Params, Plan, PlannedAlterRoleOption,
73 PlannedRoleVariable, QueryWhen, SideEffectingFunc, UpdatePrivilege, VariableValue,
74};
75use mz_sql::session::metadata::SessionMetadata;
76use mz_sql::session::user::UserKind;
77use mz_sql::session::vars::{
78 self, IsolationLevel, 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::inline::IntoInlineConnection;
92use mz_storage_types::controller::StorageError;
93use mz_transform::dataflow::DataflowMetainfo;
94use mz_transform::notice::{OptimizerNotice, RawOptimizerNotice};
95use smallvec::SmallVec;
96use timely::progress::Antichain;
97use tokio::sync::{oneshot, watch};
98use tracing::{Instrument, Span, info, warn};
99
100use crate::catalog::{self, Catalog, ConnCatalog, DropObjectInfo, UpdatePrivilegeVariant};
101use crate::command::{ExecuteResponse, Response};
102use crate::coord::appends::{
103 BuiltinTableAppendNotify, DeferredOp, DeferredPlan, PendingWriteTxn, UserWriteResponder,
104};
105use crate::coord::read_then_write::validate_read_then_write_dependencies;
106use crate::coord::sequencer::emit_optimizer_notices;
107use crate::coord::{
108 AlterConnectionValidationReady, AlterMaterializedViewReadyContext, AlterSinkReadyContext,
109 Coordinator, CreateConnectionValidationReady, DeferredPlanStatement, ExecuteContext,
110 ExplainContext, Message, NetworkPolicyError, PendingRead, PendingReadTxn, PendingTxn,
111 PendingTxnResponse, PlanValidity, StageResult, Staged, StagedContext, TargetCluster,
112 WatchSetResponse, validate_ip_with_policy_rules,
113};
114use crate::error::AdapterError;
115use crate::notice::{AdapterNotice, DroppedInUseIndex};
116use crate::optimize::dataflows::{EvalTime, ExprPrep, ExprPrepOneShot};
117use crate::optimize::{self, Optimize};
118use crate::session::{
119 EndTransactionAction, RequireLinearization, Session, TransactionOps, TransactionStatus,
120 WriteLocks, WriteOp,
121};
122use crate::util::{ClientTransmitter, ResultExt, viewable_variables};
123use crate::{PeekResponseUnary, ReadHolds};
124
125type RtrTimestampFuture = BoxFuture<'static, Result<Timestamp, StorageError>>;
127
128mod cluster;
129pub(crate) use cluster::cancel_carried_reconfiguration;
130mod copy_from;
131mod create_index;
132mod create_materialized_view;
133mod create_view;
134mod explain_timestamp;
135mod peek;
136mod secret;
137mod subscribe;
138
139macro_rules! return_if_err {
142 ($expr:expr, $ctx:expr) => {
143 match $expr {
144 Ok(v) => v,
145 Err(e) => return $ctx.retire(Err(e.into())),
146 }
147 };
148}
149
150pub(super) use return_if_err;
151
152fn spawn_linearized_read_ts<S>(
153 oracle: Option<Arc<dyn TimestampOracle<Timestamp> + Send + Sync>>,
154 name: &'static str,
155 build_stage: impl FnOnce(Option<Timestamp>) -> S + Send + 'static,
156) -> StageResult<Box<S>>
157where
158 S: Send + 'static,
159{
160 match oracle {
161 Some(oracle) => {
162 let span = Span::current();
163 StageResult::Handle(mz_ore::task::spawn(
164 move || name,
165 async move {
166 let oracle_read_ts = oracle.read_ts().await;
167 Ok(Box::new(build_stage(Some(oracle_read_ts))))
168 }
169 .instrument(span),
170 ))
171 }
172 None => StageResult::Immediate(Box::new(build_stage(None))),
173 }
174}
175
176struct DropOps {
177 ops: Vec<catalog::Op>,
178 dropped_active_db: bool,
179 dropped_active_cluster: bool,
180 dropped_in_use_indexes: Vec<DroppedInUseIndex>,
181}
182
183struct CreateSourceInner {
185 ops: Vec<catalog::Op>,
186 sources: Vec<(CatalogItemId, Source)>,
187 if_not_exists_ids: BTreeMap<CatalogItemId, QualifiedItemName>,
188}
189
190impl Coordinator {
191 pub(crate) async fn sequence_staged<S>(
198 &mut self,
199 mut ctx: S::Ctx,
200 parent_span: Span,
201 mut stage: S,
202 ) where
203 S: Staged + 'static,
204 S::Ctx: Send + 'static,
205 {
206 return_if_err!(stage.validity().check(self.catalog()), ctx);
207 loop {
208 let mut cancel_enabled = stage.cancel_enabled();
209 if let Some(session) = ctx.session() {
210 if cancel_enabled {
211 if let Some((_prev_tx, prev_rx)) = self
214 .connection_cancel_watches
215 .insert(session.conn_id().clone(), watch::channel(false))
216 {
217 let was_canceled = *prev_rx.borrow();
218 if was_canceled {
219 ctx.retire(Err(AdapterError::Canceled));
220 return;
221 }
222 }
223 } else {
224 self.connection_cancel_watches.remove(session.conn_id());
227 }
228 } else {
229 cancel_enabled = false
230 };
231 let next = stage
232 .stage(self, &mut ctx)
233 .instrument(parent_span.clone())
234 .await;
235 let res = return_if_err!(next, ctx);
236 stage = match res {
237 StageResult::Handle(handle) => {
238 let internal_cmd_tx = self.internal_cmd_tx.clone();
239 self.handle_spawn(ctx, handle, cancel_enabled, move |ctx, next| {
240 let _ = internal_cmd_tx.send(next.message(ctx, parent_span));
241 });
242 return;
243 }
244 StageResult::HandleRetire(handle) => {
245 self.handle_spawn(ctx, handle, cancel_enabled, move |ctx, resp| {
246 ctx.retire(Ok(resp));
247 });
248 return;
249 }
250 StageResult::Response(resp) => {
251 ctx.retire(Ok(resp));
252 return;
253 }
254 StageResult::Immediate(stage) => *stage,
255 }
256 }
257 }
258
259 fn handle_spawn<C, T, F>(
262 &self,
263 ctx: C,
264 handle: JoinHandle<Result<T, AdapterError>>,
265 cancel_enabled: bool,
266 f: F,
267 ) where
268 C: StagedContext + Send + 'static,
269 T: Send + 'static,
270 F: FnOnce(C, T) + Send + 'static,
271 {
272 let rx: BoxFuture<()> = if let Some((_tx, rx)) = ctx
273 .session()
274 .and_then(|session| self.connection_cancel_watches.get(session.conn_id()))
275 {
276 let mut rx = rx.clone();
277 Box::pin(async move {
278 let _ = rx.wait_for(|v| *v).await;
280 ()
281 })
282 } else {
283 Box::pin(future::pending())
284 };
285 spawn(|| "sequence_staged", async move {
286 tokio::select! {
287 res = handle => {
288 let next = return_if_err!(res, ctx);
289 f(ctx, next);
290 }
291 _ = rx, if cancel_enabled => {
292 ctx.retire(Err(AdapterError::Canceled));
293 }
294 }
295 });
296 }
297
298 async fn create_source_inner(
299 &self,
300 session: &Session,
301 plans: Vec<plan::CreateSourcePlanBundle>,
302 ) -> Result<CreateSourceInner, AdapterError> {
303 let mut ops = vec![];
304 let mut sources = vec![];
305
306 let if_not_exists_ids = plans
307 .iter()
308 .filter_map(
309 |plan::CreateSourcePlanBundle {
310 item_id,
311 global_id: _,
312 plan,
313 resolved_ids: _,
314 available_source_references: _,
315 }| {
316 if plan.if_not_exists {
317 Some((*item_id, plan.name.clone()))
318 } else {
319 None
320 }
321 },
322 )
323 .collect::<BTreeMap<_, _>>();
324
325 for plan::CreateSourcePlanBundle {
326 item_id,
327 global_id,
328 mut plan,
329 resolved_ids,
330 available_source_references,
331 } in plans
332 {
333 let name = plan.name.clone();
334
335 if let mz_sql::plan::DataSourceDesc::Webhook {
337 validate_using: Some(validate),
338 ..
339 } = &mut plan.source.data_source
340 {
341 if let Err(reason) = validate.reduce_expression().await {
342 self.metrics
343 .webhook_validation_reduce_failures
344 .with_label_values(&[reason])
345 .inc();
346 return Err(AdapterError::Internal(format!(
347 "failed to reduce check expression, {reason}"
348 )));
349 }
350 }
351
352 let mut reference_ops = vec![];
355 if let Some(references) = &available_source_references {
356 reference_ops.push(catalog::Op::UpdateSourceReferences {
357 source_id: item_id,
358 references: references.clone().into(),
359 });
360 }
361
362 let source = Source::new(plan, global_id, resolved_ids, None, false);
363 ops.push(catalog::Op::CreateItem {
364 id: item_id,
365 name,
366 item: CatalogItem::Source(source.clone()),
367 owner_id: *session.current_role_id(),
368 });
369 sources.push((item_id, source));
370 ops.extend(reference_ops);
372 }
373
374 Ok(CreateSourceInner {
375 ops,
376 sources,
377 if_not_exists_ids,
378 })
379 }
380
381 pub(crate) fn plan_subsource(
389 &self,
390 session: &Session,
391 params: &mz_sql::plan::Params,
392 subsource_stmt: CreateSubsourceStatement<mz_sql::names::Aug>,
393 item_id: CatalogItemId,
394 global_id: GlobalId,
395 ) -> Result<CreateSourcePlanBundle, AdapterError> {
396 let catalog = self.catalog().for_session(session);
397 let resolved_ids = mz_sql::names::visit_dependencies(&catalog, &subsource_stmt);
398
399 let (plan, _sql_impl_ids) = self.plan_statement(
400 session,
401 Statement::CreateSubsource(subsource_stmt),
402 params,
403 &resolved_ids,
404 )?;
405 let plan = match plan {
406 Plan::CreateSource(plan) => plan,
407 _ => unreachable!(),
408 };
409 Ok(CreateSourcePlanBundle {
410 item_id,
411 global_id,
412 plan,
413 resolved_ids,
414 available_source_references: None,
415 })
416 }
417
418 pub(crate) async fn plan_purified_alter_source_add_subsource(
420 &mut self,
421 session: &Session,
422 params: Params,
423 source_name: ResolvedItemName,
424 options: Vec<AlterSourceAddSubsourceOption<Aug>>,
425 subsources: BTreeMap<UnresolvedItemName, PurifiedSourceExport>,
426 ) -> Result<(Plan, ResolvedIds), AdapterError> {
427 let mut subsource_plans = Vec::with_capacity(subsources.len());
428
429 let conn_catalog = self.catalog().for_system_session();
431 let pcx = plan::PlanContext::zero();
432 let scx = StatementContext::new(Some(&pcx), &conn_catalog);
433
434 let entry = self.catalog().get_entry(source_name.item_id());
435 let source = entry.source().ok_or_else(|| {
436 AdapterError::internal(
437 "plan alter source",
438 format!("expected Source found {entry:?}"),
439 )
440 })?;
441
442 let item_id = entry.id();
443 let ingestion_id = source.global_id();
444 let subsource_stmts = generate_subsource_statements(&scx, source_name, subsources)?;
445
446 let ids = self
447 .allocate_user_ids(u64::cast_from(subsource_stmts.len()))
448 .await?;
449 for (subsource_stmt, (item_id, global_id)) in subsource_stmts.into_iter().zip_eq(ids) {
450 let s = self.plan_subsource(session, ¶ms, subsource_stmt, item_id, global_id)?;
451 subsource_plans.push(s);
452 }
453
454 let action = mz_sql::plan::AlterSourceAction::AddSubsourceExports {
455 subsources: subsource_plans,
456 options,
457 };
458
459 Ok((
460 Plan::AlterSource(mz_sql::plan::AlterSourcePlan {
461 item_id,
462 ingestion_id,
463 action,
464 }),
465 ResolvedIds::empty(),
466 ))
467 }
468
469 pub(crate) fn plan_purified_alter_source_refresh_references(
471 &self,
472 _session: &Session,
473 _params: Params,
474 source_name: ResolvedItemName,
475 available_source_references: plan::SourceReferences,
476 ) -> Result<(Plan, ResolvedIds), AdapterError> {
477 let entry = self.catalog().get_entry(source_name.item_id());
478 let source = entry.source().ok_or_else(|| {
479 AdapterError::internal(
480 "plan alter source",
481 format!("expected Source found {entry:?}"),
482 )
483 })?;
484 let action = mz_sql::plan::AlterSourceAction::RefreshReferences {
485 references: available_source_references,
486 };
487
488 Ok((
489 Plan::AlterSource(mz_sql::plan::AlterSourcePlan {
490 item_id: entry.id(),
491 ingestion_id: source.global_id(),
492 action,
493 }),
494 ResolvedIds::empty(),
495 ))
496 }
497
498 pub(crate) async fn plan_purified_create_source(
502 &mut self,
503 ctx: &ExecuteContext,
504 params: Params,
505 progress_stmt: Option<CreateSubsourceStatement<Aug>>,
506 mut source_stmt: mz_sql::ast::CreateSourceStatement<Aug>,
507 subsources: BTreeMap<UnresolvedItemName, PurifiedSourceExport>,
508 available_source_references: plan::SourceReferences,
509 ) -> Result<(Plan, ResolvedIds), AdapterError> {
510 let mut create_source_plans = Vec::with_capacity(subsources.len() + 2);
511
512 if let Some(progress_stmt) = progress_stmt {
514 assert_none!(progress_stmt.of_source);
519 let (item_id, global_id) = self.allocate_user_id().await?;
520 let progress_plan =
521 self.plan_subsource(ctx.session(), ¶ms, progress_stmt, item_id, global_id)?;
522 let progress_full_name = self
523 .catalog()
524 .resolve_full_name(&progress_plan.plan.name, None);
525 let progress_subsource = ResolvedItemName::Item {
526 id: progress_plan.item_id,
527 qualifiers: progress_plan.plan.name.qualifiers.clone(),
528 full_name: progress_full_name,
529 print_id: true,
530 version: RelationVersionSelector::Latest,
531 };
532
533 create_source_plans.push(progress_plan);
534
535 source_stmt.progress_subsource = Some(DeferredItemName::Named(progress_subsource));
536 }
537
538 let catalog = self.catalog().for_session(ctx.session());
539 let resolved_ids = mz_sql::names::visit_dependencies(&catalog, &source_stmt);
540
541 let propagated_with_options: Vec<_> = source_stmt
542 .with_options
543 .iter()
544 .filter_map(|opt| match opt.name {
545 CreateSourceOptionName::TimestampInterval => None,
546 CreateSourceOptionName::RetainHistory => Some(CreateSubsourceOption {
547 name: CreateSubsourceOptionName::RetainHistory,
548 value: opt.value.clone(),
549 }),
550 })
551 .collect();
552
553 let source_plan = match self.plan_statement(
555 ctx.session(),
556 Statement::CreateSource(source_stmt),
557 ¶ms,
558 &resolved_ids,
559 )? {
560 (Plan::CreateSource(plan), _sql_impl_ids) => plan,
561 (p, _) => unreachable!("s must be CreateSourcePlan but got {:?}", p),
562 };
563
564 let (item_id, global_id) = self.allocate_user_id().await?;
565
566 let source_full_name = self.catalog().resolve_full_name(&source_plan.name, None);
567 let of_source = ResolvedItemName::Item {
568 id: item_id,
569 qualifiers: source_plan.name.qualifiers.clone(),
570 full_name: source_full_name,
571 print_id: true,
572 version: RelationVersionSelector::Latest,
573 };
574
575 let conn_catalog = self.catalog().for_system_session();
577 let pcx = plan::PlanContext::zero();
578 let scx = StatementContext::new(Some(&pcx), &conn_catalog);
579
580 let mut subsource_stmts = generate_subsource_statements(&scx, of_source, subsources)?;
581
582 for subsource_stmt in subsource_stmts.iter_mut() {
583 subsource_stmt
584 .with_options
585 .extend(propagated_with_options.iter().cloned())
586 }
587
588 create_source_plans.push(CreateSourcePlanBundle {
589 item_id,
590 global_id,
591 plan: source_plan,
592 resolved_ids: resolved_ids.clone(),
593 available_source_references: Some(available_source_references),
594 });
595
596 let ids = self
598 .allocate_user_ids(u64::cast_from(subsource_stmts.len()))
599 .await?;
600 for (stmt, (item_id, global_id)) in subsource_stmts.into_iter().zip_eq(ids) {
601 let plan = self.plan_subsource(ctx.session(), ¶ms, stmt, item_id, global_id)?;
602 create_source_plans.push(plan);
603 }
604
605 Ok((
606 Plan::CreateSources(create_source_plans),
607 ResolvedIds::empty(),
608 ))
609 }
610
611 #[instrument]
612 pub(super) async fn sequence_create_source(
613 &mut self,
614 ctx: &mut ExecuteContext,
615 plans: Vec<plan::CreateSourcePlanBundle>,
616 ) -> Result<ExecuteResponse, AdapterError> {
617 let CreateSourceInner {
618 ops,
619 sources,
620 if_not_exists_ids,
621 } = self.create_source_inner(ctx.session(), plans).await?;
622
623 let transact_result = self
624 .catalog_transact_with_ddl_transaction(ctx, ops, |_, _| Box::pin(async {}))
625 .await;
626
627 for (item_id, source) in &sources {
629 if matches!(source.data_source, DataSourceDesc::Webhook { .. }) {
630 if let Some(url) = self.catalog().state().try_get_webhook_url(item_id) {
631 ctx.session()
632 .add_notice(AdapterNotice::WebhookSourceCreated { url });
633 }
634 }
635 }
636
637 match transact_result {
638 Ok(()) => Ok(ExecuteResponse::CreatedSource),
639 Err(AdapterError::Catalog(mz_catalog::memory::error::Error {
640 kind: ErrorKind::Sql(CatalogError::ItemAlreadyExists(id, _)),
641 })) if if_not_exists_ids.contains_key(&id) => {
642 ctx.session()
643 .add_notice(AdapterNotice::ObjectAlreadyExists {
644 name: if_not_exists_ids[&id].item.clone(),
645 ty: "source",
646 });
647 Ok(ExecuteResponse::CreatedSource)
648 }
649 Err(err) => Err(err),
650 }
651 }
652
653 async fn check_connection_secret_content_guards(
657 &self,
658 details: &ConnectionDetails,
659 ) -> Result<(), AdapterError> {
660 for (secret_id, guard) in details.secret_content_guards() {
661 let contents = self.caching_secrets_reader.read_string(secret_id).await?;
662 guard(&contents)?;
663 }
664 Ok(())
665 }
666
667 pub(super) fn check_secret_content_guards_of_dependents(
672 &self,
673 secret_id: CatalogItemId,
674 contents: &str,
675 ) -> Result<(), AdapterError> {
676 for dependent_id in self.catalog().get_entry(&secret_id).used_by() {
677 if let CatalogItem::Connection(conn) = self.catalog().get_entry(dependent_id).item() {
678 for (guarded_id, guard) in conn.details.secret_content_guards() {
679 if guarded_id == secret_id {
680 guard(contents)?;
681 }
682 }
683 }
684 }
685 Ok(())
686 }
687
688 #[instrument]
689 pub(super) async fn sequence_create_connection(
690 &mut self,
691 mut ctx: ExecuteContext,
692 plan: plan::CreateConnectionPlan,
693 resolved_ids: ResolvedIds,
694 ) {
695 let (connection_id, connection_gid) = match self.allocate_user_id().await {
696 Ok(item_id) => item_id,
697 Err(err) => return ctx.retire(Err(err)),
698 };
699
700 match &plan.connection.details {
701 ConnectionDetails::Ssh { key_1, key_2, .. } => {
702 let key_1 = match key_1.as_key_pair() {
703 Some(key_1) => key_1.clone(),
704 None => {
705 return ctx.retire(Err(AdapterError::Unstructured(anyhow!(
706 "the PUBLIC KEY 1 option cannot be explicitly specified"
707 ))));
708 }
709 };
710
711 let key_2 = match key_2.as_key_pair() {
712 Some(key_2) => key_2.clone(),
713 None => {
714 return ctx.retire(Err(AdapterError::Unstructured(anyhow!(
715 "the PUBLIC KEY 2 option cannot be explicitly specified"
716 ))));
717 }
718 };
719
720 let key_set = SshKeyPairSet::from_parts(key_1, key_2);
721 let secret = key_set.to_bytes();
722 if let Err(err) = self.secrets_controller.ensure(connection_id, &secret).await {
723 return ctx.retire(Err(err.into()));
724 }
725 self.caching_secrets_reader.invalidate(connection_id);
726 }
727 _ => (),
728 };
729
730 if let Err(err) = self
733 .check_connection_secret_content_guards(&plan.connection.details)
734 .await
735 {
736 return ctx.retire(Err(err));
737 }
738
739 if plan.validate {
740 let internal_cmd_tx = self.internal_cmd_tx.clone();
741 let catalog = self.owned_catalog();
742 let conn_id = ctx.session().conn_id().clone();
743 let otel_ctx = OpenTelemetryContext::obtain();
744 let role_metadata = ctx.session().role_metadata().clone();
745
746 let connection = plan
747 .connection
748 .details
749 .to_connection()
750 .into_inline_connection(self.catalog().state());
751
752 let current_storage_parameters = self.controller.storage.config().clone();
753 task::spawn(|| format!("validate_connection:{conn_id}"), async move {
754 let result = match std::panic::AssertUnwindSafe(
755 connection.validate(connection_id, ¤t_storage_parameters),
756 )
757 .ore_catch_unwind()
758 .await
759 {
760 Ok(Ok(())) => Ok(plan),
761 Ok(Err(err)) => Err(err.into()),
762 Err(_panic) => {
763 tracing::error!("connection validation panicked");
764 Err(AdapterError::Internal(
765 "connection validation panicked".into(),
766 ))
767 }
768 };
769
770 let result = internal_cmd_tx.send(Message::CreateConnectionValidationReady(
772 CreateConnectionValidationReady {
773 ctx,
774 result,
775 connection_id,
776 connection_gid,
777 plan_validity: PlanValidity::new(
778 &catalog,
779 resolved_ids.items().copied().collect(),
780 None,
781 None,
782 role_metadata,
783 ),
784 otel_ctx,
785 resolved_ids: resolved_ids.clone(),
786 },
787 ));
788 if let Err(e) = result {
789 tracing::warn!("internal_cmd_rx dropped before we could send: {:?}", e);
790 }
791 });
792 } else {
793 let result = self
794 .sequence_create_connection_stage_finish(
795 &mut ctx,
796 connection_id,
797 connection_gid,
798 plan,
799 resolved_ids,
800 )
801 .await;
802 ctx.retire(result);
803 }
804 }
805
806 #[instrument]
807 pub(crate) async fn sequence_create_connection_stage_finish(
808 &mut self,
809 ctx: &mut ExecuteContext,
810 connection_id: CatalogItemId,
811 connection_gid: GlobalId,
812 plan: plan::CreateConnectionPlan,
813 resolved_ids: ResolvedIds,
814 ) -> Result<ExecuteResponse, AdapterError> {
815 let ops = vec![catalog::Op::CreateItem {
816 id: connection_id,
817 name: plan.name.clone(),
818 item: CatalogItem::Connection(Connection {
819 create_sql: plan.connection.create_sql,
820 global_id: connection_gid,
821 details: plan.connection.details.clone(),
822 resolved_ids,
823 }),
824 owner_id: *ctx.session().current_role_id(),
825 }];
826
827 let conn_id = ctx.session().conn_id().clone();
830 let transact_result = self
831 .catalog_transact_with_context(Some(&conn_id), Some(ctx), ops)
832 .await;
833
834 match transact_result {
835 Ok(_) => Ok(ExecuteResponse::CreatedConnection),
836 Err(AdapterError::Catalog(mz_catalog::memory::error::Error {
837 kind: ErrorKind::Sql(CatalogError::ItemAlreadyExists(_, _)),
838 })) if plan.if_not_exists => {
839 if matches!(plan.connection.details, ConnectionDetails::Ssh { .. }) {
842 if let Err(e) = self.secrets_controller.delete(connection_id).await {
843 tracing::warn!(
844 "Dropping SSH secret for existing connection has encountered an error: {}",
845 e
846 );
847 } else {
848 self.caching_secrets_reader.invalidate(connection_id);
849 }
850 }
851 ctx.session()
852 .add_notice(AdapterNotice::ObjectAlreadyExists {
853 name: plan.name.item,
854 ty: "connection",
855 });
856 Ok(ExecuteResponse::CreatedConnection)
857 }
858 Err(err) => {
859 if matches!(plan.connection.details, ConnectionDetails::Ssh { .. }) {
862 if let Err(e) = self.secrets_controller.delete(connection_id).await {
863 tracing::warn!(
864 "Dropping SSH secret for failed connection has encountered an error: {}",
865 e
866 );
867 } else {
868 self.caching_secrets_reader.invalidate(connection_id);
869 }
870 }
871 Err(err)
872 }
873 }
874 }
875
876 #[instrument]
877 pub(super) async fn sequence_create_database(
878 &mut self,
879 session: &Session,
880 plan: plan::CreateDatabasePlan,
881 ) -> Result<ExecuteResponse, AdapterError> {
882 let ops = vec![catalog::Op::CreateDatabase {
883 name: plan.name.clone(),
884 owner_id: *session.current_role_id(),
885 }];
886 match self.catalog_transact(Some(session), ops).await {
887 Ok(_) => Ok(ExecuteResponse::CreatedDatabase),
888 Err(AdapterError::Catalog(mz_catalog::memory::error::Error {
889 kind: ErrorKind::Sql(CatalogError::DatabaseAlreadyExists(_)),
890 })) if plan.if_not_exists => {
891 session.add_notice(AdapterNotice::DatabaseAlreadyExists { name: plan.name });
892 Ok(ExecuteResponse::CreatedDatabase)
893 }
894 Err(err) => Err(err),
895 }
896 }
897
898 #[instrument]
899 pub(super) async fn sequence_create_schema(
900 &mut self,
901 session: &Session,
902 plan: plan::CreateSchemaPlan,
903 ) -> Result<ExecuteResponse, AdapterError> {
904 let op = catalog::Op::CreateSchema {
905 database_id: plan.database_spec,
906 schema_name: plan.schema_name.clone(),
907 owner_id: *session.current_role_id(),
908 };
909 match self.catalog_transact(Some(session), vec![op]).await {
910 Ok(_) => Ok(ExecuteResponse::CreatedSchema),
911 Err(AdapterError::Catalog(mz_catalog::memory::error::Error {
912 kind: ErrorKind::Sql(CatalogError::SchemaAlreadyExists(_)),
913 })) if plan.if_not_exists => {
914 session.add_notice(AdapterNotice::SchemaAlreadyExists {
915 name: plan.schema_name,
916 });
917 Ok(ExecuteResponse::CreatedSchema)
918 }
919 Err(err) => Err(err),
920 }
921 }
922
923 fn validate_role_attributes(&self, attributes: &RoleAttributesRaw) -> Result<(), AdapterError> {
925 if !ENABLE_PASSWORD_AUTH.get(self.catalog().system_config().dyncfgs()) {
926 if attributes.superuser.is_some() || attributes.password.is_some() {
927 return Err(AdapterError::UnavailableFeature {
928 feature: "SUPERUSER and PASSWORD attributes".to_string(),
929 docs: Some("https://materialize.com/docs/sql/create-role/#details".to_string()),
930 });
931 }
932 }
933 Ok(())
934 }
935
936 #[instrument]
937 pub(super) async fn sequence_create_role(
938 &mut self,
939 conn_id: Option<&ConnectionId>,
940 plan::CreateRolePlan { name, attributes }: plan::CreateRolePlan,
941 ) -> Result<ExecuteResponse, AdapterError> {
942 self.validate_role_attributes(&attributes.clone())?;
943 let op = catalog::Op::CreateRole { name, attributes };
944 self.catalog_transact_with_context(conn_id, None, vec![op])
945 .await
946 .map(|_| ExecuteResponse::CreatedRole)
947 }
948
949 #[instrument]
950 pub(super) async fn sequence_create_network_policy(
951 &mut self,
952 session: &Session,
953 plan::CreateNetworkPolicyPlan { name, rules }: plan::CreateNetworkPolicyPlan,
954 ) -> Result<ExecuteResponse, AdapterError> {
955 let op = catalog::Op::CreateNetworkPolicy {
956 rules,
957 name,
958 owner_id: *session.current_role_id(),
959 };
960 self.catalog_transact_with_context(Some(session.conn_id()), None, vec![op])
961 .await
962 .map(|_| ExecuteResponse::CreatedNetworkPolicy)
963 }
964
965 #[instrument]
966 pub(super) async fn sequence_alter_network_policy(
967 &mut self,
968 session: &Session,
969 plan::AlterNetworkPolicyPlan { id, name, rules }: plan::AlterNetworkPolicyPlan,
970 ) -> Result<ExecuteResponse, AdapterError> {
971 let current_network_policy_name =
973 self.catalog().system_config().default_network_policy_name();
974 if current_network_policy_name == name {
976 self.validate_alter_network_policy(session, &rules)?;
977 }
978
979 let op = catalog::Op::AlterNetworkPolicy {
980 id,
981 rules,
982 name,
983 owner_id: *session.current_role_id(),
984 };
985 self.catalog_transact_with_context(Some(session.conn_id()), None, vec![op])
986 .await
987 .map(|_| ExecuteResponse::AlteredObject(ObjectType::NetworkPolicy))
988 }
989
990 #[instrument]
991 pub(super) async fn sequence_create_table(
992 &mut self,
993 ctx: &mut ExecuteContext,
994 plan: plan::CreateTablePlan,
995 resolved_ids: ResolvedIds,
996 ) -> Result<ExecuteResponse, AdapterError> {
997 let plan::CreateTablePlan {
998 name,
999 table,
1000 if_not_exists,
1001 } = plan;
1002
1003 let conn_id = if table.temporary {
1004 Some(ctx.session().conn_id())
1005 } else {
1006 None
1007 };
1008 let (table_id, global_id) = self.allocate_user_id().await?;
1009 let collections = [(RelationVersion::root(), global_id)].into_iter().collect();
1010
1011 let data_source = match table.data_source {
1012 plan::TableDataSource::TableWrites { defaults } => {
1013 TableDataSource::TableWrites { defaults }
1014 }
1015 plan::TableDataSource::DataSource {
1016 desc: data_source_plan,
1017 timeline,
1018 } => match data_source_plan {
1019 plan::DataSourceDesc::IngestionExport {
1020 ingestion_id,
1021 external_reference,
1022 details,
1023 data_config,
1024 } => TableDataSource::DataSource {
1025 desc: DataSourceDesc::IngestionExport {
1026 ingestion_id,
1027 external_reference,
1028 details,
1029 data_config,
1030 },
1031 timeline,
1032 },
1033 plan::DataSourceDesc::Webhook {
1034 validate_using,
1035 body_format,
1036 headers,
1037 cluster_id,
1038 } => TableDataSource::DataSource {
1039 desc: DataSourceDesc::Webhook {
1040 validate_using,
1041 body_format,
1042 headers,
1043 cluster_id: cluster_id.expect("Webhook Tables must have cluster_id set"),
1044 },
1045 timeline,
1046 },
1047 o => {
1048 unreachable!("CREATE TABLE data source got {:?}", o)
1049 }
1050 },
1051 };
1052
1053 let is_webhook = if let TableDataSource::DataSource {
1054 desc: DataSourceDesc::Webhook { .. },
1055 timeline: _,
1056 } = &data_source
1057 {
1058 true
1059 } else {
1060 false
1061 };
1062
1063 let table = Table {
1064 create_sql: Some(table.create_sql),
1065 desc: table.desc,
1066 collections,
1067 conn_id: conn_id.cloned(),
1068 resolved_ids,
1069 custom_logical_compaction_window: table.compaction_window,
1070 is_retained_metrics_object: false,
1071 data_source,
1072 };
1073 let ops = vec![catalog::Op::CreateItem {
1074 id: table_id,
1075 name: name.clone(),
1076 item: CatalogItem::Table(table.clone()),
1077 owner_id: *ctx.session().current_role_id(),
1078 }];
1079
1080 let catalog_result = self
1081 .catalog_transact_with_ddl_transaction(ctx, ops, |_, _| Box::pin(async {}))
1082 .await;
1083
1084 if is_webhook {
1085 if let Some(url) = self.catalog().state().try_get_webhook_url(&table_id) {
1088 ctx.session()
1089 .add_notice(AdapterNotice::WebhookSourceCreated { url })
1090 }
1091 }
1092
1093 match catalog_result {
1094 Ok(()) => Ok(ExecuteResponse::CreatedTable),
1095 Err(AdapterError::Catalog(mz_catalog::memory::error::Error {
1096 kind: ErrorKind::Sql(CatalogError::ItemAlreadyExists(_, _)),
1097 })) if if_not_exists => {
1098 ctx.session_mut()
1099 .add_notice(AdapterNotice::ObjectAlreadyExists {
1100 name: name.item,
1101 ty: "table",
1102 });
1103 Ok(ExecuteResponse::CreatedTable)
1104 }
1105 Err(err) => Err(err),
1106 }
1107 }
1108
1109 #[instrument]
1110 pub(super) async fn sequence_create_sink(
1111 &mut self,
1112 ctx: ExecuteContext,
1113 plan: plan::CreateSinkPlan,
1114 resolved_ids: ResolvedIds,
1115 ) {
1116 let plan::CreateSinkPlan {
1117 name,
1118 sink,
1119 with_snapshot,
1120 if_not_exists,
1121 in_cluster,
1122 } = plan;
1123
1124 let (item_id, global_id) = return_if_err!(self.allocate_user_id().await, ctx);
1126
1127 let catalog_sink = Sink {
1128 create_sql: sink.create_sql,
1129 global_id,
1130 from: sink.from,
1131 connection: sink.connection,
1132 envelope: sink.envelope,
1133 version: sink.version,
1134 with_snapshot,
1135 resolved_ids,
1136 cluster_id: in_cluster,
1137 commit_interval: sink.commit_interval,
1138 };
1139
1140 let ops = vec![catalog::Op::CreateItem {
1141 id: item_id,
1142 name: name.clone(),
1143 item: CatalogItem::Sink(catalog_sink.clone()),
1144 owner_id: *ctx.session().current_role_id(),
1145 }];
1146
1147 let result = self.catalog_transact(Some(ctx.session()), ops).await;
1148
1149 match result {
1150 Ok(()) => {}
1151 Err(AdapterError::Catalog(mz_catalog::memory::error::Error {
1152 kind: ErrorKind::Sql(CatalogError::ItemAlreadyExists(_, _)),
1153 })) if if_not_exists => {
1154 ctx.session()
1155 .add_notice(AdapterNotice::ObjectAlreadyExists {
1156 name: name.item,
1157 ty: "sink",
1158 });
1159 ctx.retire(Ok(ExecuteResponse::CreatedSink));
1160 return;
1161 }
1162 Err(e) => {
1163 ctx.retire(Err(e));
1164 return;
1165 }
1166 };
1167
1168 self.create_storage_export(global_id, &catalog_sink)
1169 .await
1170 .unwrap_or_terminate("cannot fail to create exports");
1171
1172 self.initialize_storage_read_policies([item_id].into(), CompactionWindow::Default)
1173 .await;
1174
1175 ctx.retire(Ok(ExecuteResponse::CreatedSink))
1176 }
1177
1178 pub(super) fn validate_system_column_references(
1201 &self,
1202 uses_ambiguous_columns: bool,
1203 depends_on: &BTreeSet<GlobalId>,
1204 ) -> Result<(), AdapterError> {
1205 if uses_ambiguous_columns
1206 && depends_on
1207 .iter()
1208 .any(|id| id.is_system() && self.catalog().get_entry_by_global_id(id).is_relation())
1209 {
1210 Err(AdapterError::AmbiguousSystemColumnReference)
1211 } else {
1212 Ok(())
1213 }
1214 }
1215
1216 #[instrument]
1217 pub(super) async fn sequence_create_type(
1218 &mut self,
1219 session: &Session,
1220 plan: plan::CreateTypePlan,
1221 resolved_ids: ResolvedIds,
1222 ) -> Result<ExecuteResponse, AdapterError> {
1223 let (item_id, global_id) = self.allocate_user_id().await?;
1224 plan.typ
1226 .inner
1227 .desc(&self.catalog().for_session(session))
1228 .map_err(AdapterError::from)?;
1229 let typ = Type {
1230 create_sql: Some(plan.typ.create_sql),
1231 global_id,
1232 details: CatalogTypeDetails {
1233 array_id: None,
1234 typ: plan.typ.inner,
1235 pg_metadata: None,
1236 },
1237 resolved_ids,
1238 };
1239 let op = catalog::Op::CreateItem {
1240 id: item_id,
1241 name: plan.name,
1242 item: CatalogItem::Type(typ),
1243 owner_id: *session.current_role_id(),
1244 };
1245 match self.catalog_transact(Some(session), vec![op]).await {
1246 Ok(()) => Ok(ExecuteResponse::CreatedType),
1247 Err(err) => Err(err),
1248 }
1249 }
1250
1251 #[instrument]
1252 pub(super) async fn sequence_comment_on(
1253 &mut self,
1254 session: &Session,
1255 plan: plan::CommentPlan,
1256 ) -> Result<ExecuteResponse, AdapterError> {
1257 let op = catalog::Op::Comment {
1258 object_id: plan.object_id,
1259 sub_component: plan.sub_component,
1260 comment: plan.comment,
1261 };
1262 self.catalog_transact(Some(session), vec![op]).await?;
1263 Ok(ExecuteResponse::Comment)
1264 }
1265
1266 #[instrument]
1267 pub(super) async fn sequence_drop_objects(
1268 &mut self,
1269 ctx: &mut ExecuteContext,
1270 plan::DropObjectsPlan {
1271 drop_ids,
1272 object_type,
1273 referenced_ids,
1274 }: plan::DropObjectsPlan,
1275 ) -> Result<ExecuteResponse, AdapterError> {
1276 let referenced_ids_hashset = referenced_ids.iter().collect::<HashSet<_>>();
1277 let mut objects = Vec::new();
1278 for obj_id in &drop_ids {
1279 if !referenced_ids_hashset.contains(obj_id) {
1280 let object_info = ErrorMessageObjectDescription::from_id(
1281 obj_id,
1282 &self.catalog().for_session(ctx.session()),
1283 )
1284 .to_string();
1285 objects.push(object_info);
1286 }
1287 }
1288
1289 if !objects.is_empty() {
1290 ctx.session()
1291 .add_notice(AdapterNotice::CascadeDroppedObject { objects });
1292 }
1293
1294 let expr_cache_invalidate_ids: BTreeSet<_> = drop_ids
1296 .iter()
1297 .filter_map(|id| match id {
1298 ObjectId::Item(item_id) => Some(self.catalog().get_entry(item_id).global_ids()),
1299 _ => None,
1300 })
1301 .flatten()
1302 .collect();
1303
1304 let DropOps {
1305 ops,
1306 dropped_active_db,
1307 dropped_active_cluster,
1308 dropped_in_use_indexes,
1309 } = self.sequence_drop_common(ctx.session(), drop_ids)?;
1310
1311 self.catalog_transact_with_context(None, Some(ctx), ops)
1312 .await?;
1313
1314 if !expr_cache_invalidate_ids.is_empty() {
1316 let _fut = self.catalog().update_expression_cache(
1317 Default::default(),
1318 Default::default(),
1319 expr_cache_invalidate_ids,
1320 );
1321 }
1322
1323 fail::fail_point!("after_sequencer_drop_replica");
1324
1325 if dropped_active_db {
1326 ctx.session()
1327 .add_notice(AdapterNotice::DroppedActiveDatabase {
1328 name: ctx.session().vars().database().to_string(),
1329 });
1330 }
1331 if dropped_active_cluster {
1332 ctx.session()
1333 .add_notice(AdapterNotice::DroppedActiveCluster {
1334 name: ctx.session().vars().cluster().to_string(),
1335 });
1336 }
1337 for dropped_in_use_index in dropped_in_use_indexes {
1338 ctx.session()
1339 .add_notice(AdapterNotice::DroppedInUseIndex(dropped_in_use_index));
1340 self.metrics
1341 .optimization_notices
1342 .with_label_values(&["DroppedInUseIndex"])
1343 .inc_by(1);
1344 }
1345 Ok(ExecuteResponse::DroppedObject(object_type))
1346 }
1347
1348 fn validate_dropped_role_ownership(
1349 &self,
1350 session: &Session,
1351 dropped_roles: &BTreeMap<RoleId, &str>,
1352 ) -> Result<(), AdapterError> {
1353 fn privilege_check(
1354 privileges: &PrivilegeMap,
1355 dropped_roles: &BTreeMap<RoleId, &str>,
1356 dependent_objects: &mut BTreeMap<String, Vec<String>>,
1357 object_id: &SystemObjectId,
1358 catalog: &ConnCatalog,
1359 ) {
1360 for privilege in privileges.all_values() {
1361 if let Some(role_name) = dropped_roles.get(&privilege.grantee) {
1362 let grantor_name = catalog.get_role(&privilege.grantor).name();
1363 let object_description =
1364 ErrorMessageObjectDescription::from_sys_id(object_id, catalog);
1365 dependent_objects
1366 .entry(role_name.to_string())
1367 .or_default()
1368 .push(format!(
1369 "privileges on {object_description} granted by {grantor_name}",
1370 ));
1371 }
1372 if let Some(role_name) = dropped_roles.get(&privilege.grantor) {
1373 let grantee_name = catalog.get_role(&privilege.grantee).name();
1374 let object_description =
1375 ErrorMessageObjectDescription::from_sys_id(object_id, catalog);
1376 dependent_objects
1377 .entry(role_name.to_string())
1378 .or_default()
1379 .push(format!(
1380 "privileges granted on {object_description} to {grantee_name}"
1381 ));
1382 }
1383 }
1384 }
1385
1386 let catalog = self.catalog().for_session(session);
1387 let mut dependent_objects: BTreeMap<_, Vec<_>> = BTreeMap::new();
1388 for entry in self.catalog.entries() {
1389 let id = SystemObjectId::Object(entry.id().into());
1390 if let Some(role_name) = dropped_roles.get(entry.owner_id()) {
1391 let object_description = ErrorMessageObjectDescription::from_sys_id(&id, &catalog);
1392 dependent_objects
1393 .entry(role_name.to_string())
1394 .or_default()
1395 .push(format!("owner of {object_description}"));
1396 }
1397 privilege_check(
1398 entry.privileges(),
1399 dropped_roles,
1400 &mut dependent_objects,
1401 &id,
1402 &catalog,
1403 );
1404 }
1405 for database in self.catalog.databases() {
1406 let database_id = SystemObjectId::Object(database.id().into());
1407 if let Some(role_name) = dropped_roles.get(&database.owner_id) {
1408 let object_description =
1409 ErrorMessageObjectDescription::from_sys_id(&database_id, &catalog);
1410 dependent_objects
1411 .entry(role_name.to_string())
1412 .or_default()
1413 .push(format!("owner of {object_description}"));
1414 }
1415 privilege_check(
1416 &database.privileges,
1417 dropped_roles,
1418 &mut dependent_objects,
1419 &database_id,
1420 &catalog,
1421 );
1422 for schema in database.schemas_by_id.values() {
1423 let schema_id = SystemObjectId::Object(
1424 (ResolvedDatabaseSpecifier::Id(database.id()), *schema.id()).into(),
1425 );
1426 if let Some(role_name) = dropped_roles.get(&schema.owner_id) {
1427 let object_description =
1428 ErrorMessageObjectDescription::from_sys_id(&schema_id, &catalog);
1429 dependent_objects
1430 .entry(role_name.to_string())
1431 .or_default()
1432 .push(format!("owner of {object_description}"));
1433 }
1434 privilege_check(
1435 &schema.privileges,
1436 dropped_roles,
1437 &mut dependent_objects,
1438 &schema_id,
1439 &catalog,
1440 );
1441 }
1442 }
1443 for cluster in self.catalog.clusters() {
1444 let cluster_id = SystemObjectId::Object(cluster.id().into());
1445 if let Some(role_name) = dropped_roles.get(&cluster.owner_id) {
1446 let object_description =
1447 ErrorMessageObjectDescription::from_sys_id(&cluster_id, &catalog);
1448 dependent_objects
1449 .entry(role_name.to_string())
1450 .or_default()
1451 .push(format!("owner of {object_description}"));
1452 }
1453 privilege_check(
1454 &cluster.privileges,
1455 dropped_roles,
1456 &mut dependent_objects,
1457 &cluster_id,
1458 &catalog,
1459 );
1460 for replica in cluster.replicas() {
1461 if let Some(role_name) = dropped_roles.get(&replica.owner_id) {
1462 let replica_id =
1463 SystemObjectId::Object((replica.cluster_id(), replica.replica_id()).into());
1464 let object_description =
1465 ErrorMessageObjectDescription::from_sys_id(&replica_id, &catalog);
1466 dependent_objects
1467 .entry(role_name.to_string())
1468 .or_default()
1469 .push(format!("owner of {object_description}"));
1470 }
1471 }
1472 }
1473 privilege_check(
1474 self.catalog().system_privileges(),
1475 dropped_roles,
1476 &mut dependent_objects,
1477 &SystemObjectId::System,
1478 &catalog,
1479 );
1480 for (default_privilege_object, default_privilege_acl_items) in
1481 self.catalog.default_privileges()
1482 {
1483 if let Some(role_name) = dropped_roles.get(&default_privilege_object.role_id) {
1484 dependent_objects
1485 .entry(role_name.to_string())
1486 .or_default()
1487 .push(format!(
1488 "default privileges on {}S created by {}",
1489 default_privilege_object.object_type, role_name
1490 ));
1491 }
1492 for default_privilege_acl_item in default_privilege_acl_items {
1493 if let Some(role_name) = dropped_roles.get(&default_privilege_acl_item.grantee) {
1494 dependent_objects
1495 .entry(role_name.to_string())
1496 .or_default()
1497 .push(format!(
1498 "default privileges on {}S granted to {}",
1499 default_privilege_object.object_type, role_name
1500 ));
1501 }
1502 }
1503 }
1504
1505 if !dependent_objects.is_empty() {
1506 Err(AdapterError::DependentObject(dependent_objects))
1507 } else {
1508 Ok(())
1509 }
1510 }
1511
1512 #[instrument]
1513 pub(super) async fn sequence_drop_owned(
1514 &mut self,
1515 session: &Session,
1516 plan: plan::DropOwnedPlan,
1517 ) -> Result<ExecuteResponse, AdapterError> {
1518 for role_id in &plan.role_ids {
1519 self.catalog().ensure_not_reserved_role(role_id)?;
1520 }
1521
1522 let mut privilege_revokes = plan.privilege_revokes;
1523
1524 let session_catalog = self.catalog().for_session(session);
1526 if rbac::is_rbac_enabled_for_session(session_catalog.system_vars(), session)
1527 && !session.is_superuser()
1528 {
1529 let role_membership =
1531 session_catalog.collect_role_membership(session.current_role_id());
1532 let invalid_revokes: BTreeSet<_> = privilege_revokes
1533 .extract_if(.., |(_, privilege)| {
1534 !role_membership.contains(&privilege.grantor)
1535 })
1536 .map(|(object_id, _)| object_id)
1537 .collect();
1538 for invalid_revoke in invalid_revokes {
1539 let object_description =
1540 ErrorMessageObjectDescription::from_sys_id(&invalid_revoke, &session_catalog);
1541 session.add_notice(AdapterNotice::CannotRevoke { object_description });
1542 }
1543 }
1544
1545 let mut privilege_revokes_by_target: BTreeMap<SystemObjectId, Vec<MzAclItem>> =
1547 BTreeMap::new();
1548 for (object_id, privilege) in privilege_revokes {
1549 privilege_revokes_by_target
1550 .entry(object_id)
1551 .or_default()
1552 .push(privilege);
1553 }
1554 let privilege_revoke_ops =
1555 privilege_revokes_by_target
1556 .into_iter()
1557 .map(|(target_id, privileges)| catalog::Op::UpdatePrivilege {
1558 target_id,
1559 privileges,
1560 variant: UpdatePrivilegeVariant::Revoke,
1561 });
1562 let default_privilege_revoke_ops = plan.default_privilege_revokes.into_iter().map(
1563 |(privilege_object, privilege_acl_item)| catalog::Op::UpdateDefaultPrivilege {
1564 privilege_object,
1565 privilege_acl_item,
1566 variant: UpdatePrivilegeVariant::Revoke,
1567 },
1568 );
1569 let DropOps {
1570 ops: drop_ops,
1571 dropped_active_db,
1572 dropped_active_cluster,
1573 dropped_in_use_indexes,
1574 } = self.sequence_drop_common(session, plan.drop_ids)?;
1575
1576 let ops = privilege_revoke_ops
1577 .chain(default_privilege_revoke_ops)
1578 .chain(drop_ops.into_iter())
1579 .collect();
1580
1581 self.catalog_transact(Some(session), ops).await?;
1582
1583 if dropped_active_db {
1584 session.add_notice(AdapterNotice::DroppedActiveDatabase {
1585 name: session.vars().database().to_string(),
1586 });
1587 }
1588 if dropped_active_cluster {
1589 session.add_notice(AdapterNotice::DroppedActiveCluster {
1590 name: session.vars().cluster().to_string(),
1591 });
1592 }
1593 for dropped_in_use_index in dropped_in_use_indexes {
1594 session.add_notice(AdapterNotice::DroppedInUseIndex(dropped_in_use_index));
1595 }
1596 Ok(ExecuteResponse::DroppedOwned)
1597 }
1598
1599 fn sequence_drop_common(
1600 &self,
1601 session: &Session,
1602 ids: Vec<ObjectId>,
1603 ) -> Result<DropOps, AdapterError> {
1604 let mut dropped_active_db = false;
1605 let mut dropped_active_cluster = false;
1606 let mut dropped_in_use_indexes = Vec::new();
1607 let mut dropped_roles = BTreeMap::new();
1608 let mut dropped_databases = BTreeSet::new();
1609 let mut dropped_schemas = BTreeSet::new();
1610 let mut role_revokes = BTreeSet::new();
1614 let mut default_privilege_revokes = BTreeSet::new();
1617
1618 let mut clusters_to_drop = BTreeSet::new();
1620
1621 let ids_set = ids.iter().collect::<BTreeSet<_>>();
1622 for id in &ids {
1623 match id {
1624 ObjectId::Database(id) => {
1625 let name = self.catalog().get_database(id).name();
1626 if name == session.vars().database() {
1627 dropped_active_db = true;
1628 }
1629 dropped_databases.insert(id);
1630 }
1631 ObjectId::Schema((_, spec)) => {
1632 if let SchemaSpecifier::Id(id) = spec {
1633 dropped_schemas.insert(id);
1634 }
1635 }
1636 ObjectId::Cluster(id) => {
1637 clusters_to_drop.insert(*id);
1638 if let Some(active_id) = self
1639 .catalog()
1640 .active_cluster(session)
1641 .ok()
1642 .map(|cluster| cluster.id())
1643 {
1644 if id == &active_id {
1645 dropped_active_cluster = true;
1646 }
1647 }
1648 }
1649 ObjectId::Role(id) => {
1650 let role = self.catalog().get_role(id);
1651 let name = role.name();
1652 dropped_roles.insert(*id, name);
1653 for (group_id, grantor_id) in &role.membership.map {
1655 role_revokes.insert((*group_id, *id, *grantor_id));
1656 }
1657 }
1658 ObjectId::Item(id) => {
1659 if let Some(index) = self.catalog().get_entry(id).index() {
1660 let humanizer = self.catalog().for_session(session);
1661 let dependants = self
1662 .controller
1663 .compute
1664 .collection_reverse_dependencies(index.cluster_id, index.global_id())
1665 .ok()
1666 .into_iter()
1667 .flatten()
1668 .filter(|dependant_id| {
1669 if dependant_id.is_transient() {
1676 return false;
1677 }
1678 let Some(dependent_id) = humanizer
1680 .try_get_item_by_global_id(dependant_id)
1681 .map(|item| item.id())
1682 else {
1683 return false;
1684 };
1685 !ids_set.contains(&ObjectId::Item(dependent_id))
1688 })
1689 .flat_map(|dependant_id| {
1690 humanizer.humanize_id(dependant_id)
1694 })
1695 .collect_vec();
1696 if !dependants.is_empty() {
1697 dropped_in_use_indexes.push(DroppedInUseIndex {
1698 index_name: humanizer
1699 .humanize_id(index.global_id())
1700 .unwrap_or_else(|| id.to_string()),
1701 dependant_objects: dependants,
1702 });
1703 }
1704 }
1705 }
1706 _ => {}
1707 }
1708 }
1709
1710 for id in &ids {
1711 match id {
1712 ObjectId::ClusterReplica((cluster_id, replica_id)) => {
1716 if !clusters_to_drop.contains(cluster_id) {
1717 let cluster = self.catalog.get_cluster(*cluster_id);
1718 if cluster.is_managed() {
1719 let replica =
1720 cluster.replica(*replica_id).expect("Catalog out of sync");
1721 if !replica.config.location.internal() {
1722 coord_bail!("cannot drop replica of managed cluster");
1723 }
1724 }
1725 }
1726 }
1727 _ => {}
1728 }
1729 }
1730
1731 for role_id in dropped_roles.keys() {
1732 self.catalog().ensure_not_reserved_role(role_id)?;
1733 }
1734 self.validate_dropped_role_ownership(session, &dropped_roles)?;
1735 let dropped_role_ids: BTreeSet<_> = dropped_roles.keys().collect();
1737 for role in self.catalog().user_roles() {
1738 for dropped_role_id in
1739 dropped_role_ids.intersection(&role.membership.map.keys().collect())
1740 {
1741 role_revokes.insert((
1742 **dropped_role_id,
1743 role.id(),
1744 *role
1745 .membership
1746 .map
1747 .get(*dropped_role_id)
1748 .expect("included in keys above"),
1749 ));
1750 }
1751 }
1752
1753 for (default_privilege_object, default_privilege_acls) in
1754 self.catalog().default_privileges()
1755 {
1756 if matches!(
1757 &default_privilege_object.database_id,
1758 Some(database_id) if dropped_databases.contains(database_id),
1759 ) || matches!(
1760 &default_privilege_object.schema_id,
1761 Some(schema_id) if dropped_schemas.contains(schema_id),
1762 ) {
1763 for default_privilege_acl in default_privilege_acls {
1764 default_privilege_revokes.insert((
1765 default_privilege_object.clone(),
1766 default_privilege_acl.clone(),
1767 ));
1768 }
1769 }
1770 }
1771
1772 let ops = role_revokes
1773 .into_iter()
1774 .map(|(role_id, member_id, grantor_id)| catalog::Op::RevokeRole {
1775 role_id,
1776 member_id,
1777 grantor_id,
1778 })
1779 .chain(default_privilege_revokes.into_iter().map(
1780 |(privilege_object, privilege_acl_item)| catalog::Op::UpdateDefaultPrivilege {
1781 privilege_object,
1782 privilege_acl_item,
1783 variant: UpdatePrivilegeVariant::Revoke,
1784 },
1785 ))
1786 .chain(iter::once(catalog::Op::DropObjects(
1787 ids.into_iter()
1788 .map(DropObjectInfo::manual_drop_from_object_id)
1789 .collect(),
1790 )))
1791 .collect();
1792
1793 Ok(DropOps {
1794 ops,
1795 dropped_active_db,
1796 dropped_active_cluster,
1797 dropped_in_use_indexes,
1798 })
1799 }
1800
1801 pub(super) fn sequence_explain_schema(
1802 &self,
1803 ExplainSinkSchemaPlan { json_schema, .. }: ExplainSinkSchemaPlan,
1804 ) -> Result<ExecuteResponse, AdapterError> {
1805 let json_value: serde_json::Value = serde_json::from_str(&json_schema).map_err(|e| {
1806 AdapterError::Explain(mz_repr::explain::ExplainError::SerdeJsonError(e))
1807 })?;
1808
1809 let json_string = json_string(&json_value);
1810 let row = Row::pack_slice(&[Datum::String(&json_string)]);
1811 Ok(Self::send_immediate_rows(row))
1812 }
1813
1814 pub(super) fn sequence_show_all_variables(
1815 &self,
1816 session: &Session,
1817 ) -> Result<ExecuteResponse, AdapterError> {
1818 let mut rows = viewable_variables(self.catalog().state(), session)
1819 .map(|v| (v.name(), v.value(), v.description()))
1820 .collect::<Vec<_>>();
1821 rows.sort_by_cached_key(|(name, _, _)| name.to_lowercase());
1822
1823 let rows: Vec<_> = rows
1825 .into_iter()
1826 .map(|(name, val, desc)| {
1827 Row::pack_slice(&[
1828 Datum::String(name),
1829 Datum::String(&val),
1830 Datum::String(desc),
1831 ])
1832 })
1833 .collect();
1834 Ok(Self::send_immediate_rows(rows))
1835 }
1836
1837 pub(super) fn sequence_show_variable(
1838 &self,
1839 session: &Session,
1840 plan: plan::ShowVariablePlan,
1841 ) -> Result<ExecuteResponse, AdapterError> {
1842 if &plan.name == SCHEMA_ALIAS {
1843 let schemas = self.catalog.resolve_search_path(session);
1844 let schema = schemas.first();
1845 return match schema {
1846 Some((database_spec, schema_spec)) => {
1847 let schema_name = &self
1848 .catalog
1849 .get_schema(database_spec, schema_spec, session.conn_id())
1850 .name()
1851 .schema;
1852 let row = Row::pack_slice(&[Datum::String(schema_name)]);
1853 Ok(Self::send_immediate_rows(row))
1854 }
1855 None => {
1856 if session.vars().current_object_missing_warnings() {
1857 session.add_notice(AdapterNotice::NoResolvableSearchPathSchema {
1858 search_path: session
1859 .vars()
1860 .search_path()
1861 .into_iter()
1862 .map(|schema| schema.to_string())
1863 .collect(),
1864 });
1865 }
1866 Ok(Self::send_immediate_rows(Row::pack_slice(&[Datum::Null])))
1867 }
1868 };
1869 }
1870
1871 let variable = session
1872 .vars()
1873 .get(self.catalog().system_config(), &plan.name)
1874 .or_else(|_| self.catalog().system_config().get(&plan.name))?;
1875
1876 variable.visible(session.user(), self.catalog().system_config())?;
1879
1880 let row = Row::pack_slice(&[Datum::String(&variable.value())]);
1881 if variable.name() == vars::DATABASE.name()
1882 && matches!(
1883 self.catalog().resolve_database(&variable.value()),
1884 Err(CatalogError::UnknownDatabase(_))
1885 )
1886 && session.vars().current_object_missing_warnings()
1887 {
1888 let name = variable.value();
1889 session.add_notice(AdapterNotice::DatabaseDoesNotExist { name });
1890 } else if variable.name() == vars::CLUSTER.name()
1891 && matches!(
1892 self.catalog().resolve_cluster(&variable.value()),
1893 Err(CatalogError::UnknownCluster(_))
1894 )
1895 && session.vars().current_object_missing_warnings()
1896 {
1897 let name = variable.value();
1898 session.add_notice(AdapterNotice::ClusterDoesNotExist { name });
1899 }
1900 Ok(Self::send_immediate_rows(row))
1901 }
1902
1903 #[instrument]
1904 pub(super) async fn sequence_inspect_shard(
1905 &self,
1906 session: &Session,
1907 plan: plan::InspectShardPlan,
1908 ) -> Result<ExecuteResponse, AdapterError> {
1909 if !session.user().is_internal() {
1912 return Err(AdapterError::Unauthorized(
1913 rbac::UnauthorizedError::MzSystem {
1914 action: "inspect".into(),
1915 },
1916 ));
1917 }
1918 let state = self
1919 .controller
1920 .storage
1921 .inspect_persist_state(plan.id)
1922 .await?;
1923 let jsonb = Jsonb::from_serde_json(state)?;
1924 Ok(Self::send_immediate_rows(jsonb.into_row()))
1925 }
1926
1927 #[instrument]
1928 pub(super) fn sequence_set_variable(
1929 &self,
1930 session: &mut Session,
1931 plan: plan::SetVariablePlan,
1932 ) -> Result<ExecuteResponse, AdapterError> {
1933 let (name, local) = (plan.name, plan.local);
1934 if &name == TRANSACTION_ISOLATION_VAR_NAME {
1935 self.validate_set_isolation_level(session)?;
1936 }
1937 if &name == vars::CLUSTER.name() {
1938 self.validate_set_cluster(session)?;
1939 }
1940
1941 let vars = session.vars_mut();
1942 let values = match plan.value {
1943 plan::VariableValue::Default => None,
1944 plan::VariableValue::Values(values) => Some(values),
1945 };
1946
1947 match values {
1948 Some(values) => {
1949 vars.set(
1950 self.catalog().system_config(),
1951 &name,
1952 VarInput::SqlSet(&values),
1953 local,
1954 )?;
1955
1956 let vars = session.vars();
1957
1958 if name == vars::OLD_AUTO_ROUTE_CATALOG_QUERIES {
1961 session.add_notice(AdapterNotice::AutoRouteIntrospectionQueriesUsage);
1962 } else if name == vars::CLUSTER.name()
1963 && values[0] == vars::OLD_CATALOG_SERVER_CLUSTER
1964 {
1965 session.add_notice(AdapterNotice::IntrospectionClusterUsage);
1966 }
1967
1968 if name.as_str() == vars::DATABASE.name()
1970 && matches!(
1971 self.catalog().resolve_database(vars.database()),
1972 Err(CatalogError::UnknownDatabase(_))
1973 )
1974 && session.vars().current_object_missing_warnings()
1975 {
1976 let name = vars.database().to_string();
1977 session.add_notice(AdapterNotice::DatabaseDoesNotExist { name });
1978 } else if name.as_str() == vars::CLUSTER.name()
1979 && matches!(
1980 self.catalog().resolve_cluster(vars.cluster()),
1981 Err(CatalogError::UnknownCluster(_))
1982 )
1983 && session.vars().current_object_missing_warnings()
1984 {
1985 let name = vars.cluster().to_string();
1986 session.add_notice(AdapterNotice::ClusterDoesNotExist { name });
1987 } else if name.as_str() == TRANSACTION_ISOLATION_VAR_NAME {
1988 let v = values.into_first().to_lowercase();
1989 if v == IsolationLevel::ReadUncommitted.as_variant_str()
1990 || v == IsolationLevel::ReadCommitted.as_variant_str()
1991 || v == IsolationLevel::RepeatableRead.as_variant_str()
1992 {
1993 session.add_notice(AdapterNotice::UnimplementedIsolationLevel {
1994 isolation_level: v,
1995 });
1996 } else if v == IsolationLevel::StrongSessionSerializable.as_variant_str() {
1997 session.add_notice(AdapterNotice::StrongSessionSerializable);
1998 }
1999 }
2000
2001 if (name.as_str() == TRANSACTION_ISOLATION_VAR_NAME
2005 || name.as_str() == vars::REAL_TIME_RECENCY.name())
2006 && session
2007 .vars()
2008 .transaction_isolation()
2009 .is_bounded_staleness()
2010 && session.vars().real_time_recency()
2011 {
2012 return Err(AdapterError::BoundedStalenessRealTimeRecencyConflict);
2013 }
2014 }
2015 None => vars.reset(self.catalog().system_config(), &name, local)?,
2016 }
2017
2018 Ok(ExecuteResponse::SetVariable { name, reset: false })
2019 }
2020
2021 pub(super) fn sequence_reset_variable(
2022 &self,
2023 session: &mut Session,
2024 plan: plan::ResetVariablePlan,
2025 ) -> Result<ExecuteResponse, AdapterError> {
2026 let name = plan.name;
2027 if &name == TRANSACTION_ISOLATION_VAR_NAME {
2028 self.validate_set_isolation_level(session)?;
2029 }
2030 if &name == vars::CLUSTER.name() {
2031 self.validate_set_cluster(session)?;
2032 }
2033 session
2034 .vars_mut()
2035 .reset(self.catalog().system_config(), &name, false)?;
2036 Ok(ExecuteResponse::SetVariable { name, reset: true })
2037 }
2038
2039 pub(super) fn sequence_set_transaction(
2040 &self,
2041 session: &mut Session,
2042 plan: plan::SetTransactionPlan,
2043 ) -> Result<ExecuteResponse, AdapterError> {
2044 for mode in plan.modes {
2046 match mode {
2047 TransactionMode::AccessMode(_) => {
2048 return Err(AdapterError::Unsupported("SET TRANSACTION <access-mode>"));
2049 }
2050 TransactionMode::IsolationLevel(isolation_level) => {
2051 self.validate_set_isolation_level(session)?;
2052
2053 session.vars_mut().set(
2054 self.catalog().system_config(),
2055 TRANSACTION_ISOLATION_VAR_NAME,
2056 VarInput::Flat(&isolation_level.to_ast_string_stable()),
2057 plan.local,
2058 )?
2059 }
2060 }
2061 }
2062 Ok(ExecuteResponse::SetVariable {
2063 name: TRANSACTION_ISOLATION_VAR_NAME.to_string(),
2064 reset: false,
2065 })
2066 }
2067
2068 fn validate_set_isolation_level(&self, session: &Session) -> Result<(), AdapterError> {
2069 if session.transaction().contains_ops() {
2070 Err(AdapterError::InvalidSetIsolationLevel)
2071 } else {
2072 Ok(())
2073 }
2074 }
2075
2076 fn validate_set_cluster(&self, session: &Session) -> Result<(), AdapterError> {
2077 if session.transaction().contains_ops() {
2078 Err(AdapterError::InvalidSetCluster)
2079 } else {
2080 Ok(())
2081 }
2082 }
2083
2084 #[instrument]
2085 pub(super) async fn sequence_end_transaction(
2086 &mut self,
2087 mut ctx: ExecuteContext,
2088 mut action: EndTransactionAction,
2089 ) {
2090 if let (EndTransactionAction::Commit, TransactionStatus::Failed(_)) =
2092 (&action, ctx.session().transaction())
2093 {
2094 action = EndTransactionAction::Rollback;
2095 }
2096 let response = match action {
2097 EndTransactionAction::Commit => Ok(PendingTxnResponse::Committed {
2098 params: BTreeMap::new(),
2099 }),
2100 EndTransactionAction::Rollback => Ok(PendingTxnResponse::Rolledback {
2101 params: BTreeMap::new(),
2102 }),
2103 };
2104
2105 let result = self.sequence_end_transaction_inner(&mut ctx, action).await;
2106
2107 let (response, action) = match result {
2108 Ok((Some(TransactionOps::Writes(writes)), _)) if writes.is_empty() => {
2109 (response, action)
2110 }
2111 Ok((Some(TransactionOps::Writes(writes)), write_lock_guards)) => {
2112 let validated_locks = match write_lock_guards {
2116 None => None,
2117 Some(locks) => match locks.validate(writes.iter().map(|op| op.id)) {
2118 Ok(locks) => Some(locks),
2119 Err(missing) => {
2120 tracing::error!(?missing, "programming error, missing write locks");
2121 return ctx.retire(Err(AdapterError::WrongSetOfLocks));
2122 }
2123 },
2124 };
2125
2126 let mut collected_writes: BTreeMap<CatalogItemId, SmallVec<_>> = BTreeMap::new();
2127 for WriteOp { id, rows } in writes {
2128 let total_rows = collected_writes.entry(id).or_default();
2129 total_rows.push(rows);
2130 }
2131
2132 self.submit_write(PendingWriteTxn::User {
2133 span: Span::current(),
2134 writes: collected_writes,
2135 write_locks: validated_locks,
2136 responder: UserWriteResponder::Session(PendingTxn {
2137 ctx,
2138 response,
2139 action,
2140 }),
2141 });
2142 return;
2143 }
2144 Ok((
2145 Some(TransactionOps::Peeks {
2146 determination,
2147 requires_linearization: RequireLinearization::Required,
2148 ..
2149 }),
2150 _,
2151 )) if ctx.session().vars().transaction_isolation()
2152 == &IsolationLevel::StrictSerializable =>
2153 {
2154 let conn_id = ctx.session().conn_id().clone();
2155 let pending_read_txn = PendingReadTxn {
2156 txn: PendingRead::Read {
2157 txn: PendingTxn {
2158 ctx,
2159 response,
2160 action,
2161 },
2162 },
2163 timestamp_context: determination.timestamp_context,
2164 created: Instant::now(),
2165 num_requeues: 0,
2166 otel_ctx: OpenTelemetryContext::obtain(),
2167 };
2168 self.strict_serializable_reads_tx
2169 .send((conn_id, pending_read_txn))
2170 .expect("sending to strict_serializable_reads_tx cannot fail");
2171 return;
2172 }
2173 Ok((
2174 Some(TransactionOps::Peeks {
2175 determination,
2176 requires_linearization: RequireLinearization::Required,
2177 ..
2178 }),
2179 _,
2180 )) if ctx.session().vars().transaction_isolation()
2181 == &IsolationLevel::StrongSessionSerializable =>
2182 {
2183 if let Some((timeline, ts)) = determination.timestamp_context.timeline_timestamp() {
2184 ctx.session_mut()
2185 .ensure_timestamp_oracle(timeline.clone())
2186 .apply_write(*ts);
2187 }
2188 (response, action)
2189 }
2190 Ok((Some(TransactionOps::SingleStatement { stmt, params }), _)) => {
2191 self.internal_cmd_tx
2192 .send(Message::ExecuteSingleStatementTransaction {
2193 ctx,
2194 otel_ctx: OpenTelemetryContext::obtain(),
2195 stmt,
2196 params,
2197 })
2198 .expect("must send");
2199 return;
2200 }
2201 Ok((_, _)) => (response, action),
2202 Err(err) => (Err(err), EndTransactionAction::Rollback),
2203 };
2204 let changed = ctx.session_mut().vars_mut().end_transaction(action);
2205 let response = response.map(|mut r| {
2207 r.extend_params(changed);
2208 ExecuteResponse::from(r)
2209 });
2210
2211 ctx.retire(response);
2212 }
2213
2214 #[instrument]
2215 async fn sequence_end_transaction_inner(
2216 &mut self,
2217 ctx: &mut ExecuteContext,
2218 action: EndTransactionAction,
2219 ) -> Result<(Option<TransactionOps>, Option<WriteLocks>), AdapterError> {
2220 let (txn, retire_notify) = self.clear_transaction(ctx.session_mut()).await;
2221 ctx.delay_response_until(retire_notify);
2222
2223 if let EndTransactionAction::Commit = action {
2224 if let (Some(mut ops), write_lock_guards) = txn.into_ops_and_lock_guard() {
2225 match &mut ops {
2226 TransactionOps::Writes(writes) => {
2227 for WriteOp { id, .. } in &mut writes.iter() {
2228 let _ = self.catalog().try_get_entry(id).ok_or_else(|| {
2230 AdapterError::Catalog(mz_catalog::memory::error::Error {
2231 kind: ErrorKind::Sql(CatalogError::UnknownItem(id.to_string())),
2232 })
2233 })?;
2234 }
2235
2236 writes.retain(|WriteOp { rows, .. }| !rows.is_empty());
2238 }
2239 TransactionOps::DDL {
2240 ops,
2241 state: _,
2242 side_effects,
2243 revision,
2244 snapshot: _,
2245 } => {
2246 if *revision != self.catalog().transient_revision() {
2248 return Err(AdapterError::DDLTransactionRace);
2249 }
2250 let ops = std::mem::take(ops);
2252 let side_effects = std::mem::take(side_effects);
2253 self.catalog_transact_with_side_effects(
2254 Some(ctx),
2255 ops,
2256 move |a, mut ctx| {
2257 Box::pin(async move {
2258 for side_effect in side_effects {
2259 side_effect(a, ctx.as_mut().map(|ctx| &mut **ctx)).await;
2260 }
2261 })
2262 },
2263 )
2264 .await?;
2265 }
2266 _ => (),
2267 }
2268 return Ok((Some(ops), write_lock_guards));
2269 }
2270 }
2271
2272 Ok((None, None))
2273 }
2274
2275 pub(super) async fn sequence_side_effecting_func(
2276 &mut self,
2277 ctx: ExecuteContext,
2278 plan: SideEffectingFunc,
2279 ) {
2280 match plan {
2281 SideEffectingFunc::PgCancelBackend { connection_id } => {
2282 let Some(connection_id) = connection_id else {
2283 ctx.retire(Ok(Self::send_immediate_rows(Row::pack_slice(&[
2286 Datum::Null,
2287 ]))));
2288 return;
2289 };
2290
2291 if ctx.session().conn_id().unhandled() == connection_id {
2292 ctx.retire(Err(AdapterError::Canceled));
2296 return;
2297 }
2298
2299 let res = if let Some((id_handle, _conn_meta)) =
2300 self.active_conns.get_key_value(&connection_id)
2301 {
2302 self.handle_privileged_cancel(id_handle.clone()).await;
2304 Datum::True
2305 } else {
2306 Datum::False
2307 };
2308 ctx.retire(Ok(Self::send_immediate_rows(Row::pack_slice(&[res]))));
2309 }
2310 }
2311 }
2312
2313 pub(crate) async fn execute_side_effecting_func(
2324 &mut self,
2325 plan: SideEffectingFunc,
2326 conn_id: ConnectionId,
2327 ) -> Result<ExecuteResponse, AdapterError> {
2328 match plan {
2329 SideEffectingFunc::PgCancelBackend { connection_id } => {
2330 let Some(connection_id) = connection_id else {
2331 return Ok(Self::send_immediate_rows(Row::pack_slice(&[Datum::Null])));
2334 };
2335
2336 if conn_id.unhandled() == connection_id {
2337 return Err(AdapterError::Canceled);
2341 }
2342
2343 if let Some((id_handle, _conn_meta)) =
2348 self.active_conns.get_key_value(&connection_id)
2349 {
2350 self.handle_privileged_cancel(id_handle.clone()).await;
2351 Ok(Self::send_immediate_rows(Row::pack_slice(&[Datum::True])))
2352 } else {
2353 Ok(Self::send_immediate_rows(Row::pack_slice(&[Datum::False])))
2355 }
2356 }
2357 }
2358 }
2359
2360 pub(crate) async fn determine_real_time_recent_timestamp(
2364 &self,
2365 source_ids: impl Iterator<Item = GlobalId>,
2366 real_time_recency_timeout: Duration,
2367 ) -> Result<Option<RtrTimestampFuture>, AdapterError> {
2368 let item_ids = source_ids
2369 .map(|gid| {
2370 self.catalog
2371 .try_resolve_item_id(&gid)
2372 .ok_or_else(|| AdapterError::RtrDropFailure(gid.to_string()))
2373 })
2374 .collect::<Result<Vec<_>, _>>()?;
2375
2376 let mut to_visit = VecDeque::from_iter(item_ids.into_iter().filter(CatalogItemId::is_user));
2382 if to_visit.is_empty() {
2385 return Ok(None);
2386 }
2387
2388 let mut timestamp_objects = BTreeSet::new();
2389
2390 while let Some(id) = to_visit.pop_front() {
2391 timestamp_objects.insert(id);
2392 to_visit.extend(
2393 self.catalog()
2394 .get_entry(&id)
2395 .uses()
2396 .into_iter()
2397 .filter(|id| !timestamp_objects.contains(id) && id.is_user()),
2398 );
2399 }
2400 let timestamp_objects = timestamp_objects
2401 .into_iter()
2402 .flat_map(|item_id| self.catalog().get_entry(&item_id).global_ids())
2403 .collect();
2404
2405 let r = self
2406 .controller
2407 .determine_real_time_recent_timestamp(timestamp_objects, real_time_recency_timeout)
2408 .await?;
2409
2410 Ok(Some(r))
2411 }
2412
2413 pub(crate) async fn await_real_time_recent_timestamp<F>(
2414 catalog: Arc<Catalog>,
2415 fut: F,
2416 ) -> Result<Timestamp, AdapterError>
2417 where
2418 F: Future<Output = Result<Timestamp, StorageError>>,
2419 {
2420 fut.await
2421 .map_err(|error| Self::real_time_recent_timestamp_error(&catalog, error))
2422 }
2423
2424 fn real_time_recent_timestamp_error(catalog: &Catalog, error: StorageError) -> AdapterError {
2425 let rtr_name = |id: &GlobalId| {
2426 catalog
2427 .try_get_entry_by_global_id(id)
2428 .map(|e| e.name().item.clone())
2429 .unwrap_or_else(|| id.to_string())
2430 };
2431
2432 match error {
2433 StorageError::RtrTimeout(id) => AdapterError::RtrTimeout(rtr_name(&id)),
2434 StorageError::RtrDropFailure(id) => AdapterError::RtrDropFailure(rtr_name(&id)),
2435 error => error.into(),
2436 }
2437 }
2438
2439 pub(crate) async fn determine_real_time_recent_timestamp_if_needed(
2442 &self,
2443 session: &Session,
2444 source_ids: impl Iterator<Item = GlobalId>,
2445 ) -> Result<Option<RtrTimestampFuture>, AdapterError> {
2446 let vars = session.vars();
2447
2448 if vars.real_time_recency()
2449 && vars.transaction_isolation() == &IsolationLevel::StrictSerializable
2450 && !session.contains_read_timestamp()
2451 {
2452 self.determine_real_time_recent_timestamp(source_ids, *vars.real_time_recency_timeout())
2453 .await
2454 } else {
2455 Ok(None)
2456 }
2457 }
2458
2459 #[instrument]
2460 pub(super) async fn sequence_explain_plan(
2461 &mut self,
2462 ctx: ExecuteContext,
2463 plan: plan::ExplainPlanPlan,
2464 target_cluster: TargetCluster,
2465 ) {
2466 match &plan.explainee {
2467 plan::Explainee::Statement(stmt) => match stmt {
2468 plan::ExplaineeStatement::CreateView { .. } => {
2469 self.explain_create_view(ctx, plan).await;
2470 }
2471 plan::ExplaineeStatement::CreateMaterializedView { .. } => {
2472 self.explain_create_materialized_view(ctx, plan).await;
2473 }
2474 plan::ExplaineeStatement::CreateIndex { .. } => {
2475 self.explain_create_index(ctx, plan).await;
2476 }
2477 plan::ExplaineeStatement::Select { .. } => {
2478 self.explain_peek(ctx, plan, target_cluster).await;
2479 }
2480 plan::ExplaineeStatement::Subscribe { .. } => {
2481 self.explain_subscribe(ctx, plan, target_cluster).await;
2482 }
2483 },
2484 plan::Explainee::View(_) => {
2485 let result = self.explain_view(&ctx, plan);
2486 ctx.retire(result);
2487 }
2488 plan::Explainee::MaterializedView(_) => {
2489 let result = self.explain_materialized_view(&ctx, plan);
2490 ctx.retire(result);
2491 }
2492 plan::Explainee::Index(_) => {
2493 let result = self.explain_index(&ctx, plan);
2494 ctx.retire(result);
2495 }
2496 plan::Explainee::ReplanView(_) => {
2497 self.explain_replan_view(ctx, plan).await;
2498 }
2499 plan::Explainee::ReplanMaterializedView(_) => {
2500 self.explain_replan_materialized_view(ctx, plan).await;
2501 }
2502 plan::Explainee::ReplanIndex(_) => {
2503 self.explain_replan_index(ctx, plan).await;
2504 }
2505 };
2506 }
2507
2508 pub(super) async fn sequence_explain_pushdown(
2509 &mut self,
2510 ctx: ExecuteContext,
2511 plan: plan::ExplainPushdownPlan,
2512 target_cluster: TargetCluster,
2513 ) {
2514 match plan.explainee {
2515 Explainee::Statement(ExplaineeStatement::Select {
2516 broken: false,
2517 plan,
2518 desc: _,
2519 }) => {
2520 let stage = return_if_err!(
2521 self.peek_validate(
2522 ctx.session(),
2523 plan,
2524 target_cluster,
2525 None,
2526 ExplainContext::Pushdown,
2527 Some(ctx.session().vars().max_query_result_size()),
2528 ),
2529 ctx
2530 );
2531 self.sequence_staged(ctx, Span::current(), stage).await;
2532 }
2533 Explainee::MaterializedView(item_id) => {
2534 self.explain_pushdown_materialized_view(ctx, item_id).await;
2535 }
2536 _ => {
2537 ctx.retire(Err(AdapterError::Unsupported(
2538 "EXPLAIN FILTER PUSHDOWN queries for this explainee type",
2539 )));
2540 }
2541 };
2542 }
2543
2544 async fn execute_explain_pushdown_with_read_holds(
2546 &self,
2547 ctx: ExecuteContext,
2548 as_of: Antichain<Timestamp>,
2549 mz_now: ResultSpec<'static>,
2550 read_holds: Option<ReadHolds>,
2551 imports: impl IntoIterator<Item = (GlobalId, MapFilterProject)> + 'static,
2552 ) {
2553 let fut = self
2554 .explain_pushdown_future(ctx.session(), as_of, mz_now, imports)
2555 .await;
2556 task::spawn(|| "render explain pushdown", async move {
2557 let _read_holds = read_holds;
2559 let res = fut.await;
2560 ctx.retire(res);
2561 });
2562 }
2563
2564 async fn explain_pushdown_future<I: IntoIterator<Item = (GlobalId, MapFilterProject)>>(
2566 &self,
2567 session: &Session,
2568 as_of: Antichain<Timestamp>,
2569 mz_now: ResultSpec<'static>,
2570 imports: I,
2571 ) -> impl Future<Output = Result<ExecuteResponse, AdapterError>> + use<I> {
2572 super::explain_pushdown_future_inner(
2574 session,
2575 &self.catalog,
2576 &self.controller.storage_collections,
2577 as_of,
2578 mz_now,
2579 imports,
2580 )
2581 .await
2582 }
2583
2584 #[instrument]
2585 pub(super) async fn sequence_insert(
2586 &mut self,
2587 mut ctx: ExecuteContext,
2588 plan: plan::InsertPlan,
2589 ) {
2590 if !ctx.session_mut().transaction().allows_writes() {
2598 ctx.retire(Err(AdapterError::ReadOnlyTransaction));
2599 return;
2600 }
2601 if ctx
2602 .session()
2603 .vars()
2604 .transaction_isolation()
2605 .is_bounded_staleness()
2606 {
2607 ctx.retire(Err(AdapterError::BoundedStalenessReadOnly));
2608 return;
2609 }
2610
2611 let optimized_mir = if let Some(..) = &plan.values.as_const() {
2625 let expr = return_if_err!(
2628 plan.values
2629 .clone()
2630 .lower(self.catalog().system_config(), None),
2631 ctx
2632 );
2633 OptimizedMirRelationExpr(expr)
2634 } else {
2635 let optimizer_config = optimize::OptimizerConfig::from(self.catalog().system_config());
2637
2638 let mut optimizer = optimize::view::Optimizer::new(optimizer_config, None);
2640
2641 return_if_err!(optimizer.optimize(plan.values.clone()), ctx)
2643 };
2644
2645 match optimized_mir.into_inner() {
2646 selection if selection.as_const().is_some() && plan.returning.is_empty() => {
2647 let catalog = self.owned_catalog();
2648 mz_ore::task::spawn(|| "coord::sequence_inner", async move {
2649 let result =
2650 Self::insert_constant(&catalog, ctx.session_mut(), plan.id, selection);
2651 ctx.retire(result);
2652 });
2653 }
2654 _ => {
2656 let desc_arity = match self.catalog().try_get_entry(&plan.id) {
2657 Some(table) => {
2658 let desc = table.relation_desc_latest().expect("table has a desc");
2660 desc.arity()
2661 }
2662 None => {
2663 ctx.retire(Err(AdapterError::Catalog(
2664 mz_catalog::memory::error::Error {
2665 kind: ErrorKind::Sql(CatalogError::UnknownItem(
2666 plan.id.to_string(),
2667 )),
2668 },
2669 )));
2670 return;
2671 }
2672 };
2673
2674 let finishing = RowSetFinishing {
2675 order_by: vec![],
2676 limit: None,
2677 offset: 0,
2678 project: (0..desc_arity).collect(),
2679 };
2680
2681 let read_then_write_plan = plan::ReadThenWritePlan {
2682 id: plan.id,
2683 selection: plan.values,
2684 finishing,
2685 assignments: BTreeMap::new(),
2686 kind: MutationKind::Insert,
2687 returning: plan.returning,
2688 };
2689
2690 self.sequence_read_then_write(ctx, read_then_write_plan)
2691 .await;
2692 }
2693 }
2694 }
2695
2696 #[instrument]
2701 pub(super) async fn sequence_read_then_write(
2702 &mut self,
2703 mut ctx: ExecuteContext,
2704 plan: plan::ReadThenWritePlan,
2705 ) {
2706 if ctx
2707 .session()
2708 .vars()
2709 .transaction_isolation()
2710 .is_bounded_staleness()
2711 {
2712 ctx.retire(Err(AdapterError::BoundedStalenessReadOnly));
2713 return;
2714 }
2715
2716 let mut source_ids: BTreeSet<_> = plan
2717 .selection
2718 .depends_on()
2719 .into_iter()
2720 .map(|gid| self.catalog().resolve_item_id(&gid))
2721 .collect();
2722 source_ids.insert(plan.id);
2723
2724 if ctx.session().transaction().write_locks().is_none() {
2726 let mut write_locks = WriteLocks::builder(source_ids.iter().copied());
2728
2729 for id in &source_ids {
2731 if let Some(lock) = self.try_grant_object_write_lock(*id) {
2732 write_locks.insert_lock(*id, lock);
2733 }
2734 }
2735
2736 let write_locks = match write_locks.all_or_nothing(ctx.session().conn_id()) {
2738 Ok(locks) => locks,
2739 Err(missing) => {
2740 let role_metadata = ctx.session().role_metadata().clone();
2742 let acquire_future = self.grant_object_write_lock(missing).map(Option::Some);
2743 let plan = DeferredPlan {
2744 ctx,
2745 plan: Plan::ReadThenWrite(plan),
2746 validity: PlanValidity::new(
2747 &self.catalog,
2748 source_ids.clone(),
2749 None,
2750 None,
2751 role_metadata,
2752 ),
2753 requires_locks: source_ids,
2754 resolved_ids: ResolvedIds::empty(),
2756 sql_impl_resolved_ids: ResolvedIds::empty(),
2757 };
2758 return self.defer_op(acquire_future, DeferredOp::Plan(plan));
2759 }
2760 };
2761
2762 ctx.session_mut()
2763 .try_grant_write_locks(write_locks)
2764 .expect("session has already been granted write locks");
2765 }
2766
2767 let plan::ReadThenWritePlan {
2768 id,
2769 kind,
2770 selection,
2771 mut assignments,
2772 finishing,
2773 mut returning,
2774 } = plan;
2775
2776 let desc = match self.catalog().try_get_entry(&id) {
2778 Some(table) => {
2779 table
2781 .relation_desc_latest()
2782 .expect("table has a desc")
2783 .into_owned()
2784 }
2785 None => {
2786 ctx.retire(Err(AdapterError::Catalog(
2787 mz_catalog::memory::error::Error {
2788 kind: ErrorKind::Sql(CatalogError::UnknownItem(id.to_string())),
2789 },
2790 )));
2791 return;
2792 }
2793 };
2794
2795 let contains_temporal = selection.contains_temporal()
2797 || assignments.values().any(|e| e.contains_temporal())
2798 || returning.iter().any(|e| e.contains_temporal());
2799 if contains_temporal {
2800 ctx.retire(Err(AdapterError::Unsupported(
2801 "calls to mz_now in write statements",
2802 )));
2803 return;
2804 }
2805
2806 let dependency_ids = selection
2808 .depends_on()
2809 .into_iter()
2810 .map(|gid| self.catalog().resolve_item_id(&gid));
2811 let max_rw_dependencies =
2812 READ_THEN_WRITE_MAX_DEPENDENCIES.get(self.catalog().system_config().dyncfgs());
2813 if let Err(err) = validate_read_then_write_dependencies(
2814 self.catalog(),
2815 dependency_ids,
2816 max_rw_dependencies,
2817 ) {
2818 ctx.retire(Err(err));
2819 return;
2820 }
2821
2822 let (peek_tx, peek_rx) = oneshot::channel();
2823 let peek_client_tx = ClientTransmitter::new(peek_tx, self.internal_cmd_tx.clone());
2824 let (tx, _, session, extra, response_barriers) = ctx.into_parts();
2825 let peek_ctx = ExecuteContext::from_parts(
2837 peek_client_tx,
2838 self.internal_cmd_tx.clone(),
2839 session,
2840 Default::default(),
2841 );
2842
2843 self.sequence_peek(
2844 peek_ctx,
2845 plan::SelectPlan {
2846 select: None,
2847 source: selection,
2848 when: QueryWhen::FreshestTableWrite,
2849 finishing,
2850 copy_to: None,
2851 },
2852 TargetCluster::Active,
2853 None,
2854 )
2855 .await;
2856
2857 let internal_cmd_tx = self.internal_cmd_tx.clone();
2858 let strict_serializable_reads_tx = self.strict_serializable_reads_tx.clone();
2859 let catalog = self.owned_catalog();
2860 let max_result_size = self.catalog().system_config().max_result_size();
2861
2862 task::spawn(|| format!("sequence_read_then_write:{id}"), async move {
2863 let (peek_response, session) = match peek_rx.await {
2864 Ok(Response {
2865 result: Ok(resp),
2866 session,
2867 otel_ctx,
2868 }) => {
2869 otel_ctx.attach_as_parent();
2870 (resp, session)
2871 }
2872 Ok(Response {
2873 result: Err(e),
2874 session,
2875 otel_ctx,
2876 }) => {
2877 let ctx = ExecuteContext::from_parts_with_response_barriers(
2878 tx,
2879 internal_cmd_tx.clone(),
2880 session,
2881 extra,
2882 response_barriers,
2883 );
2884 otel_ctx.attach_as_parent();
2885 ctx.retire(Err(e));
2886 return;
2887 }
2888 Err(e) => return warn!("internal_cmd_rx dropped before we could send: {:?}", e),
2890 };
2891 let mut ctx = ExecuteContext::from_parts_with_response_barriers(
2892 tx,
2893 internal_cmd_tx.clone(),
2894 session,
2895 extra,
2896 response_barriers,
2897 );
2898 let mut timeout_dur = *ctx.session().vars().statement_timeout();
2899
2900 if timeout_dur == Duration::ZERO {
2902 timeout_dur = Duration::MAX;
2903 }
2904
2905 let style = ExprPrepOneShot {
2906 logical_time: EvalTime::NotAvailable, session: ctx.session(),
2908 catalog_state: catalog.state(),
2909 };
2910 for expr in assignments.values_mut().chain(returning.iter_mut()) {
2911 return_if_err!(style.prep_scalar_expr(expr), ctx);
2912 }
2913
2914 let make_diffs = move |mut rows: Box<dyn RowIterator>|
2915 -> Result<(Vec<(Row, Diff)>, u64), AdapterError> {
2916 let arena = RowArena::new();
2917 let mut diffs = Vec::new();
2918 let mut datum_vec = mz_repr::DatumVec::new();
2919
2920 while let Some(row) = rows.next() {
2921 if !assignments.is_empty() {
2922 assert!(
2923 matches!(kind, MutationKind::Update),
2924 "only updates support assignments"
2925 );
2926 let mut datums = datum_vec.borrow_with(row);
2927 let mut updates = vec![];
2928 for (idx, expr) in &assignments {
2929 let updated = match expr.eval(&datums, &arena) {
2930 Ok(updated) => updated,
2931 Err(e) => return Err(AdapterError::Unstructured(anyhow!(e))),
2932 };
2933 updates.push((*idx, updated));
2934 }
2935 for (idx, new_value) in updates {
2936 datums[idx] = new_value;
2937 }
2938 let updated = Row::pack_slice(&datums);
2939 diffs.push((updated, Diff::ONE));
2940 }
2941 match kind {
2942 MutationKind::Update | MutationKind::Delete => {
2946 diffs.push((row.to_owned(), Diff::MINUS_ONE))
2947 }
2948 MutationKind::Insert => diffs.push((row.to_owned(), Diff::ONE)),
2949 }
2950 }
2951
2952 let mut byte_size: u64 = 0;
2955 for (row, diff) in &diffs {
2956 byte_size = byte_size.saturating_add(u64::cast_from(row.byte_len()));
2957 if diff.is_positive() {
2958 for (idx, datum) in row.iter().enumerate() {
2959 desc.constraints_met(idx, &datum)?;
2960 }
2961 }
2962 }
2963 Ok((diffs, byte_size))
2964 };
2965
2966 let diffs = match peek_response {
2967 ExecuteResponse::SendingRowsStreaming {
2968 rows: mut rows_stream,
2969 ..
2970 } => {
2971 let mut byte_size: u64 = 0;
2972 let mut diffs = Vec::new();
2973 let result = loop {
2974 match tokio::time::timeout(timeout_dur, rows_stream.next()).await {
2975 Ok(Some(res)) => match res {
2976 PeekResponseUnary::Rows(new_rows) => {
2977 match make_diffs(new_rows) {
2978 Ok((mut new_diffs, new_byte_size)) => {
2979 byte_size = byte_size.saturating_add(new_byte_size);
2980 if byte_size > max_result_size {
2981 break Err(AdapterError::ResultSize(format!(
2982 "result exceeds max size of {max_result_size}"
2983 )));
2984 }
2985 diffs.append(&mut new_diffs)
2986 }
2987 Err(e) => break Err(e),
2988 };
2989 }
2990 PeekResponseUnary::Canceled => break Err(AdapterError::Canceled),
2991 PeekResponseUnary::Error(e) => {
2992 break Err(AdapterError::Unstructured(anyhow!(e)));
2993 }
2994 PeekResponseUnary::DependencyDropped(dep) => {
2995 break Err(dep.to_concurrent_dependency_drop());
2996 }
2997 },
2998 Ok(None) => break Ok(diffs),
2999 Err(_) => {
3000 let result = internal_cmd_tx.send(Message::CancelPendingPeeks {
3005 conn_id: ctx.session().conn_id().clone(),
3006 });
3007 if let Err(e) = result {
3008 warn!("internal_cmd_rx dropped before we could send: {:?}", e);
3009 }
3010 break Err(AdapterError::StatementTimeout);
3011 }
3012 }
3013 };
3014
3015 result
3016 }
3017 ExecuteResponse::SendingRowsImmediate { rows } => {
3018 make_diffs(rows).map(|(diffs, _byte_size)| diffs)
3019 }
3020 resp => Err(AdapterError::Unstructured(anyhow!(
3021 "unexpected peek response: {resp:?}"
3022 ))),
3023 };
3024
3025 let mut returning_rows = Vec::new();
3026 let mut diff_err: Option<AdapterError> = None;
3027 if let (false, Ok(diffs)) = (returning.is_empty(), &diffs) {
3028 let arena = RowArena::new();
3029 for (row, diff) in diffs {
3030 if !diff.is_positive() {
3031 continue;
3032 }
3033 let mut returning_row = Row::with_capacity(returning.len());
3034 let mut packer = returning_row.packer();
3035 for expr in &returning {
3036 let datums: Vec<_> = row.iter().collect();
3037 match expr.eval(&datums, &arena) {
3038 Ok(datum) => {
3039 packer.push(datum);
3040 }
3041 Err(err) => {
3042 diff_err = Some(err.into());
3043 break;
3044 }
3045 }
3046 }
3047 let diff = NonZeroI64::try_from(diff.into_inner()).expect("known to be >= 1");
3048 let diff = match NonZeroUsize::try_from(diff) {
3049 Ok(diff) => diff,
3050 Err(err) => {
3051 diff_err = Some(err.into());
3052 break;
3053 }
3054 };
3055 returning_rows.push((returning_row, diff));
3056 if diff_err.is_some() {
3057 break;
3058 }
3059 }
3060 }
3061 let diffs = if let Some(err) = diff_err {
3062 Err(err)
3063 } else {
3064 diffs
3065 };
3066
3067 let timestamp_context = ctx.session_mut().take_transaction_timestamp_context();
3070 if let Some(timestamp_context) = timestamp_context {
3079 let (tx, rx) = tokio::sync::oneshot::channel();
3080 let conn_id = ctx.session().conn_id().clone();
3081 let pending_read_txn = PendingReadTxn {
3082 txn: PendingRead::ReadThenWrite { ctx, tx },
3083 timestamp_context,
3084 created: Instant::now(),
3085 num_requeues: 0,
3086 otel_ctx: OpenTelemetryContext::obtain(),
3087 };
3088 let result = strict_serializable_reads_tx.send((conn_id, pending_read_txn));
3089 if let Err(e) = result {
3091 warn!(
3092 "strict_serializable_reads_tx dropped before we could send: {:?}",
3093 e
3094 );
3095 return;
3096 }
3097 let result = rx.await;
3098 ctx = match result {
3100 Ok(Some(ctx)) => ctx,
3101 Ok(None) => {
3102 return;
3105 }
3106 Err(e) => {
3107 warn!(
3108 "tx used to linearize read in read then write transaction dropped before we could send: {:?}",
3109 e
3110 );
3111 return;
3112 }
3113 };
3114 }
3115
3116 match diffs {
3117 Ok(diffs) => {
3118 let result = Self::send_diffs(
3119 ctx.session_mut(),
3120 plan::SendDiffsPlan {
3121 id,
3122 updates: diffs,
3123 kind,
3124 returning: returning_rows,
3125 max_result_size,
3126 },
3127 );
3128 ctx.retire(result);
3129 }
3130 Err(e) => {
3131 ctx.retire(Err(e));
3132 }
3133 }
3134 });
3135 }
3136
3137 #[instrument]
3138 pub(super) async fn sequence_alter_item_rename(
3139 &mut self,
3140 ctx: &mut ExecuteContext,
3141 plan: plan::AlterItemRenamePlan,
3142 ) -> Result<ExecuteResponse, AdapterError> {
3143 let op = catalog::Op::RenameItem {
3144 id: plan.id,
3145 current_full_name: plan.current_full_name,
3146 to_name: plan.to_name,
3147 };
3148 match self
3149 .catalog_transact_with_ddl_transaction(ctx, vec![op], |_, _| Box::pin(async {}))
3150 .await
3151 {
3152 Ok(()) => Ok(ExecuteResponse::AlteredObject(plan.object_type)),
3153 Err(err) => Err(err),
3154 }
3155 }
3156
3157 #[instrument]
3158 pub(super) async fn sequence_alter_retain_history(
3159 &mut self,
3160 ctx: &mut ExecuteContext,
3161 plan: plan::AlterRetainHistoryPlan,
3162 ) -> Result<ExecuteResponse, AdapterError> {
3163 let ops = vec![catalog::Op::AlterRetainHistory {
3164 id: plan.id,
3165 value: plan.value,
3166 window: plan.window,
3167 }];
3168 self.catalog_transact_with_context(None, Some(ctx), ops)
3169 .await?;
3170 Ok(ExecuteResponse::AlteredObject(plan.object_type))
3171 }
3172
3173 #[instrument]
3174 pub(super) async fn sequence_alter_source_timestamp_interval(
3175 &mut self,
3176 ctx: &mut ExecuteContext,
3177 plan: plan::AlterSourceTimestampIntervalPlan,
3178 ) -> Result<ExecuteResponse, AdapterError> {
3179 let ops = vec![catalog::Op::AlterSourceTimestampInterval {
3180 id: plan.id,
3181 value: plan.value,
3182 interval: plan.interval,
3183 }];
3184 self.catalog_transact_with_context(None, Some(ctx), ops)
3185 .await?;
3186 Ok(ExecuteResponse::AlteredObject(ObjectType::Source))
3187 }
3188
3189 #[instrument]
3190 pub(super) async fn sequence_alter_schema_rename(
3191 &mut self,
3192 ctx: &mut ExecuteContext,
3193 plan: plan::AlterSchemaRenamePlan,
3194 ) -> Result<ExecuteResponse, AdapterError> {
3195 let (database_spec, schema_spec) = plan.cur_schema_spec;
3196 let op = catalog::Op::RenameSchema {
3197 database_spec,
3198 schema_spec,
3199 new_name: plan.new_schema_name,
3200 check_reserved_names: true,
3201 };
3202 match self
3203 .catalog_transact_with_ddl_transaction(ctx, vec![op], |_, _| Box::pin(async {}))
3204 .await
3205 {
3206 Ok(()) => Ok(ExecuteResponse::AlteredObject(ObjectType::Schema)),
3207 Err(err) => Err(err),
3208 }
3209 }
3210
3211 #[instrument]
3212 pub(super) async fn sequence_alter_schema_swap(
3213 &mut self,
3214 ctx: &mut ExecuteContext,
3215 plan: plan::AlterSchemaSwapPlan,
3216 ) -> Result<ExecuteResponse, AdapterError> {
3217 let plan::AlterSchemaSwapPlan {
3218 schema_a_spec: (schema_a_db, schema_a),
3219 schema_a_name,
3220 schema_b_spec: (schema_b_db, schema_b),
3221 schema_b_name,
3222 name_temp,
3223 } = plan;
3224
3225 let op_a = catalog::Op::RenameSchema {
3226 database_spec: schema_a_db,
3227 schema_spec: schema_a,
3228 new_name: name_temp,
3229 check_reserved_names: false,
3230 };
3231 let op_b = catalog::Op::RenameSchema {
3232 database_spec: schema_b_db,
3233 schema_spec: schema_b,
3234 new_name: schema_a_name,
3235 check_reserved_names: false,
3236 };
3237 let op_c = catalog::Op::RenameSchema {
3238 database_spec: schema_a_db,
3239 schema_spec: schema_a,
3240 new_name: schema_b_name,
3241 check_reserved_names: false,
3242 };
3243
3244 match self
3245 .catalog_transact_with_ddl_transaction(ctx, vec![op_a, op_b, op_c], |_, _| {
3246 Box::pin(async {})
3247 })
3248 .await
3249 {
3250 Ok(()) => Ok(ExecuteResponse::AlteredObject(ObjectType::Schema)),
3251 Err(err) => Err(err),
3252 }
3253 }
3254
3255 #[instrument]
3256 pub(super) async fn sequence_alter_role(
3257 &mut self,
3258 session: &Session,
3259 plan::AlterRolePlan { id, name, option }: plan::AlterRolePlan,
3260 ) -> Result<ExecuteResponse, AdapterError> {
3261 let catalog = self.catalog().for_session(session);
3262 let role = catalog.get_role(&id);
3263
3264 let mut notices = vec![];
3266
3267 let mut attributes: RoleAttributesRaw = role.attributes().clone().into();
3269 let mut vars = role.vars().clone();
3270
3271 let mut nopassword = false;
3274
3275 match option {
3277 PlannedAlterRoleOption::Attributes(attrs) => {
3278 self.validate_role_attributes(&attrs.clone().into())?;
3279
3280 if let Some(inherit) = attrs.inherit {
3281 attributes.inherit = inherit;
3282 }
3283
3284 if let Some(password) = attrs.password {
3285 attributes.password = Some(password);
3286 attributes.scram_iterations =
3287 Some(self.catalog().system_config().scram_iterations())
3288 }
3289
3290 if let Some(superuser) = attrs.superuser {
3291 attributes.superuser = Some(superuser);
3292 }
3293
3294 if let Some(login) = attrs.login {
3295 attributes.login = Some(login);
3296 }
3297
3298 if attrs.nopassword.unwrap_or(false) {
3299 nopassword = true;
3300 }
3301
3302 if let Some(notice) = self.should_emit_rbac_notice(session) {
3303 notices.push(notice);
3304 }
3305 }
3306 PlannedAlterRoleOption::Variable(variable) => {
3307 let session_var = session.vars().inspect(variable.name())?;
3309 session_var.visible(session.user(), catalog.system_vars())?;
3311
3312 if variable.name() == vars::OLD_AUTO_ROUTE_CATALOG_QUERIES {
3315 notices.push(AdapterNotice::AutoRouteIntrospectionQueriesUsage);
3316 } else if let PlannedRoleVariable::Set {
3317 name,
3318 value: VariableValue::Values(vals),
3319 } = &variable
3320 {
3321 if name == vars::CLUSTER.name() && vals[0] == vars::OLD_CATALOG_SERVER_CLUSTER {
3322 notices.push(AdapterNotice::IntrospectionClusterUsage);
3323 }
3324 }
3325
3326 let var_name = match variable {
3327 PlannedRoleVariable::Set { name, value } => {
3328 match &value {
3330 VariableValue::Default => {
3331 vars.remove(&name);
3332 }
3333 VariableValue::Values(vals) => {
3334 let var = match &vals[..] {
3335 [val] => OwnedVarInput::Flat(val.clone()),
3336 vals => OwnedVarInput::SqlSet(vals.to_vec()),
3337 };
3338 session_var.check(var.borrow())?;
3340
3341 vars.insert(name.clone(), var);
3342 }
3343 };
3344 name
3345 }
3346 PlannedRoleVariable::Reset { name } => {
3347 vars.remove(&name);
3349 name
3350 }
3351 };
3352
3353 notices.push(AdapterNotice::VarDefaultUpdated {
3355 role: Some(name.clone()),
3356 var_name: Some(var_name),
3357 });
3358 }
3359 }
3360
3361 let op = catalog::Op::AlterRole {
3362 id,
3363 name,
3364 attributes,
3365 nopassword,
3366 vars: RoleVars { map: vars },
3367 };
3368 let response = self
3369 .catalog_transact(Some(session), vec![op])
3370 .await
3371 .map(|_| ExecuteResponse::AlteredRole)?;
3372
3373 session.add_notices(notices);
3375
3376 Ok(response)
3377 }
3378
3379 #[instrument]
3380 pub(super) async fn sequence_alter_sink_prepare(
3381 &mut self,
3382 ctx: ExecuteContext,
3383 plan: plan::AlterSinkPlan,
3384 ) {
3385 let id_bundle = crate::CollectionIdBundle {
3387 storage_ids: BTreeSet::from_iter([plan.sink.from]),
3388 compute_ids: BTreeMap::new(),
3389 };
3390 let read_hold = self.acquire_read_holds(&id_bundle);
3391
3392 let Some(read_ts) = read_hold.least_valid_read().into_option() else {
3393 ctx.retire(Err(AdapterError::UnreadableSinkCollection));
3394 return;
3395 };
3396
3397 let otel_ctx = OpenTelemetryContext::obtain();
3398 let from_item_id = self.catalog().resolve_item_id(&plan.sink.from);
3399
3400 let plan_validity = PlanValidity::new(
3401 self.catalog(),
3402 BTreeSet::from_iter([plan.item_id, from_item_id]),
3403 Some(plan.in_cluster),
3404 None,
3405 ctx.session().role_metadata().clone(),
3406 );
3407
3408 info!(
3409 "preparing alter sink for {}: frontiers={:?} export={:?}",
3410 plan.global_id,
3411 self.controller
3412 .storage_collections
3413 .collections_frontiers(vec![plan.global_id, plan.sink.from]),
3414 self.controller.storage.export(plan.global_id)
3415 );
3416
3417 self.install_storage_watch_set(
3423 ctx.session().conn_id().clone(),
3424 BTreeSet::from_iter([plan.global_id]),
3425 read_ts,
3426 WatchSetResponse::AlterSinkReady(AlterSinkReadyContext {
3427 ctx: Some(ctx),
3428 otel_ctx,
3429 plan,
3430 plan_validity,
3431 read_hold,
3432 }),
3433 ).expect("plan validity verified above; we are on the coordinator main task, so they couldn't have gone away since then");
3434 }
3435
3436 #[instrument]
3437 pub async fn sequence_alter_sink_finish(&mut self, mut ctx: AlterSinkReadyContext) {
3438 ctx.otel_ctx.attach_as_parent();
3439
3440 let plan::AlterSinkPlan {
3441 item_id,
3442 global_id,
3443 sink: sink_plan,
3444 with_snapshot,
3445 in_cluster,
3446 set_options,
3447 reset_options,
3448 } = ctx.plan.clone();
3449
3450 match ctx.plan_validity.check(self.catalog()) {
3459 Ok(()) => {}
3460 Err(err) => {
3461 ctx.retire(Err(err));
3462 return;
3463 }
3464 }
3465
3466 let entry = self.catalog().get_entry(&item_id);
3467 let CatalogItem::Sink(old_sink) = entry.item() else {
3468 panic!("invalid item kind for `AlterSinkPlan`");
3469 };
3470
3471 if sink_plan.version != old_sink.version + 1 {
3472 ctx.retire(Err(AdapterError::ChangedPlan(
3473 "sink was altered concurrently".into(),
3474 )));
3475 return;
3476 }
3477
3478 info!(
3479 "finishing alter sink for {global_id}: frontiers={:?} export={:?}",
3480 self.controller
3481 .storage_collections
3482 .collections_frontiers(vec![global_id, sink_plan.from]),
3483 self.controller.storage.export(global_id),
3484 );
3485
3486 let write_frontier = &self
3489 .controller
3490 .storage
3491 .export(global_id)
3492 .expect("sink known to exist")
3493 .write_frontier;
3494 let as_of = ctx.read_hold.least_valid_read();
3495 assert!(
3496 write_frontier.iter().all(|t| as_of.less_than(t)),
3497 "{:?} should be strictly less than {:?}",
3498 &*as_of,
3499 &**write_frontier
3500 );
3501
3502 let create_sql = &old_sink.create_sql;
3508 let parsed = mz_sql::parse::parse(create_sql).expect("valid create_sql");
3509 let Statement::CreateSink(mut stmt) = parsed.into_element().ast else {
3510 unreachable!("invalid statement kind for sink");
3511 };
3512
3513 plan::apply_sink_option_edits(
3515 &mut stmt.with_options,
3516 &[CreateSinkOption {
3517 name: CreateSinkOptionName::Version,
3518 value: Some(WithOptionValue::Value(mz_sql::ast::Value::Number(
3519 sink_plan.version.to_string(),
3520 ))),
3521 }],
3522 &[],
3523 );
3524
3525 let conn_catalog = self.catalog().for_system_session();
3526 let (mut stmt, resolved_ids) =
3527 mz_sql::names::resolve(&conn_catalog, stmt).expect("resolvable create_sql");
3528
3529 plan::apply_sink_option_edits(&mut stmt.with_options, &set_options, &reset_options);
3531
3532 let from_entry = self.catalog().get_entry_by_global_id(&sink_plan.from);
3534 let full_name = self.catalog().resolve_full_name(from_entry.name(), None);
3535 stmt.from = ResolvedItemName::Item {
3536 id: from_entry.id(),
3537 qualifiers: from_entry.name.qualifiers.clone(),
3538 full_name,
3539 print_id: true,
3540 version: from_entry.version,
3541 };
3542
3543 let mut resolved_ids = resolved_ids;
3550 resolved_ids.remove_item(&self.catalog().resolve_item_id(&old_sink.from));
3551 resolved_ids.add_item(from_entry.id());
3552
3553 let new_sink = Sink {
3554 create_sql: stmt.to_ast_string_stable(),
3555 global_id,
3556 from: sink_plan.from,
3557 connection: sink_plan.connection.clone(),
3558 envelope: sink_plan.envelope,
3559 version: sink_plan.version,
3560 with_snapshot,
3561 resolved_ids,
3562 cluster_id: in_cluster,
3563 commit_interval: sink_plan.commit_interval,
3564 };
3565
3566 let ops = vec![catalog::Op::UpdateItem {
3567 id: item_id,
3568 name: entry.name().clone(),
3569 to_item: CatalogItem::Sink(new_sink),
3570 }];
3571
3572 match self
3573 .catalog_transact(Some(ctx.ctx().session_mut()), ops)
3574 .await
3575 {
3576 Ok(()) => {}
3577 Err(err) => {
3578 ctx.retire(Err(err));
3579 return;
3580 }
3581 }
3582
3583 let storage_sink_desc = StorageSinkDesc {
3584 from: sink_plan.from,
3585 from_desc: from_entry
3586 .relation_desc()
3587 .expect("sinks can only be built on items with descs")
3588 .into_owned(),
3589 connection: sink_plan
3590 .connection
3591 .clone()
3592 .into_inline_connection(self.catalog().state()),
3593 envelope: sink_plan.envelope,
3594 as_of,
3595 with_snapshot,
3596 version: sink_plan.version,
3597 from_storage_metadata: (),
3598 to_storage_metadata: (),
3599 commit_interval: sink_plan.commit_interval,
3600 };
3601
3602 self.controller
3603 .storage
3604 .alter_export(
3605 global_id,
3606 ExportDescription {
3607 sink: storage_sink_desc,
3608 instance_id: in_cluster,
3609 },
3610 )
3611 .await
3612 .unwrap_or_terminate("cannot fail to alter source desc");
3613
3614 ctx.retire(Ok(ExecuteResponse::AlteredObject(ObjectType::Sink)));
3615 }
3616
3617 #[instrument]
3618 pub(super) async fn sequence_alter_connection(
3619 &mut self,
3620 ctx: ExecuteContext,
3621 AlterConnectionPlan { id, action }: AlterConnectionPlan,
3622 ) {
3623 match action {
3624 AlterConnectionAction::RotateKeys => {
3625 self.sequence_rotate_keys(ctx, id).await;
3626 }
3627 AlterConnectionAction::AlterOptions {
3628 set_options,
3629 drop_options,
3630 validate,
3631 } => {
3632 self.sequence_alter_connection_options(ctx, id, set_options, drop_options, validate)
3633 .await
3634 }
3635 }
3636 }
3637
3638 #[instrument]
3639 async fn sequence_alter_connection_options(
3640 &mut self,
3641 mut ctx: ExecuteContext,
3642 id: CatalogItemId,
3643 set_options: BTreeMap<ConnectionOptionName, Option<WithOptionValue<mz_sql::names::Aug>>>,
3644 drop_options: BTreeSet<ConnectionOptionName>,
3645 validate: bool,
3646 ) {
3647 let cur_entry = self.catalog().get_entry(&id);
3648 let cur_conn = cur_entry.connection().expect("known to be connection");
3649 let connection_gid = cur_conn.global_id();
3650
3651 let inner = || -> Result<Connection, AdapterError> {
3652 let create_conn_stmt = match mz_sql::parse::parse(&cur_conn.create_sql)
3654 .expect("invalid create sql persisted to catalog")
3655 .into_element()
3656 .ast
3657 {
3658 Statement::CreateConnection(stmt) => stmt,
3659 _ => unreachable!("proved type is source"),
3660 };
3661
3662 let catalog = self.catalog().for_system_session();
3663
3664 let (mut create_conn_stmt, resolved_ids) =
3666 mz_sql::names::resolve(&catalog, create_conn_stmt)
3667 .map_err(|e| AdapterError::internal("ALTER CONNECTION", e))?;
3668
3669 create_conn_stmt
3671 .values
3672 .retain(|o| !set_options.contains_key(&o.name) && !drop_options.contains(&o.name));
3673
3674 create_conn_stmt.values.extend(
3676 set_options
3677 .into_iter()
3678 .map(|(name, value)| ConnectionOption { name, value }),
3679 );
3680
3681 let mut catalog = self.catalog().for_system_session();
3684 catalog.mark_id_unresolvable_for_replanning(id);
3685
3686 let plan = match mz_sql::plan::plan(
3688 None,
3689 &catalog,
3690 Statement::CreateConnection(create_conn_stmt),
3691 &Params::empty(),
3692 &resolved_ids,
3693 )
3694 .map_err(|e| AdapterError::InvalidAlter("CONNECTION", e))?
3695 {
3696 (Plan::CreateConnection(plan), _sql_impl_ids) => plan,
3697 (p, _) => {
3698 unreachable!("create connection plan is only valid response, got {:?}", p)
3699 }
3700 };
3701
3702 let create_conn_stmt = match mz_sql::parse::parse(&plan.connection.create_sql)
3704 .expect("invalid create sql persisted to catalog")
3705 .into_element()
3706 .ast
3707 {
3708 Statement::CreateConnection(stmt) => stmt,
3709 _ => unreachable!("proved type is source"),
3710 };
3711
3712 let catalog = self.catalog().for_system_session();
3713
3714 let (_, new_deps) = mz_sql::names::resolve(&catalog, create_conn_stmt)
3716 .map_err(|e| AdapterError::internal("ALTER CONNECTION", e))?;
3717
3718 Ok(Connection {
3719 create_sql: plan.connection.create_sql,
3720 global_id: cur_conn.global_id,
3721 details: plan.connection.details,
3722 resolved_ids: new_deps,
3723 })
3724 };
3725
3726 let conn = match inner() {
3727 Ok(conn) => conn,
3728 Err(e) => {
3729 return ctx.retire(Err(e));
3730 }
3731 };
3732
3733 if let Err(err) = self
3736 .check_connection_secret_content_guards(&conn.details)
3737 .await
3738 {
3739 return ctx.retire(Err(err));
3740 }
3741
3742 if validate {
3743 let connection = conn
3744 .details
3745 .to_connection()
3746 .into_inline_connection(self.catalog().state());
3747
3748 let internal_cmd_tx = self.internal_cmd_tx.clone();
3749 let catalog = self.owned_catalog();
3750 let conn_id = ctx.session().conn_id().clone();
3751 let otel_ctx = OpenTelemetryContext::obtain();
3752 let role_metadata = ctx.session().role_metadata().clone();
3753 let current_storage_parameters = self.controller.storage.config().clone();
3754
3755 task::spawn(
3756 || format!("validate_alter_connection:{conn_id}"),
3757 async move {
3758 let resolved_ids = conn.resolved_ids.clone();
3759 let dependency_ids: BTreeSet<_> = resolved_ids.items().copied().collect();
3760 let result = match std::panic::AssertUnwindSafe(
3761 connection.validate(id, ¤t_storage_parameters),
3762 )
3763 .ore_catch_unwind()
3764 .await
3765 {
3766 Ok(Ok(())) => Ok(conn),
3767 Ok(Err(err)) => Err(err.into()),
3768 Err(_panic) => {
3769 tracing::error!("alter connection validation panicked");
3770 Err(AdapterError::Internal(
3771 "connection validation panicked".into(),
3772 ))
3773 }
3774 };
3775
3776 let result = internal_cmd_tx.send(Message::AlterConnectionValidationReady(
3778 AlterConnectionValidationReady {
3779 ctx,
3780 result,
3781 connection_id: id,
3782 connection_gid,
3783 plan_validity: PlanValidity::new(
3784 &catalog,
3785 dependency_ids.clone(),
3786 None,
3787 None,
3788 role_metadata,
3789 ),
3790 otel_ctx,
3791 resolved_ids,
3792 },
3793 ));
3794 if let Err(e) = result {
3795 tracing::warn!("internal_cmd_rx dropped before we could send: {:?}", e);
3796 }
3797 },
3798 );
3799 } else {
3800 let result = self
3801 .sequence_alter_connection_stage_finish(ctx.session_mut(), id, conn)
3802 .await;
3803 ctx.retire(result);
3804 }
3805 }
3806
3807 #[instrument]
3808 pub(crate) async fn sequence_alter_connection_stage_finish(
3809 &mut self,
3810 session: &Session,
3811 id: CatalogItemId,
3812 connection: Connection,
3813 ) -> Result<ExecuteResponse, AdapterError> {
3814 match self.catalog.get_entry(&id).item() {
3815 CatalogItem::Connection(curr_conn) => {
3816 curr_conn
3817 .details
3818 .to_connection()
3819 .alter_compatible(curr_conn.global_id, &connection.details.to_connection())
3820 .map_err(StorageError::from)?;
3821 }
3822 _ => unreachable!("known to be a connection"),
3823 };
3824
3825 let ops = vec![catalog::Op::UpdateItem {
3826 id,
3827 name: self.catalog.get_entry(&id).name().clone(),
3828 to_item: CatalogItem::Connection(connection.clone()),
3829 }];
3830
3831 self.catalog_transact(Some(session), ops).await?;
3832
3833 Ok(ExecuteResponse::AlteredObject(ObjectType::Connection))
3840 }
3841
3842 #[instrument]
3843 pub(super) async fn sequence_alter_source(
3844 &mut self,
3845 session: &Session,
3846 plan::AlterSourcePlan {
3847 item_id,
3848 ingestion_id,
3849 action,
3850 }: plan::AlterSourcePlan,
3851 ) -> Result<ExecuteResponse, AdapterError> {
3852 let cur_entry = self.catalog().get_entry(&item_id);
3853 let cur_source = cur_entry.source().expect("known to be source");
3854
3855 let create_sql_to_stmt_deps = |coord: &Coordinator, err_cx, create_source_sql| {
3856 let create_source_stmt = match mz_sql::parse::parse(create_source_sql)
3858 .expect("invalid create sql persisted to catalog")
3859 .into_element()
3860 .ast
3861 {
3862 Statement::CreateSource(stmt) => stmt,
3863 _ => unreachable!("proved type is source"),
3864 };
3865
3866 let catalog = coord.catalog().for_system_session();
3867
3868 mz_sql::names::resolve(&catalog, create_source_stmt)
3870 .map_err(|e| AdapterError::internal(err_cx, e))
3871 };
3872
3873 match action {
3874 plan::AlterSourceAction::AddSubsourceExports {
3875 subsources,
3876 options,
3877 } => {
3878 const ALTER_SOURCE: &str = "ALTER SOURCE...ADD SUBSOURCES";
3879
3880 let mz_sql::plan::AlterSourceAddSubsourceOptionExtracted {
3881 text_columns: mut new_text_columns,
3882 exclude_columns: mut new_exclude_columns,
3883 ..
3884 } = options.try_into()?;
3885
3886 let (mut create_source_stmt, resolved_ids) =
3888 create_sql_to_stmt_deps(self, ALTER_SOURCE, cur_entry.create_sql())?;
3889
3890 let catalog = self.catalog();
3892 let curr_references: BTreeSet<_> = catalog
3893 .get_entry(&item_id)
3894 .used_by()
3895 .into_iter()
3896 .filter_map(|subsource| {
3897 catalog
3898 .get_entry(subsource)
3899 .subsource_details()
3900 .map(|(_id, reference, _details)| reference)
3901 })
3902 .collect();
3903
3904 let purification_err =
3907 || AdapterError::internal(ALTER_SOURCE, "error in subsource purification");
3908
3909 match &mut create_source_stmt.connection {
3913 CreateSourceConnection::Postgres {
3914 options: curr_options,
3915 ..
3916 } => {
3917 let mz_sql::plan::PgConfigOptionExtracted {
3918 mut text_columns, ..
3919 } = curr_options.clone().try_into()?;
3920
3921 curr_options.retain(|o| !matches!(o.name, PgConfigOptionName::TextColumns));
3924
3925 text_columns.retain(|column_qualified_reference| {
3927 mz_ore::soft_assert_eq_or_log!(
3928 column_qualified_reference.0.len(),
3929 4,
3930 "all TEXT COLUMNS values must be column-qualified references"
3931 );
3932 let mut table = column_qualified_reference.clone();
3933 table.0.truncate(3);
3934 curr_references.contains(&table)
3935 });
3936
3937 new_text_columns.extend(text_columns);
3939
3940 if !new_text_columns.is_empty() {
3942 new_text_columns.sort();
3943 let new_text_columns = new_text_columns
3944 .into_iter()
3945 .map(WithOptionValue::UnresolvedItemName)
3946 .collect();
3947
3948 curr_options.push(PgConfigOption {
3949 name: PgConfigOptionName::TextColumns,
3950 value: Some(WithOptionValue::Sequence(new_text_columns)),
3951 });
3952 }
3953 }
3954 CreateSourceConnection::MySql {
3955 options: curr_options,
3956 ..
3957 } => {
3958 let mz_sql::plan::MySqlConfigOptionExtracted {
3959 mut text_columns,
3960 mut exclude_columns,
3961 ..
3962 } = curr_options.clone().try_into()?;
3963
3964 curr_options.retain(|o| {
3967 !matches!(
3968 o.name,
3969 MySqlConfigOptionName::TextColumns
3970 | MySqlConfigOptionName::ExcludeColumns
3971 )
3972 });
3973
3974 let column_referenced =
3976 |column_qualified_reference: &UnresolvedItemName| {
3977 mz_ore::soft_assert_eq_or_log!(
3978 column_qualified_reference.0.len(),
3979 3,
3980 "all TEXT COLUMNS & EXCLUDE COLUMNS values must be column-qualified references"
3981 );
3982 let mut table = column_qualified_reference.clone();
3983 table.0.truncate(2);
3984 curr_references.contains(&table)
3985 };
3986 text_columns.retain(column_referenced);
3987 exclude_columns.retain(column_referenced);
3988
3989 new_text_columns.extend(text_columns);
3991 new_exclude_columns.extend(exclude_columns);
3992
3993 if !new_text_columns.is_empty() {
3995 new_text_columns.sort();
3996 let new_text_columns = new_text_columns
3997 .into_iter()
3998 .map(WithOptionValue::UnresolvedItemName)
3999 .collect();
4000
4001 curr_options.push(MySqlConfigOption {
4002 name: MySqlConfigOptionName::TextColumns,
4003 value: Some(WithOptionValue::Sequence(new_text_columns)),
4004 });
4005 }
4006 if !new_exclude_columns.is_empty() {
4008 new_exclude_columns.sort();
4009 let new_exclude_columns = new_exclude_columns
4010 .into_iter()
4011 .map(WithOptionValue::UnresolvedItemName)
4012 .collect();
4013
4014 curr_options.push(MySqlConfigOption {
4015 name: MySqlConfigOptionName::ExcludeColumns,
4016 value: Some(WithOptionValue::Sequence(new_exclude_columns)),
4017 });
4018 }
4019 }
4020 CreateSourceConnection::SqlServer {
4021 options: curr_options,
4022 ..
4023 } => {
4024 let mz_sql::plan::SqlServerConfigOptionExtracted {
4025 mut text_columns,
4026 mut exclude_columns,
4027 ..
4028 } = curr_options.clone().try_into()?;
4029
4030 curr_options.retain(|o| {
4033 !matches!(
4034 o.name,
4035 SqlServerConfigOptionName::TextColumns
4036 | SqlServerConfigOptionName::ExcludeColumns
4037 )
4038 });
4039
4040 let column_referenced =
4046 |column_qualified_reference: &UnresolvedItemName| {
4047 mz_ore::soft_assert_eq_or_log!(
4048 column_qualified_reference.0.len(),
4049 3,
4050 "all TEXT COLUMNS & EXCLUDE COLUMNS values must be column-qualified references"
4051 );
4052 let mut table = column_qualified_reference.clone();
4053 table.0.truncate(2);
4054 curr_references.iter().any(|r| r.0.ends_with(&table.0))
4055 };
4056 text_columns.retain(column_referenced);
4057 exclude_columns.retain(column_referenced);
4058
4059 new_text_columns.extend(text_columns);
4061 new_exclude_columns.extend(exclude_columns);
4062
4063 if !new_text_columns.is_empty() {
4065 new_text_columns.sort();
4066 let new_text_columns = new_text_columns
4067 .into_iter()
4068 .map(WithOptionValue::UnresolvedItemName)
4069 .collect();
4070
4071 curr_options.push(SqlServerConfigOption {
4072 name: SqlServerConfigOptionName::TextColumns,
4073 value: Some(WithOptionValue::Sequence(new_text_columns)),
4074 });
4075 }
4076 if !new_exclude_columns.is_empty() {
4078 new_exclude_columns.sort();
4079 let new_exclude_columns = new_exclude_columns
4080 .into_iter()
4081 .map(WithOptionValue::UnresolvedItemName)
4082 .collect();
4083
4084 curr_options.push(SqlServerConfigOption {
4085 name: SqlServerConfigOptionName::ExcludeColumns,
4086 value: Some(WithOptionValue::Sequence(new_exclude_columns)),
4087 });
4088 }
4089 }
4090 _ => return Err(purification_err()),
4091 };
4092
4093 let mut catalog = self.catalog().for_system_session();
4094 catalog.mark_id_unresolvable_for_replanning(cur_entry.id());
4095
4096 let planned = mz_sql::plan::plan(
4098 None,
4099 &catalog,
4100 Statement::CreateSource(create_source_stmt),
4101 &Params::empty(),
4102 &resolved_ids,
4103 )
4104 .map_err(|e| AdapterError::internal(ALTER_SOURCE, e))?;
4105 let plan = match planned {
4106 (Plan::CreateSource(plan), _sql_impl_ids) => plan,
4107 (p, _) => {
4108 unreachable!("create source plan is only valid response, got {:?}", p)
4109 }
4110 };
4111
4112 let source = Source::new(
4116 plan,
4117 cur_source.global_id,
4118 resolved_ids,
4119 cur_source.custom_logical_compaction_window,
4120 cur_source.is_retained_metrics_object,
4121 );
4122
4123 let desc = match &source.data_source {
4125 DataSourceDesc::Ingestion { desc, .. }
4126 | DataSourceDesc::OldSyntaxIngestion { desc, .. } => {
4127 desc.clone().into_inline_connection(self.catalog().state())
4128 }
4129 _ => unreachable!("already verified of type ingestion"),
4130 };
4131
4132 self.controller
4133 .storage
4134 .check_alter_ingestion_source_desc(ingestion_id, &desc)
4135 .map_err(|e| AdapterError::internal(ALTER_SOURCE, e))?;
4136
4137 let mut ops = vec![catalog::Op::UpdateItem {
4140 id: item_id,
4141 name: self.catalog.get_entry(&item_id).name().clone(),
4144 to_item: CatalogItem::Source(source),
4145 }];
4146
4147 let CreateSourceInner {
4148 ops: new_ops,
4149 sources: _,
4150 if_not_exists_ids,
4151 } = self.create_source_inner(session, subsources).await?;
4152
4153 ops.extend(new_ops.into_iter());
4154
4155 assert!(
4156 if_not_exists_ids.is_empty(),
4157 "IF NOT EXISTS not supported for ALTER SOURCE...ADD SUBSOURCES"
4158 );
4159
4160 self.catalog_transact(Some(session), ops).await?;
4161 }
4162 plan::AlterSourceAction::RefreshReferences { references } => {
4163 self.catalog_transact(
4164 Some(session),
4165 vec![catalog::Op::UpdateSourceReferences {
4166 source_id: item_id,
4167 references: references.into(),
4168 }],
4169 )
4170 .await?;
4171 }
4172 }
4173
4174 Ok(ExecuteResponse::AlteredObject(ObjectType::Source))
4175 }
4176
4177 #[instrument]
4178 pub(super) async fn sequence_alter_system_set(
4179 &mut self,
4180 session: &Session,
4181 plan::AlterSystemSetPlan { name, value }: plan::AlterSystemSetPlan,
4182 ) -> Result<ExecuteResponse, AdapterError> {
4183 self.is_user_allowed_to_alter_system(session, Some(&name))?;
4184 if NETWORK_POLICY.name.to_string().to_lowercase() == name.clone().to_lowercase() {
4186 self.validate_alter_system_network_policy(session, &value)?;
4187 }
4188
4189 let op = match value {
4190 plan::VariableValue::Values(values) => catalog::Op::UpdateSystemConfiguration {
4191 name: name.clone(),
4192 value: OwnedVarInput::SqlSet(values),
4193 },
4194 plan::VariableValue::Default => {
4195 catalog::Op::ResetSystemConfiguration { name: name.clone() }
4196 }
4197 };
4198 self.catalog_transact(Some(session), vec![op]).await?;
4199
4200 session.add_notice(AdapterNotice::VarDefaultUpdated {
4201 role: None,
4202 var_name: Some(name),
4203 });
4204 Ok(ExecuteResponse::AlteredSystemConfiguration)
4205 }
4206
4207 #[instrument]
4208 pub(super) async fn sequence_alter_system_reset(
4209 &mut self,
4210 session: &Session,
4211 plan::AlterSystemResetPlan { name }: plan::AlterSystemResetPlan,
4212 ) -> Result<ExecuteResponse, AdapterError> {
4213 self.is_user_allowed_to_alter_system(session, Some(&name))?;
4214 let op = catalog::Op::ResetSystemConfiguration { name: name.clone() };
4215 self.catalog_transact(Some(session), vec![op]).await?;
4216 session.add_notice(AdapterNotice::VarDefaultUpdated {
4217 role: None,
4218 var_name: Some(name),
4219 });
4220 Ok(ExecuteResponse::AlteredSystemConfiguration)
4221 }
4222
4223 #[instrument]
4224 pub(super) async fn sequence_alter_system_reset_all(
4225 &mut self,
4226 session: &Session,
4227 _: plan::AlterSystemResetAllPlan,
4228 ) -> Result<ExecuteResponse, AdapterError> {
4229 self.is_user_allowed_to_alter_system(session, None)?;
4230 let op = catalog::Op::ResetAllSystemConfiguration;
4231 self.catalog_transact(Some(session), vec![op]).await?;
4232 session.add_notice(AdapterNotice::VarDefaultUpdated {
4233 role: None,
4234 var_name: None,
4235 });
4236 Ok(ExecuteResponse::AlteredSystemConfiguration)
4237 }
4238
4239 fn is_user_allowed_to_alter_system(
4241 &self,
4242 session: &Session,
4243 var_name: Option<&str>,
4244 ) -> Result<(), AdapterError> {
4245 match (session.user().kind(), var_name) {
4246 (UserKind::Superuser, None) if session.user().is_internal() => Ok(()),
4248 (UserKind::Superuser, Some(name))
4250 if session.user().is_internal()
4251 || self.catalog().system_config().user_modifiable(name) =>
4252 {
4253 let var = self.catalog().system_config().get(name)?;
4256 var.visible(session.user(), self.catalog().system_config())?;
4257 Ok(())
4258 }
4259 (UserKind::Regular, Some(name))
4262 if self.catalog().system_config().user_modifiable(name) =>
4263 {
4264 Err(AdapterError::Unauthorized(
4265 rbac::UnauthorizedError::Superuser {
4266 action: format!("toggle the '{name}' system configuration parameter"),
4267 },
4268 ))
4269 }
4270 _ => Err(AdapterError::Unauthorized(
4271 rbac::UnauthorizedError::MzSystem {
4272 action: "alter system".into(),
4273 },
4274 )),
4275 }
4276 }
4277
4278 fn validate_alter_system_network_policy(
4279 &self,
4280 session: &Session,
4281 policy_value: &plan::VariableValue,
4282 ) -> Result<(), AdapterError> {
4283 let policy_name = match &policy_value {
4284 plan::VariableValue::Default => Some(NETWORK_POLICY.default_value().format()),
4286 plan::VariableValue::Values(values) if values.len() == 1 => {
4287 values.iter().next().cloned()
4288 }
4289 plan::VariableValue::Values(values) => {
4290 tracing::warn!(?values, "can't set multiple network policies at once");
4291 None
4292 }
4293 };
4294 let maybe_network_policy = policy_name
4295 .as_ref()
4296 .and_then(|name| self.catalog.get_network_policy_by_name(name));
4297 let Some(network_policy) = maybe_network_policy else {
4298 return Err(AdapterError::PlanError(plan::PlanError::VarError(
4299 VarError::InvalidParameterValue {
4300 name: NETWORK_POLICY.name(),
4301 invalid_values: vec![policy_name.unwrap_or_else(|| "<none>".to_string())],
4302 reason: "no network policy with such name exists".to_string(),
4303 },
4304 )));
4305 };
4306 self.validate_alter_network_policy(session, &network_policy.rules)
4307 }
4308
4309 fn validate_alter_network_policy(
4314 &self,
4315 session: &Session,
4316 policy_rules: &Vec<NetworkPolicyRule>,
4317 ) -> Result<(), AdapterError> {
4318 if session.user().is_internal() {
4321 return Ok(());
4322 }
4323 if let Some(ip) = session.meta().client_ip() {
4324 validate_ip_with_policy_rules(ip, policy_rules)
4325 .map_err(|_| AdapterError::PlanError(plan::PlanError::NetworkPolicyLockoutError))?;
4326 } else {
4327 return Err(AdapterError::NetworkPolicyDenied(
4330 NetworkPolicyError::MissingIp,
4331 ));
4332 }
4333 Ok(())
4334 }
4335
4336 #[instrument]
4338 pub(super) fn sequence_execute(
4339 &self,
4340 session: &mut Session,
4341 plan: plan::ExecutePlan,
4342 ) -> Result<String, AdapterError> {
4343 Self::verify_prepared_statement(self.catalog(), session, &plan.name)?;
4345 let ps = session
4346 .get_prepared_statement_unverified(&plan.name)
4347 .expect("known to exist");
4348 let stmt = ps.stmt().cloned();
4349 let desc = ps.desc().clone();
4350 let state_revision = ps.state_revision;
4351 let logging = Arc::clone(ps.logging());
4352 session.create_new_portal(stmt, logging, desc, plan.params, Vec::new(), state_revision)
4353 }
4354
4355 #[instrument]
4356 pub(super) async fn sequence_grant_privileges(
4357 &mut self,
4358 session: &Session,
4359 plan::GrantPrivilegesPlan {
4360 update_privileges,
4361 grantees,
4362 }: plan::GrantPrivilegesPlan,
4363 ) -> Result<ExecuteResponse, AdapterError> {
4364 self.sequence_update_privileges(
4365 session,
4366 update_privileges,
4367 grantees,
4368 UpdatePrivilegeVariant::Grant,
4369 )
4370 .await
4371 }
4372
4373 #[instrument]
4374 pub(super) async fn sequence_revoke_privileges(
4375 &mut self,
4376 session: &Session,
4377 plan::RevokePrivilegesPlan {
4378 update_privileges,
4379 revokees,
4380 }: plan::RevokePrivilegesPlan,
4381 ) -> Result<ExecuteResponse, AdapterError> {
4382 self.sequence_update_privileges(
4383 session,
4384 update_privileges,
4385 revokees,
4386 UpdatePrivilegeVariant::Revoke,
4387 )
4388 .await
4389 }
4390
4391 #[instrument]
4392 async fn sequence_update_privileges(
4393 &mut self,
4394 session: &Session,
4395 update_privileges: Vec<UpdatePrivilege>,
4396 grantees: Vec<RoleId>,
4397 variant: UpdatePrivilegeVariant,
4398 ) -> Result<ExecuteResponse, AdapterError> {
4399 let mut ops = Vec::with_capacity(update_privileges.len());
4400 let mut warnings = Vec::new();
4401 let catalog = self.catalog().for_session(session);
4402
4403 for UpdatePrivilege {
4404 acl_mode,
4405 target_id,
4406 grantor,
4407 acl_from_all,
4408 } in update_privileges
4409 {
4410 let actual_object_type = catalog.get_system_object_type(&target_id);
4411 if actual_object_type.is_relation() && !acl_from_all {
4417 let applicable_privileges = rbac::all_object_privileges(actual_object_type);
4418 let non_applicable_privileges = acl_mode.difference(applicable_privileges);
4419 if !non_applicable_privileges.is_empty() {
4420 let object_description =
4421 ErrorMessageObjectDescription::from_sys_id(&target_id, &catalog);
4422 warnings.push(AdapterNotice::NonApplicablePrivilegeTypes {
4423 non_applicable_privileges,
4424 object_description,
4425 })
4426 }
4427 }
4428
4429 if let SystemObjectId::Object(object_id) = &target_id {
4430 self.catalog()
4431 .ensure_not_reserved_object(object_id, session.conn_id())?;
4432 }
4433
4434 let privileges = self
4435 .catalog()
4436 .get_privileges(&target_id, session.conn_id())
4437 .ok_or(AdapterError::Unsupported(
4440 "GRANTs/REVOKEs on an object type with no privileges",
4441 ))?;
4442
4443 let mut target_privileges = Vec::with_capacity(grantees.len());
4446 for grantee in &grantees {
4447 self.catalog().ensure_not_system_role(grantee)?;
4448 self.catalog().ensure_not_predefined_role(grantee)?;
4449 let existing_privilege = privileges
4450 .get_acl_item(grantee, &grantor)
4451 .map(Cow::Borrowed)
4452 .unwrap_or_else(|| Cow::Owned(MzAclItem::empty(*grantee, grantor)));
4453
4454 let changes = match variant {
4456 UpdatePrivilegeVariant::Grant => {
4457 !existing_privilege.acl_mode.contains(acl_mode)
4458 }
4459 UpdatePrivilegeVariant::Revoke => !existing_privilege
4460 .acl_mode
4461 .intersection(acl_mode)
4462 .is_empty(),
4463 };
4464 if changes {
4465 target_privileges.push(MzAclItem {
4466 grantee: *grantee,
4467 grantor,
4468 acl_mode,
4469 });
4470 }
4471 }
4472 if !target_privileges.is_empty() {
4473 ops.push(catalog::Op::UpdatePrivilege {
4474 target_id: target_id.clone(),
4475 privileges: target_privileges,
4476 variant,
4477 });
4478 }
4479 }
4480
4481 if ops.is_empty() {
4482 session.add_notices(warnings);
4483 return Ok(variant.into());
4484 }
4485
4486 let res = self
4487 .catalog_transact(Some(session), ops)
4488 .await
4489 .map(|_| match variant {
4490 UpdatePrivilegeVariant::Grant => ExecuteResponse::GrantedPrivilege,
4491 UpdatePrivilegeVariant::Revoke => ExecuteResponse::RevokedPrivilege,
4492 });
4493 if res.is_ok() {
4494 session.add_notices(warnings);
4495 }
4496 res
4497 }
4498
4499 #[instrument]
4500 pub(super) async fn sequence_alter_default_privileges(
4501 &mut self,
4502 session: &Session,
4503 plan::AlterDefaultPrivilegesPlan {
4504 privilege_objects,
4505 privilege_acl_items,
4506 is_grant,
4507 }: plan::AlterDefaultPrivilegesPlan,
4508 ) -> Result<ExecuteResponse, AdapterError> {
4509 let mut ops = Vec::with_capacity(privilege_objects.len() * privilege_acl_items.len());
4510 let variant = if is_grant {
4511 UpdatePrivilegeVariant::Grant
4512 } else {
4513 UpdatePrivilegeVariant::Revoke
4514 };
4515 for privilege_object in &privilege_objects {
4516 self.catalog()
4517 .ensure_not_system_role(&privilege_object.role_id)?;
4518 self.catalog()
4519 .ensure_not_predefined_role(&privilege_object.role_id)?;
4520 if let Some(database_id) = privilege_object.database_id {
4521 self.catalog()
4522 .ensure_not_reserved_object(&database_id.into(), session.conn_id())?;
4523 }
4524 if let Some(schema_id) = privilege_object.schema_id {
4525 let database_spec: ResolvedDatabaseSpecifier = privilege_object.database_id.into();
4526 let schema_spec: SchemaSpecifier = schema_id.into();
4527
4528 self.catalog().ensure_not_reserved_object(
4529 &(database_spec, schema_spec).into(),
4530 session.conn_id(),
4531 )?;
4532 }
4533 for privilege_acl_item in &privilege_acl_items {
4534 self.catalog()
4535 .ensure_not_system_role(&privilege_acl_item.grantee)?;
4536 self.catalog()
4537 .ensure_not_predefined_role(&privilege_acl_item.grantee)?;
4538 ops.push(catalog::Op::UpdateDefaultPrivilege {
4539 privilege_object: privilege_object.clone(),
4540 privilege_acl_item: privilege_acl_item.clone(),
4541 variant,
4542 })
4543 }
4544 }
4545
4546 self.catalog_transact(Some(session), ops).await?;
4547 Ok(ExecuteResponse::AlteredDefaultPrivileges)
4548 }
4549
4550 #[instrument]
4551 pub(super) async fn sequence_grant_role(
4552 &mut self,
4553 session: &Session,
4554 plan::GrantRolePlan {
4555 role_ids,
4556 member_ids,
4557 grantor_id,
4558 }: plan::GrantRolePlan,
4559 ) -> Result<ExecuteResponse, AdapterError> {
4560 let catalog = self.catalog();
4561 let mut ops = Vec::with_capacity(role_ids.len() * member_ids.len());
4562 for role_id in role_ids {
4563 for member_id in &member_ids {
4564 let member_membership: BTreeSet<_> =
4565 catalog.get_role(member_id).membership().keys().collect();
4566 if member_membership.contains(&role_id) {
4567 let role_name = catalog.get_role(&role_id).name().to_string();
4568 let member_name = catalog.get_role(member_id).name().to_string();
4569 catalog.ensure_not_reserved_role(member_id)?;
4571 catalog.ensure_grantable_role(&role_id)?;
4572 session.add_notice(AdapterNotice::RoleMembershipAlreadyExists {
4573 role_name,
4574 member_name,
4575 });
4576 } else {
4577 ops.push(catalog::Op::GrantRole {
4578 role_id,
4579 member_id: *member_id,
4580 grantor_id,
4581 });
4582 }
4583 }
4584 }
4585
4586 if ops.is_empty() {
4587 return Ok(ExecuteResponse::GrantedRole);
4588 }
4589
4590 self.catalog_transact(Some(session), ops)
4591 .await
4592 .map(|_| ExecuteResponse::GrantedRole)
4593 }
4594
4595 #[instrument]
4596 pub(super) async fn sequence_revoke_role(
4597 &mut self,
4598 session: &Session,
4599 plan::RevokeRolePlan {
4600 role_ids,
4601 member_ids,
4602 grantor_id,
4603 }: plan::RevokeRolePlan,
4604 ) -> Result<ExecuteResponse, AdapterError> {
4605 let catalog = self.catalog();
4606 let mut ops = Vec::with_capacity(role_ids.len() * member_ids.len());
4607 for role_id in role_ids {
4608 for member_id in &member_ids {
4609 let member_membership: BTreeSet<_> =
4610 catalog.get_role(member_id).membership().keys().collect();
4611 if !member_membership.contains(&role_id) {
4612 let role_name = catalog.get_role(&role_id).name().to_string();
4613 let member_name = catalog.get_role(member_id).name().to_string();
4614 catalog.ensure_not_reserved_role(member_id)?;
4616 catalog.ensure_grantable_role(&role_id)?;
4617 session.add_notice(AdapterNotice::RoleMembershipDoesNotExists {
4618 role_name,
4619 member_name,
4620 });
4621 } else {
4622 ops.push(catalog::Op::RevokeRole {
4623 role_id,
4624 member_id: *member_id,
4625 grantor_id,
4626 });
4627 }
4628 }
4629 }
4630
4631 if ops.is_empty() {
4632 return Ok(ExecuteResponse::RevokedRole);
4633 }
4634
4635 self.catalog_transact(Some(session), ops)
4636 .await
4637 .map(|_| ExecuteResponse::RevokedRole)
4638 }
4639
4640 #[instrument]
4641 pub(super) async fn sequence_alter_owner(
4642 &mut self,
4643 session: &Session,
4644 plan::AlterOwnerPlan {
4645 id,
4646 object_type,
4647 new_owner,
4648 }: plan::AlterOwnerPlan,
4649 ) -> Result<ExecuteResponse, AdapterError> {
4650 let mut ops = vec![catalog::Op::UpdateOwner {
4651 id: id.clone(),
4652 new_owner,
4653 }];
4654
4655 match &id {
4656 ObjectId::Item(global_id) => {
4657 let entry = self.catalog().get_entry(global_id);
4658
4659 if entry.is_index() {
4661 let name = self
4662 .catalog()
4663 .resolve_full_name(entry.name(), Some(session.conn_id()))
4664 .to_string();
4665 session.add_notice(AdapterNotice::AlterIndexOwner { name });
4666 return Ok(ExecuteResponse::AlteredObject(object_type));
4667 }
4668
4669 let dependent_index_ops = entry
4671 .used_by()
4672 .into_iter()
4673 .filter(|id| self.catalog().get_entry(id).is_index())
4674 .map(|id| catalog::Op::UpdateOwner {
4675 id: ObjectId::Item(*id),
4676 new_owner,
4677 });
4678 ops.extend(dependent_index_ops);
4679
4680 let dependent_subsources =
4682 entry
4683 .progress_id()
4684 .into_iter()
4685 .map(|item_id| catalog::Op::UpdateOwner {
4686 id: ObjectId::Item(item_id),
4687 new_owner,
4688 });
4689 ops.extend(dependent_subsources);
4690 }
4691 ObjectId::Cluster(cluster_id) => {
4692 let cluster = self.catalog().get_cluster(*cluster_id);
4693 let managed_cluster_replica_ops =
4695 cluster.replicas().map(|replica| catalog::Op::UpdateOwner {
4696 id: ObjectId::ClusterReplica((cluster.id(), replica.replica_id())),
4697 new_owner,
4698 });
4699 ops.extend(managed_cluster_replica_ops);
4700 }
4701 _ => {}
4702 }
4703
4704 self.catalog_transact(Some(session), ops)
4705 .await
4706 .map(|_| ExecuteResponse::AlteredObject(object_type))
4707 }
4708
4709 #[instrument]
4710 pub(super) async fn sequence_reassign_owned(
4711 &mut self,
4712 session: &Session,
4713 plan::ReassignOwnedPlan {
4714 old_roles,
4715 new_role,
4716 reassign_ids,
4717 }: plan::ReassignOwnedPlan,
4718 ) -> Result<ExecuteResponse, AdapterError> {
4719 for role_id in old_roles.iter().chain(iter::once(&new_role)) {
4720 self.catalog().ensure_not_reserved_role(role_id)?;
4721 }
4722
4723 let ops = reassign_ids
4724 .into_iter()
4725 .map(|id| catalog::Op::UpdateOwner {
4726 id,
4727 new_owner: new_role,
4728 })
4729 .collect();
4730
4731 self.catalog_transact(Some(session), ops)
4732 .await
4733 .map(|_| ExecuteResponse::ReassignOwned)
4734 }
4735
4736 #[instrument]
4737 pub(crate) async fn handle_deferred_statement(&mut self) {
4738 let Some(DeferredPlanStatement { ctx, ps }) = self.serialized_ddl.pop_front() else {
4742 return;
4743 };
4744 match ps {
4745 crate::coord::PlanStatement::Statement { stmt, params } => {
4746 self.handle_execute_inner(stmt, params, ctx).await;
4747 }
4748 crate::coord::PlanStatement::Plan {
4749 plan,
4750 resolved_ids,
4751 sql_impl_resolved_ids,
4752 } => {
4753 self.sequence_plan(ctx, plan, resolved_ids, sql_impl_resolved_ids)
4754 .await;
4755 }
4756 }
4757 }
4758
4759 #[instrument]
4760 #[allow(clippy::unused_async)]
4762 pub(super) async fn sequence_alter_table(
4763 &mut self,
4764 ctx: &mut ExecuteContext,
4765 plan: plan::AlterTablePlan,
4766 ) -> Result<ExecuteResponse, AdapterError> {
4767 let plan::AlterTablePlan {
4768 relation_id,
4769 column_name,
4770 column_type,
4771 raw_sql_type,
4772 } = plan;
4773
4774 let (_, new_global_id) = self.allocate_user_id().await?;
4776 let ops = vec![catalog::Op::AlterAddColumn {
4777 id: relation_id,
4778 new_global_id,
4779 name: column_name,
4780 typ: column_type,
4781 sql: raw_sql_type,
4782 }];
4783
4784 self.catalog_transact_with_context(None, Some(ctx), ops)
4785 .await?;
4786
4787 Ok(ExecuteResponse::AlteredObject(ObjectType::Table))
4788 }
4789
4790 #[instrument]
4792 pub(super) async fn sequence_alter_materialized_view_apply_replacement_prepare(
4793 &mut self,
4794 ctx: ExecuteContext,
4795 plan: AlterMaterializedViewApplyReplacementPlan,
4796 ) {
4797 let AlterMaterializedViewApplyReplacementPlan { id, replacement_id } = plan.clone();
4807
4808 let plan_validity = PlanValidity::new(
4809 self.catalog(),
4810 BTreeSet::from_iter([id, replacement_id]),
4811 None,
4812 None,
4813 ctx.session().role_metadata().clone(),
4814 );
4815
4816 let target = self.catalog.get_entry(&id);
4817 let target_gid = target.latest_global_id();
4818
4819 let replacement = self.catalog.get_entry(&replacement_id);
4820 let replacement_gid = replacement.latest_global_id();
4821
4822 let target_upper = self
4823 .controller
4824 .storage_collections
4825 .collection_frontiers(target_gid)
4826 .expect("target MV exists")
4827 .write_frontier;
4828 let replacement_upper = self
4829 .controller
4830 .compute
4831 .collection_frontiers(replacement_gid, replacement.cluster_id())
4832 .expect("replacement MV exists")
4833 .write_frontier;
4834
4835 info!(
4836 %id, %replacement_id, ?target_upper, ?replacement_upper,
4837 "preparing materialized view replacement application",
4838 );
4839
4840 let Some(replacement_upper_ts) = replacement_upper.into_option() else {
4841 ctx.retire(Err(AdapterError::ReplaceMaterializedViewSealed {
4850 name: target.name().item.clone(),
4851 }));
4852 return;
4853 };
4854
4855 let replacement_upper_ts = replacement_upper_ts.step_back().unwrap_or(Timestamp::MIN);
4859
4860 self.install_storage_watch_set(
4864 ctx.session().conn_id().clone(),
4865 BTreeSet::from_iter([target_gid]),
4866 replacement_upper_ts,
4867 WatchSetResponse::AlterMaterializedViewReady(AlterMaterializedViewReadyContext {
4868 ctx: Some(ctx),
4869 otel_ctx: OpenTelemetryContext::obtain(),
4870 plan,
4871 plan_validity,
4872 }),
4873 )
4874 .expect("target collection exists");
4875 }
4876
4877 #[instrument]
4879 pub async fn sequence_alter_materialized_view_apply_replacement_finish(
4880 &mut self,
4881 mut ctx: AlterMaterializedViewReadyContext,
4882 ) {
4883 ctx.otel_ctx.attach_as_parent();
4884
4885 let AlterMaterializedViewApplyReplacementPlan { id, replacement_id } = ctx.plan;
4886
4887 if let Err(err) = ctx.plan_validity.check(self.catalog()) {
4892 ctx.retire(Err(err));
4893 return;
4894 }
4895
4896 info!(
4897 %id, %replacement_id,
4898 "finishing materialized view replacement application",
4899 );
4900
4901 let ops = vec![catalog::Op::AlterMaterializedViewApplyReplacement { id, replacement_id }];
4902 match self
4903 .catalog_transact(Some(ctx.ctx().session_mut()), ops)
4904 .await
4905 {
4906 Ok(()) => ctx.retire(Ok(ExecuteResponse::AlteredObject(
4907 ObjectType::MaterializedView,
4908 ))),
4909 Err(err) => ctx.retire(Err(err)),
4910 }
4911 }
4912
4913 pub(super) async fn statistics_oracle(
4914 &self,
4915 session: &Session,
4916 source_ids: &BTreeSet<GlobalId>,
4917 query_as_of: &Antichain<Timestamp>,
4918 is_oneshot: bool,
4919 ) -> Result<Box<dyn mz_transform::StatisticsOracle>, AdapterError> {
4920 super::statistics_oracle(
4921 session,
4922 source_ids,
4923 query_as_of,
4924 is_oneshot,
4925 self.catalog().system_config(),
4926 self.controller.storage_collections.as_ref(),
4927 )
4928 .await
4929 }
4930}
4931
4932impl Coordinator {
4933 pub(crate) fn emit_raw_optimizer_notices_to_user(
4941 &self,
4942 ctx: &ExecuteContext,
4943 notices: &[RawOptimizerNotice],
4944 ) {
4945 emit_optimizer_notices(&*self.catalog, ctx.session(), notices);
4946 }
4947
4948 fn persist_dataflow_metainfo(
4958 &mut self,
4959 df_meta: DataflowMetainfo<Arc<OptimizerNotice>>,
4960 export_id: GlobalId,
4961 ) -> Option<BuiltinTableAppendNotify> {
4962 if self.catalog().state().system_config().enable_mz_notices()
4965 && !df_meta.optimizer_notices.is_empty()
4966 {
4967 let mut builtin_table_updates = Vec::with_capacity(df_meta.optimizer_notices.len());
4968 self.catalog().state().pack_optimizer_notices(
4969 &mut builtin_table_updates,
4970 df_meta.optimizer_notices.iter(),
4971 Diff::ONE,
4972 );
4973
4974 self.catalog_mut().set_dataflow_metainfo(export_id, df_meta);
4976
4977 Some(self.builtin_table_update().execute(builtin_table_updates))
4978 } else {
4979 self.catalog_mut().set_dataflow_metainfo(export_id, df_meta);
4981
4982 None
4983 }
4984 }
4985}