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