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