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::{DependencyPolicy, 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 DependencyPolicy::UserDml,
2882 ) {
2883 ctx.retire(Err(err));
2884 return;
2885 }
2886
2887 let (peek_tx, peek_rx) = oneshot::channel();
2888 let peek_client_tx = ClientTransmitter::new(peek_tx, self.internal_cmd_tx.clone());
2889 let (tx, _, session, extra, response_barriers) = ctx.into_parts();
2890 let peek_ctx = ExecuteContext::from_parts(
2902 peek_client_tx,
2903 self.internal_cmd_tx.clone(),
2904 session,
2905 Default::default(),
2906 );
2907
2908 self.sequence_peek(
2909 peek_ctx,
2910 plan::SelectPlan {
2911 select: None,
2912 source: selection,
2913 when: QueryWhen::FreshestTableWrite,
2914 finishing,
2915 copy_to: None,
2916 },
2917 TargetCluster::Active,
2918 None,
2919 )
2920 .await;
2921
2922 let internal_cmd_tx = self.internal_cmd_tx.clone();
2923 let strict_serializable_reads_tx = self.strict_serializable_reads_tx.clone();
2924 let catalog = self.owned_catalog();
2925 let max_result_size = self.catalog().system_config().max_result_size();
2926
2927 task::spawn(|| format!("sequence_read_then_write:{id}"), async move {
2928 let (peek_response, session) = match peek_rx.await {
2929 Ok(Response {
2930 result: Ok(resp),
2931 session,
2932 otel_ctx,
2933 }) => {
2934 otel_ctx.attach_as_parent();
2935 (resp, session)
2936 }
2937 Ok(Response {
2938 result: Err(e),
2939 session,
2940 otel_ctx,
2941 }) => {
2942 let ctx = ExecuteContext::from_parts_with_response_barriers(
2943 tx,
2944 internal_cmd_tx.clone(),
2945 session,
2946 extra,
2947 response_barriers,
2948 );
2949 otel_ctx.attach_as_parent();
2950 ctx.retire(Err(e));
2951 return;
2952 }
2953 Err(e) => return warn!("internal_cmd_rx dropped before we could send: {:?}", e),
2955 };
2956 let mut ctx = ExecuteContext::from_parts_with_response_barriers(
2957 tx,
2958 internal_cmd_tx.clone(),
2959 session,
2960 extra,
2961 response_barriers,
2962 );
2963 let mut timeout_dur = *ctx.session().vars().statement_timeout();
2964
2965 if timeout_dur == Duration::ZERO {
2967 timeout_dur = Duration::MAX;
2968 }
2969
2970 let style = ExprPrepOneShot {
2971 logical_time: EvalTime::NotAvailable, session: ctx.session(),
2973 catalog_state: catalog.state(),
2974 };
2975 for expr in assignments.values_mut().chain(returning.iter_mut()) {
2976 return_if_err!(style.prep_scalar_expr(expr), ctx);
2977 }
2978
2979 let make_diffs = move |mut rows: Box<dyn RowIterator>|
2980 -> Result<(Vec<(Row, Diff)>, u64), AdapterError> {
2981 let arena = RowArena::new();
2982 let mut diffs = Vec::new();
2983 let mut datum_vec = mz_repr::DatumVec::new();
2984
2985 while let Some(row) = rows.next() {
2986 if !assignments.is_empty() {
2987 assert!(
2988 matches!(kind, MutationKind::Update),
2989 "only updates support assignments"
2990 );
2991 let mut datums = datum_vec.borrow_with(row);
2992 let mut updates = vec![];
2993 for (idx, expr) in &assignments {
2994 let updated = match expr.eval(&datums, &arena) {
2995 Ok(updated) => updated,
2996 Err(e) => return Err(AdapterError::Unstructured(anyhow!(e))),
2997 };
2998 updates.push((*idx, updated));
2999 }
3000 for (idx, new_value) in updates {
3001 datums[idx] = new_value;
3002 }
3003 let updated = Row::pack_slice(&datums);
3004 diffs.push((updated, Diff::ONE));
3005 }
3006 match kind {
3007 MutationKind::Update | MutationKind::Delete => {
3011 diffs.push((row.to_owned(), Diff::MINUS_ONE))
3012 }
3013 MutationKind::Insert => diffs.push((row.to_owned(), Diff::ONE)),
3014 }
3015 }
3016
3017 let mut byte_size: u64 = 0;
3020 for (row, diff) in &diffs {
3021 byte_size = byte_size.saturating_add(u64::cast_from(row.byte_len()));
3022 if diff.is_positive() {
3023 for (idx, datum) in row.iter().enumerate() {
3024 desc.constraints_met(idx, &datum)?;
3025 }
3026 }
3027 }
3028 Ok((diffs, byte_size))
3029 };
3030
3031 let diffs = match peek_response {
3032 ExecuteResponse::SendingRowsStreaming {
3033 rows: mut rows_stream,
3034 ..
3035 } => {
3036 let mut byte_size: u64 = 0;
3037 let mut diffs = Vec::new();
3038 let result = loop {
3039 match tokio::time::timeout(timeout_dur, rows_stream.next()).await {
3040 Ok(Some(res)) => match res {
3041 PeekResponseUnary::Rows(new_rows) => {
3042 match make_diffs(new_rows) {
3043 Ok((mut new_diffs, new_byte_size)) => {
3044 byte_size = byte_size.saturating_add(new_byte_size);
3045 if byte_size > max_result_size {
3046 break Err(AdapterError::ResultSize(format!(
3047 "result exceeds max size of {max_result_size}"
3048 )));
3049 }
3050 diffs.append(&mut new_diffs)
3051 }
3052 Err(e) => break Err(e),
3053 };
3054 }
3055 PeekResponseUnary::Canceled => break Err(AdapterError::Canceled),
3056 PeekResponseUnary::Error(e) => break Err(e),
3057 PeekResponseUnary::DependencyDropped(dep) => {
3058 break Err(dep.to_concurrent_dependency_drop());
3059 }
3060 },
3061 Ok(None) => break Ok(diffs),
3062 Err(_) => {
3063 let result = internal_cmd_tx.send(Message::CancelPendingPeeks {
3068 conn_id: ctx.session().conn_id().clone(),
3069 });
3070 if let Err(e) = result {
3071 warn!("internal_cmd_rx dropped before we could send: {:?}", e);
3072 }
3073 break Err(AdapterError::StatementTimeout);
3074 }
3075 }
3076 };
3077
3078 result
3079 }
3080 ExecuteResponse::SendingRowsImmediate { rows } => {
3081 make_diffs(rows).map(|(diffs, _byte_size)| diffs)
3082 }
3083 resp => Err(AdapterError::Unstructured(anyhow!(
3084 "unexpected peek response: {resp:?}"
3085 ))),
3086 };
3087
3088 let mut returning_rows = Vec::new();
3089 let mut diff_err: Option<AdapterError> = None;
3090 if let (false, Ok(diffs)) = (returning.is_empty(), &diffs) {
3091 let arena = RowArena::new();
3092 for (row, diff) in diffs {
3093 if !diff.is_positive() {
3094 continue;
3095 }
3096 let mut returning_row = Row::with_capacity(returning.len());
3097 let mut packer = returning_row.packer();
3098 for expr in &returning {
3099 let datums: Vec<_> = row.iter().collect();
3100 match expr.eval(&datums, &arena) {
3101 Ok(datum) => {
3102 packer.push(datum);
3103 }
3104 Err(err) => {
3105 diff_err = Some(err.into());
3106 break;
3107 }
3108 }
3109 }
3110 let diff = NonZeroI64::try_from(diff.into_inner()).expect("known to be >= 1");
3111 let diff = match NonZeroUsize::try_from(diff) {
3112 Ok(diff) => diff,
3113 Err(err) => {
3114 diff_err = Some(err.into());
3115 break;
3116 }
3117 };
3118 returning_rows.push((returning_row, diff));
3119 if diff_err.is_some() {
3120 break;
3121 }
3122 }
3123 }
3124 let diffs = if let Some(err) = diff_err {
3125 Err(err)
3126 } else {
3127 diffs
3128 };
3129
3130 let timestamp_context = ctx.session_mut().take_transaction_timestamp_context();
3133 if let Some(timestamp_context) = timestamp_context {
3142 let (tx, rx) = tokio::sync::oneshot::channel();
3143 let conn_id = ctx.session().conn_id().clone();
3144 let pending_read_txn = PendingReadTxn {
3145 txn: PendingRead::ReadThenWrite { ctx, tx },
3146 timestamp_context,
3147 created: Instant::now(),
3148 num_requeues: 0,
3149 otel_ctx: OpenTelemetryContext::obtain(),
3150 };
3151 let result = strict_serializable_reads_tx.send((conn_id, pending_read_txn));
3152 if let Err(e) = result {
3154 warn!(
3155 "strict_serializable_reads_tx dropped before we could send: {:?}",
3156 e
3157 );
3158 return;
3159 }
3160 let result = rx.await;
3161 ctx = match result {
3163 Ok(Some(ctx)) => ctx,
3164 Ok(None) => {
3165 return;
3168 }
3169 Err(e) => {
3170 warn!(
3171 "tx used to linearize read in read then write transaction dropped before we could send: {:?}",
3172 e
3173 );
3174 return;
3175 }
3176 };
3177 }
3178
3179 match diffs {
3180 Ok(diffs) => {
3181 let result = Self::send_diffs(
3182 ctx.session_mut(),
3183 plan::SendDiffsPlan {
3184 id,
3185 updates: diffs,
3186 kind,
3187 returning: returning_rows,
3188 max_result_size,
3189 },
3190 );
3191 ctx.retire(result);
3192 }
3193 Err(e) => {
3194 ctx.retire(Err(e));
3195 }
3196 }
3197 });
3198 }
3199
3200 #[instrument]
3201 pub(super) async fn sequence_alter_item_rename(
3202 &mut self,
3203 ctx: &mut ExecuteContext,
3204 plan: plan::AlterItemRenamePlan,
3205 ) -> Result<ExecuteResponse, AdapterError> {
3206 let op = catalog::Op::RenameItem {
3207 id: plan.id,
3208 current_full_name: plan.current_full_name,
3209 to_name: plan.to_name,
3210 };
3211 match self
3212 .catalog_transact_with_ddl_transaction(ctx, vec![op], |_, _| Box::pin(async {}))
3213 .await
3214 {
3215 Ok(()) => Ok(ExecuteResponse::AlteredObject(plan.object_type)),
3216 Err(err) => Err(err),
3217 }
3218 }
3219
3220 #[instrument]
3221 pub(super) async fn sequence_alter_retain_history(
3222 &mut self,
3223 ctx: &mut ExecuteContext,
3224 plan: plan::AlterRetainHistoryPlan,
3225 ) -> Result<ExecuteResponse, AdapterError> {
3226 let ops = vec![catalog::Op::AlterRetainHistory {
3227 id: plan.id,
3228 value: plan.value,
3229 window: plan.window,
3230 }];
3231 self.catalog_transact_with_context(None, Some(ctx), ops)
3232 .await?;
3233 Ok(ExecuteResponse::AlteredObject(plan.object_type))
3234 }
3235
3236 #[instrument]
3237 pub(super) async fn sequence_alter_source_timestamp_interval(
3238 &mut self,
3239 ctx: &mut ExecuteContext,
3240 plan: plan::AlterSourceTimestampIntervalPlan,
3241 ) -> Result<ExecuteResponse, AdapterError> {
3242 let ops = vec![catalog::Op::AlterSourceTimestampInterval {
3243 id: plan.id,
3244 value: plan.value,
3245 interval: plan.interval,
3246 }];
3247 self.catalog_transact_with_context(None, Some(ctx), ops)
3248 .await?;
3249 Ok(ExecuteResponse::AlteredObject(ObjectType::Source))
3250 }
3251
3252 #[instrument]
3253 pub(super) async fn sequence_alter_schema_rename(
3254 &mut self,
3255 ctx: &mut ExecuteContext,
3256 plan: plan::AlterSchemaRenamePlan,
3257 ) -> Result<ExecuteResponse, AdapterError> {
3258 let (database_spec, schema_spec) = plan.cur_schema_spec;
3259 let op = catalog::Op::RenameSchema {
3260 database_spec,
3261 schema_spec,
3262 new_name: plan.new_schema_name,
3263 check_reserved_names: true,
3264 };
3265 match self
3266 .catalog_transact_with_ddl_transaction(ctx, vec![op], |_, _| Box::pin(async {}))
3267 .await
3268 {
3269 Ok(()) => Ok(ExecuteResponse::AlteredObject(ObjectType::Schema)),
3270 Err(err) => Err(err),
3271 }
3272 }
3273
3274 #[instrument]
3275 pub(super) async fn sequence_alter_schema_swap(
3276 &mut self,
3277 ctx: &mut ExecuteContext,
3278 plan: plan::AlterSchemaSwapPlan,
3279 ) -> Result<ExecuteResponse, AdapterError> {
3280 let plan::AlterSchemaSwapPlan {
3281 schema_a_spec: (schema_a_db, schema_a),
3282 schema_a_name,
3283 schema_b_spec: (schema_b_db, schema_b),
3284 schema_b_name,
3285 name_temp,
3286 } = plan;
3287
3288 let op_a = catalog::Op::RenameSchema {
3289 database_spec: schema_a_db,
3290 schema_spec: schema_a,
3291 new_name: name_temp,
3292 check_reserved_names: false,
3293 };
3294 let op_b = catalog::Op::RenameSchema {
3295 database_spec: schema_b_db,
3296 schema_spec: schema_b,
3297 new_name: schema_a_name,
3298 check_reserved_names: false,
3299 };
3300 let op_c = catalog::Op::RenameSchema {
3301 database_spec: schema_a_db,
3302 schema_spec: schema_a,
3303 new_name: schema_b_name,
3304 check_reserved_names: false,
3305 };
3306
3307 match self
3308 .catalog_transact_with_ddl_transaction(ctx, vec![op_a, op_b, op_c], |_, _| {
3309 Box::pin(async {})
3310 })
3311 .await
3312 {
3313 Ok(()) => Ok(ExecuteResponse::AlteredObject(ObjectType::Schema)),
3314 Err(err) => Err(err),
3315 }
3316 }
3317
3318 #[instrument]
3319 pub(super) async fn sequence_alter_role(
3320 &mut self,
3321 session: &Session,
3322 plan::AlterRolePlan { id, name, option }: plan::AlterRolePlan,
3323 ) -> Result<ExecuteResponse, AdapterError> {
3324 let catalog = self.catalog().for_session(session);
3325 let role = catalog.get_role(&id);
3326
3327 let mut notices = vec![];
3329
3330 let mut attributes: RoleAttributesRaw = role.attributes().clone().into();
3332 let mut vars = role.vars().clone();
3333
3334 let mut nopassword = false;
3337
3338 match option {
3340 PlannedAlterRoleOption::Attributes(attrs) => {
3341 self.validate_role_attributes(&attrs.clone().into())?;
3342
3343 if let Some(inherit) = attrs.inherit {
3344 attributes.inherit = inherit;
3345 }
3346
3347 if let Some(password) = attrs.password {
3348 attributes.password = Some(password);
3349 attributes.scram_iterations =
3350 Some(self.catalog().system_config().scram_iterations())
3351 }
3352
3353 if let Some(superuser) = attrs.superuser {
3354 attributes.superuser = Some(superuser);
3355 }
3356
3357 if let Some(login) = attrs.login {
3358 attributes.login = Some(login);
3359 }
3360
3361 if attrs.nopassword.unwrap_or(false) {
3362 nopassword = true;
3363 }
3364
3365 if let Some(notice) = self.should_emit_rbac_notice(session) {
3366 notices.push(notice);
3367 }
3368 }
3369 PlannedAlterRoleOption::Variable(variable) => {
3370 let session_var = session.vars().inspect(variable.name())?;
3372 session_var.visible(session.user(), catalog.system_vars())?;
3374
3375 if variable.name() == vars::OLD_AUTO_ROUTE_CATALOG_QUERIES {
3378 notices.push(AdapterNotice::AutoRouteIntrospectionQueriesUsage);
3379 } else if let PlannedRoleVariable::Set {
3380 name,
3381 value: VariableValue::Values(vals),
3382 } = &variable
3383 {
3384 if name == vars::CLUSTER.name() && vals[0] == vars::OLD_CATALOG_SERVER_CLUSTER {
3385 notices.push(AdapterNotice::IntrospectionClusterUsage);
3386 }
3387 }
3388
3389 let var_name = match variable {
3390 PlannedRoleVariable::Set { name, value } => {
3391 match &value {
3393 VariableValue::Default => {
3394 vars.remove(&name);
3395 }
3396 VariableValue::Values(vals) => {
3397 let var = match &vals[..] {
3398 [val] => OwnedVarInput::Flat(val.clone()),
3399 vals => OwnedVarInput::SqlSet(vals.to_vec()),
3400 };
3401 session_var.check(var.borrow())?;
3403
3404 vars.insert(name.clone(), var);
3405 }
3406 };
3407 name
3408 }
3409 PlannedRoleVariable::Reset { name } => {
3410 vars.remove(&name);
3412 name
3413 }
3414 };
3415
3416 notices.push(AdapterNotice::VarDefaultUpdated {
3418 role: Some(name.clone()),
3419 var_name: Some(var_name),
3420 });
3421 }
3422 }
3423
3424 let op = catalog::Op::AlterRole {
3425 id,
3426 name,
3427 attributes,
3428 nopassword,
3429 vars: RoleVars { map: vars },
3430 };
3431 let response = self
3432 .catalog_transact(Some(session), vec![op])
3433 .await
3434 .map(|_| ExecuteResponse::AlteredRole)?;
3435
3436 session.add_notices(notices);
3438
3439 Ok(response)
3440 }
3441
3442 #[instrument]
3443 pub(super) async fn sequence_alter_sink_prepare(
3444 &mut self,
3445 ctx: ExecuteContext,
3446 plan: plan::AlterSinkPlan,
3447 ) {
3448 let id_bundle = crate::CollectionIdBundle {
3450 storage_ids: BTreeSet::from_iter([plan.sink.from]),
3451 compute_ids: BTreeMap::new(),
3452 };
3453 let read_hold = self.acquire_read_holds(&id_bundle);
3454
3455 let Some(read_ts) = read_hold.least_valid_read().into_option() else {
3456 ctx.retire(Err(AdapterError::UnreadableSinkCollection));
3457 return;
3458 };
3459
3460 let otel_ctx = OpenTelemetryContext::obtain();
3461 let from_item_id = self.catalog().resolve_item_id(&plan.sink.from);
3462
3463 let plan_validity = PlanValidity::new(
3464 self.catalog(),
3465 BTreeSet::from_iter([plan.item_id, from_item_id]),
3466 Some(plan.in_cluster),
3467 None,
3468 ctx.session().role_metadata().clone(),
3469 );
3470
3471 info!(
3472 "preparing alter sink for {}: frontiers={:?} export={:?}",
3473 plan.global_id,
3474 self.controller
3475 .storage_collections
3476 .collections_frontiers(vec![plan.global_id, plan.sink.from]),
3477 self.controller.storage.export(plan.global_id)
3478 );
3479
3480 self.install_storage_watch_set(
3486 ctx.session().conn_id().clone(),
3487 BTreeSet::from_iter([plan.global_id]),
3488 read_ts,
3489 WatchSetResponse::AlterSinkReady(AlterSinkReadyContext {
3490 ctx: Some(ctx),
3491 otel_ctx,
3492 plan,
3493 plan_validity,
3494 read_hold,
3495 }),
3496 ).expect("plan validity verified above; we are on the coordinator main task, so they couldn't have gone away since then");
3497 }
3498
3499 #[instrument]
3500 pub async fn sequence_alter_sink_finish(&mut self, mut ctx: AlterSinkReadyContext) {
3501 ctx.otel_ctx.attach_as_parent();
3502
3503 let plan::AlterSinkPlan {
3504 item_id,
3505 global_id,
3506 sink: sink_plan,
3507 with_snapshot,
3508 in_cluster,
3509 set_options,
3510 reset_options,
3511 } = ctx.plan.clone();
3512
3513 match ctx.plan_validity.check(self.catalog()) {
3522 Ok(()) => {}
3523 Err(err) => {
3524 ctx.retire(Err(err));
3525 return;
3526 }
3527 }
3528
3529 let entry = self.catalog().get_entry(&item_id);
3530 let CatalogItem::Sink(old_sink) = entry.item() else {
3531 panic!("invalid item kind for `AlterSinkPlan`");
3532 };
3533
3534 if sink_plan.version != old_sink.version + 1 {
3535 ctx.retire(Err(AdapterError::ChangedPlan(
3536 "sink was altered concurrently".into(),
3537 )));
3538 return;
3539 }
3540
3541 info!(
3542 "finishing alter sink for {global_id}: frontiers={:?} export={:?}",
3543 self.controller
3544 .storage_collections
3545 .collections_frontiers(vec![global_id, sink_plan.from]),
3546 self.controller.storage.export(global_id),
3547 );
3548
3549 let write_frontier = &self
3552 .controller
3553 .storage
3554 .export(global_id)
3555 .expect("sink known to exist")
3556 .write_frontier;
3557 let as_of = ctx.read_hold.least_valid_read();
3558 assert!(
3559 write_frontier.iter().all(|t| as_of.less_than(t)),
3560 "{:?} should be strictly less than {:?}",
3561 &*as_of,
3562 &**write_frontier
3563 );
3564
3565 let create_sql = &old_sink.create_sql;
3571 let parsed = mz_sql::parse::parse(create_sql).expect("valid create_sql");
3572 let Statement::CreateSink(mut stmt) = parsed.into_element().ast else {
3573 unreachable!("invalid statement kind for sink");
3574 };
3575
3576 plan::apply_sink_option_edits(
3578 &mut stmt.with_options,
3579 &[CreateSinkOption {
3580 name: CreateSinkOptionName::Version,
3581 value: Some(WithOptionValue::Value(mz_sql::ast::Value::Number(
3582 sink_plan.version.to_string(),
3583 ))),
3584 }],
3585 &[],
3586 );
3587
3588 let conn_catalog = self.catalog().for_system_session();
3589 let (mut stmt, resolved_ids) =
3590 mz_sql::names::resolve(&conn_catalog, stmt).expect("resolvable create_sql");
3591
3592 plan::apply_sink_option_edits(&mut stmt.with_options, &set_options, &reset_options);
3594
3595 let from_entry = self.catalog().get_entry_by_global_id(&sink_plan.from);
3597 let full_name = self.catalog().resolve_full_name(from_entry.name(), None);
3598 stmt.from = ResolvedItemName::Item {
3599 id: from_entry.id(),
3600 qualifiers: from_entry.name.qualifiers.clone(),
3601 full_name,
3602 print_id: true,
3603 version: from_entry.version,
3604 };
3605
3606 let mut resolved_ids = resolved_ids;
3613 resolved_ids.remove_item(&self.catalog().resolve_item_id(&old_sink.from));
3614 resolved_ids.add_item(from_entry.id());
3615
3616 let new_sink = Sink {
3617 create_sql: stmt.to_ast_string_stable(),
3618 global_id,
3619 from: sink_plan.from,
3620 connection: sink_plan.connection.clone(),
3621 envelope: sink_plan.envelope,
3622 version: sink_plan.version,
3623 with_snapshot,
3624 resolved_ids,
3625 cluster_id: in_cluster,
3626 commit_interval: sink_plan.commit_interval,
3627 };
3628
3629 let ops = vec![catalog::Op::UpdateItem {
3630 id: item_id,
3631 name: entry.name().clone(),
3632 to_item: CatalogItem::Sink(new_sink),
3633 }];
3634
3635 match self
3636 .catalog_transact(Some(ctx.ctx().session_mut()), ops)
3637 .await
3638 {
3639 Ok(()) => {}
3640 Err(err) => {
3641 ctx.retire(Err(err));
3642 return;
3643 }
3644 }
3645
3646 let storage_sink_desc = StorageSinkDesc {
3647 from: sink_plan.from,
3648 from_desc: from_entry
3649 .relation_desc()
3650 .expect("sinks can only be built on items with descs")
3651 .into_owned(),
3652 connection: sink_plan
3653 .connection
3654 .clone()
3655 .into_inline_connection(self.catalog().state()),
3656 envelope: sink_plan.envelope,
3657 as_of,
3658 with_snapshot,
3659 version: sink_plan.version,
3660 from_storage_metadata: (),
3661 to_storage_metadata: (),
3662 commit_interval: sink_plan.commit_interval,
3663 };
3664
3665 self.controller
3666 .storage
3667 .alter_export(
3668 global_id,
3669 ExportDescription {
3670 sink: storage_sink_desc,
3671 instance_id: in_cluster,
3672 },
3673 )
3674 .await
3675 .unwrap_or_terminate("cannot fail to alter source desc");
3676
3677 ctx.retire(Ok(ExecuteResponse::AlteredObject(ObjectType::Sink)));
3678 }
3679
3680 #[instrument]
3681 pub(super) async fn sequence_alter_connection(
3682 &mut self,
3683 ctx: ExecuteContext,
3684 AlterConnectionPlan { id, action }: AlterConnectionPlan,
3685 ) {
3686 match action {
3687 AlterConnectionAction::RotateKeys => {
3688 self.sequence_rotate_keys(ctx, id).await;
3689 }
3690 AlterConnectionAction::AlterOptions {
3691 set_options,
3692 drop_options,
3693 validate,
3694 } => {
3695 self.sequence_alter_connection_options(ctx, id, set_options, drop_options, validate)
3696 .await
3697 }
3698 }
3699 }
3700
3701 #[instrument]
3702 async fn sequence_alter_connection_options(
3703 &mut self,
3704 mut ctx: ExecuteContext,
3705 id: CatalogItemId,
3706 set_options: BTreeMap<ConnectionOptionName, Option<WithOptionValue<mz_sql::names::Aug>>>,
3707 drop_options: BTreeSet<ConnectionOptionName>,
3708 validate: bool,
3709 ) {
3710 let cur_entry = self.catalog().get_entry(&id);
3711 let cur_conn = cur_entry.connection().expect("known to be connection");
3712 let connection_gid = cur_conn.global_id();
3713
3714 let inner = || -> Result<Connection, AdapterError> {
3715 let create_conn_stmt = match mz_sql::parse::parse(&cur_conn.create_sql)
3717 .expect("invalid create sql persisted to catalog")
3718 .into_element()
3719 .ast
3720 {
3721 Statement::CreateConnection(stmt) => stmt,
3722 _ => unreachable!("proved type is source"),
3723 };
3724
3725 let catalog = self.catalog().for_system_session();
3726
3727 let (mut create_conn_stmt, resolved_ids) =
3729 mz_sql::names::resolve(&catalog, create_conn_stmt)
3730 .map_err(|e| AdapterError::internal("ALTER CONNECTION", e))?;
3731
3732 create_conn_stmt
3734 .values
3735 .retain(|o| !set_options.contains_key(&o.name) && !drop_options.contains(&o.name));
3736
3737 create_conn_stmt.values.extend(
3739 set_options
3740 .into_iter()
3741 .map(|(name, value)| ConnectionOption { name, value }),
3742 );
3743
3744 let mut catalog = self.catalog().for_system_session();
3747 catalog.mark_id_unresolvable_for_replanning(id);
3748
3749 let plan = match mz_sql::plan::plan(
3751 None,
3752 &catalog,
3753 Statement::CreateConnection(create_conn_stmt),
3754 &Params::empty(),
3755 &resolved_ids,
3756 )
3757 .map_err(|e| AdapterError::InvalidAlter("CONNECTION", e))?
3758 {
3759 (Plan::CreateConnection(plan), _sql_impl_ids) => plan,
3760 (p, _) => {
3761 unreachable!("create connection plan is only valid response, got {:?}", p)
3762 }
3763 };
3764
3765 let create_conn_stmt = match mz_sql::parse::parse(&plan.connection.create_sql)
3767 .expect("invalid create sql persisted to catalog")
3768 .into_element()
3769 .ast
3770 {
3771 Statement::CreateConnection(stmt) => stmt,
3772 _ => unreachable!("proved type is source"),
3773 };
3774
3775 let catalog = self.catalog().for_system_session();
3776
3777 let (_, new_deps) = mz_sql::names::resolve(&catalog, create_conn_stmt)
3779 .map_err(|e| AdapterError::internal("ALTER CONNECTION", e))?;
3780
3781 Ok(Connection {
3782 create_sql: plan.connection.create_sql,
3783 global_id: cur_conn.global_id,
3784 details: plan.connection.details,
3785 resolved_ids: new_deps,
3786 })
3787 };
3788
3789 let conn = match inner() {
3790 Ok(conn) => conn,
3791 Err(e) => {
3792 return ctx.retire(Err(e));
3793 }
3794 };
3795
3796 let usage_check = {
3800 let catalog = self.catalog().for_session(ctx.session());
3801 rbac::check_usage(
3802 &catalog,
3803 ctx.session(),
3804 &conn.resolved_ids,
3805 &rbac::CREATE_ITEM_USAGE,
3806 )
3807 };
3808 if let Err(err) = usage_check {
3809 return ctx.retire(Err(err.into()));
3810 }
3811
3812 if let Err(err) = check_connection_details(&conn.details) {
3815 return ctx.retire(Err(AdapterError::InvalidAlter("CONNECTION", err)));
3816 }
3817
3818 if let Err(err) = self
3821 .check_connection_secret_content_guards(&conn.details)
3822 .await
3823 {
3824 return ctx.retire(Err(err));
3825 }
3826
3827 if validate {
3828 let connection = conn
3829 .details
3830 .to_connection()
3831 .into_inline_connection(self.catalog().state());
3832
3833 let internal_cmd_tx = self.internal_cmd_tx.clone();
3834 let catalog = self.owned_catalog();
3835 let conn_id = ctx.session().conn_id().clone();
3836 let otel_ctx = OpenTelemetryContext::obtain();
3837 let role_metadata = ctx.session().role_metadata().clone();
3838 let current_storage_parameters = self.controller.storage.config().clone();
3839
3840 task::spawn(
3841 || format!("validate_alter_connection:{conn_id}"),
3842 async move {
3843 let resolved_ids = conn.resolved_ids.clone();
3844 let dependency_ids: BTreeSet<_> = resolved_ids.items().copied().collect();
3845 let result = match std::panic::AssertUnwindSafe(
3846 connection.validate(id, ¤t_storage_parameters),
3847 )
3848 .ore_catch_unwind()
3849 .await
3850 {
3851 Ok(Ok(())) => Ok(conn),
3852 Ok(Err(err)) => Err(err.into()),
3853 Err(_panic) => {
3854 tracing::error!("alter connection validation panicked");
3855 Err(AdapterError::Internal(
3856 "connection validation panicked".into(),
3857 ))
3858 }
3859 };
3860
3861 let result = internal_cmd_tx.send(Message::AlterConnectionValidationReady(
3863 AlterConnectionValidationReady {
3864 ctx,
3865 result,
3866 connection_id: id,
3867 connection_gid,
3868 plan_validity: PlanValidity::new(
3869 &catalog,
3870 dependency_ids.clone(),
3871 None,
3872 None,
3873 role_metadata,
3874 ),
3875 otel_ctx,
3876 resolved_ids,
3877 },
3878 ));
3879 if let Err(e) = result {
3880 tracing::warn!("internal_cmd_rx dropped before we could send: {:?}", e);
3881 }
3882 },
3883 );
3884 } else {
3885 let result = self
3886 .sequence_alter_connection_stage_finish(ctx.session_mut(), id, conn)
3887 .await;
3888 ctx.retire(result);
3889 }
3890 }
3891
3892 #[instrument]
3893 pub(crate) async fn sequence_alter_connection_stage_finish(
3894 &mut self,
3895 session: &Session,
3896 id: CatalogItemId,
3897 connection: Connection,
3898 ) -> Result<ExecuteResponse, AdapterError> {
3899 match self.catalog.get_entry(&id).item() {
3900 CatalogItem::Connection(curr_conn) => {
3901 curr_conn
3902 .details
3903 .to_connection()
3904 .alter_compatible(curr_conn.global_id, &connection.details.to_connection())
3905 .map_err(StorageError::from)?;
3906 }
3907 _ => unreachable!("known to be a connection"),
3908 };
3909
3910 let ops = vec![catalog::Op::UpdateItem {
3911 id,
3912 name: self.catalog.get_entry(&id).name().clone(),
3913 to_item: CatalogItem::Connection(connection.clone()),
3914 }];
3915
3916 self.catalog_transact(Some(session), ops).await?;
3917
3918 Ok(ExecuteResponse::AlteredObject(ObjectType::Connection))
3925 }
3926
3927 #[instrument]
3928 pub(super) async fn sequence_alter_source(
3929 &mut self,
3930 session: &Session,
3931 plan::AlterSourcePlan {
3932 item_id,
3933 ingestion_id,
3934 action,
3935 }: plan::AlterSourcePlan,
3936 ) -> Result<ExecuteResponse, AdapterError> {
3937 let cur_entry = self.catalog().get_entry(&item_id);
3938 let cur_source = cur_entry.source().expect("known to be source");
3939
3940 let create_sql_to_stmt_deps = |coord: &Coordinator, err_cx, create_source_sql| {
3941 let create_source_stmt = match mz_sql::parse::parse(create_source_sql)
3943 .expect("invalid create sql persisted to catalog")
3944 .into_element()
3945 .ast
3946 {
3947 Statement::CreateSource(stmt) => stmt,
3948 _ => unreachable!("proved type is source"),
3949 };
3950
3951 let catalog = coord.catalog().for_system_session();
3952
3953 mz_sql::names::resolve(&catalog, create_source_stmt)
3955 .map_err(|e| AdapterError::internal(err_cx, e))
3956 };
3957
3958 match action {
3959 plan::AlterSourceAction::AddSubsourceExports {
3960 subsources,
3961 options,
3962 } => {
3963 const ALTER_SOURCE: &str = "ALTER SOURCE...ADD SUBSOURCES";
3964
3965 let mz_sql::plan::AlterSourceAddSubsourceOptionExtracted {
3966 text_columns: mut new_text_columns,
3967 exclude_columns: mut new_exclude_columns,
3968 ..
3969 } = options.try_into()?;
3970
3971 let (mut create_source_stmt, resolved_ids) =
3973 create_sql_to_stmt_deps(self, ALTER_SOURCE, cur_entry.create_sql())?;
3974
3975 let catalog = self.catalog();
3977 let curr_references: BTreeSet<_> = catalog
3978 .get_entry(&item_id)
3979 .used_by()
3980 .into_iter()
3981 .filter_map(|subsource| {
3982 catalog
3983 .get_entry(subsource)
3984 .subsource_details()
3985 .map(|(_id, reference, _details)| reference)
3986 })
3987 .collect();
3988
3989 let purification_err =
3992 || AdapterError::internal(ALTER_SOURCE, "error in subsource purification");
3993
3994 match &mut create_source_stmt.connection {
3998 CreateSourceConnection::Postgres {
3999 options: curr_options,
4000 ..
4001 } => {
4002 let mz_sql::plan::PgConfigOptionExtracted {
4003 mut text_columns, ..
4004 } = curr_options.clone().try_into()?;
4005
4006 curr_options.retain(|o| !matches!(o.name, PgConfigOptionName::TextColumns));
4009
4010 text_columns.retain(|column_qualified_reference| {
4012 mz_ore::soft_assert_eq_or_log!(
4013 column_qualified_reference.0.len(),
4014 4,
4015 "all TEXT COLUMNS values must be column-qualified references"
4016 );
4017 let mut table = column_qualified_reference.clone();
4018 table.0.truncate(3);
4019 curr_references.contains(&table)
4020 });
4021
4022 new_text_columns.extend(text_columns);
4024
4025 if !new_text_columns.is_empty() {
4027 new_text_columns.sort();
4028 let new_text_columns = new_text_columns
4029 .into_iter()
4030 .map(WithOptionValue::UnresolvedItemName)
4031 .collect();
4032
4033 curr_options.push(PgConfigOption {
4034 name: PgConfigOptionName::TextColumns,
4035 value: Some(WithOptionValue::Sequence(new_text_columns)),
4036 });
4037 }
4038 }
4039 CreateSourceConnection::MySql {
4040 options: curr_options,
4041 ..
4042 } => {
4043 let mz_sql::plan::MySqlConfigOptionExtracted {
4044 mut text_columns,
4045 mut exclude_columns,
4046 ..
4047 } = curr_options.clone().try_into()?;
4048
4049 curr_options.retain(|o| {
4052 !matches!(
4053 o.name,
4054 MySqlConfigOptionName::TextColumns
4055 | MySqlConfigOptionName::ExcludeColumns
4056 )
4057 });
4058
4059 let column_referenced =
4061 |column_qualified_reference: &UnresolvedItemName| {
4062 mz_ore::soft_assert_eq_or_log!(
4063 column_qualified_reference.0.len(),
4064 3,
4065 "all TEXT COLUMNS & EXCLUDE COLUMNS values must be column-qualified references"
4066 );
4067 let mut table = column_qualified_reference.clone();
4068 table.0.truncate(2);
4069 curr_references.contains(&table)
4070 };
4071 text_columns.retain(column_referenced);
4072 exclude_columns.retain(column_referenced);
4073
4074 new_text_columns.extend(text_columns);
4076 new_exclude_columns.extend(exclude_columns);
4077
4078 if !new_text_columns.is_empty() {
4080 new_text_columns.sort();
4081 let new_text_columns = new_text_columns
4082 .into_iter()
4083 .map(WithOptionValue::UnresolvedItemName)
4084 .collect();
4085
4086 curr_options.push(MySqlConfigOption {
4087 name: MySqlConfigOptionName::TextColumns,
4088 value: Some(WithOptionValue::Sequence(new_text_columns)),
4089 });
4090 }
4091 if !new_exclude_columns.is_empty() {
4093 new_exclude_columns.sort();
4094 let new_exclude_columns = new_exclude_columns
4095 .into_iter()
4096 .map(WithOptionValue::UnresolvedItemName)
4097 .collect();
4098
4099 curr_options.push(MySqlConfigOption {
4100 name: MySqlConfigOptionName::ExcludeColumns,
4101 value: Some(WithOptionValue::Sequence(new_exclude_columns)),
4102 });
4103 }
4104 }
4105 CreateSourceConnection::SqlServer {
4106 options: curr_options,
4107 ..
4108 } => {
4109 let mz_sql::plan::SqlServerConfigOptionExtracted {
4110 mut text_columns,
4111 mut exclude_columns,
4112 ..
4113 } = curr_options.clone().try_into()?;
4114
4115 curr_options.retain(|o| {
4118 !matches!(
4119 o.name,
4120 SqlServerConfigOptionName::TextColumns
4121 | SqlServerConfigOptionName::ExcludeColumns
4122 )
4123 });
4124
4125 let column_referenced =
4131 |column_qualified_reference: &UnresolvedItemName| {
4132 mz_ore::soft_assert_eq_or_log!(
4133 column_qualified_reference.0.len(),
4134 3,
4135 "all TEXT COLUMNS & EXCLUDE COLUMNS values must be column-qualified references"
4136 );
4137 let mut table = column_qualified_reference.clone();
4138 table.0.truncate(2);
4139 curr_references.iter().any(|r| r.0.ends_with(&table.0))
4140 };
4141 text_columns.retain(column_referenced);
4142 exclude_columns.retain(column_referenced);
4143
4144 new_text_columns.extend(text_columns);
4146 new_exclude_columns.extend(exclude_columns);
4147
4148 if !new_text_columns.is_empty() {
4150 new_text_columns.sort();
4151 let new_text_columns = new_text_columns
4152 .into_iter()
4153 .map(WithOptionValue::UnresolvedItemName)
4154 .collect();
4155
4156 curr_options.push(SqlServerConfigOption {
4157 name: SqlServerConfigOptionName::TextColumns,
4158 value: Some(WithOptionValue::Sequence(new_text_columns)),
4159 });
4160 }
4161 if !new_exclude_columns.is_empty() {
4163 new_exclude_columns.sort();
4164 let new_exclude_columns = new_exclude_columns
4165 .into_iter()
4166 .map(WithOptionValue::UnresolvedItemName)
4167 .collect();
4168
4169 curr_options.push(SqlServerConfigOption {
4170 name: SqlServerConfigOptionName::ExcludeColumns,
4171 value: Some(WithOptionValue::Sequence(new_exclude_columns)),
4172 });
4173 }
4174 }
4175 _ => return Err(purification_err()),
4176 };
4177
4178 let mut catalog = self.catalog().for_system_session();
4179 catalog.mark_id_unresolvable_for_replanning(cur_entry.id());
4180
4181 let planned = mz_sql::plan::plan(
4183 None,
4184 &catalog,
4185 Statement::CreateSource(create_source_stmt),
4186 &Params::empty(),
4187 &resolved_ids,
4188 )
4189 .map_err(|e| AdapterError::internal(ALTER_SOURCE, e))?;
4190 let plan = match planned {
4191 (Plan::CreateSource(plan), _sql_impl_ids) => plan,
4192 (p, _) => {
4193 unreachable!("create source plan is only valid response, got {:?}", p)
4194 }
4195 };
4196
4197 let source = Source::new(
4201 plan,
4202 cur_source.global_id,
4203 resolved_ids,
4204 cur_source.custom_logical_compaction_window,
4205 cur_source.is_retained_metrics_object,
4206 );
4207
4208 let desc = match &source.data_source {
4210 DataSourceDesc::Ingestion { desc, .. }
4211 | DataSourceDesc::OldSyntaxIngestion { desc, .. } => {
4212 desc.clone().into_inline_connection(self.catalog().state())
4213 }
4214 _ => unreachable!("already verified of type ingestion"),
4215 };
4216
4217 self.controller
4218 .storage
4219 .check_alter_ingestion_source_desc(ingestion_id, &desc)
4220 .map_err(|e| AdapterError::internal(ALTER_SOURCE, e))?;
4221
4222 let mut ops = vec![catalog::Op::UpdateItem {
4225 id: item_id,
4226 name: self.catalog.get_entry(&item_id).name().clone(),
4229 to_item: CatalogItem::Source(source),
4230 }];
4231
4232 let CreateSourceInner {
4233 ops: new_ops,
4234 sources: _,
4235 if_not_exists_ids,
4236 } = self.create_source_inner(session, subsources).await?;
4237
4238 ops.extend(new_ops.into_iter());
4239
4240 assert!(
4241 if_not_exists_ids.is_empty(),
4242 "IF NOT EXISTS not supported for ALTER SOURCE...ADD SUBSOURCES"
4243 );
4244
4245 self.catalog_transact(Some(session), ops).await?;
4246 }
4247 plan::AlterSourceAction::RefreshReferences { references } => {
4248 self.catalog_transact(
4249 Some(session),
4250 vec![catalog::Op::UpdateSourceReferences {
4251 source_id: item_id,
4252 references: references.into(),
4253 }],
4254 )
4255 .await?;
4256 }
4257 }
4258
4259 Ok(ExecuteResponse::AlteredObject(ObjectType::Source))
4260 }
4261
4262 #[instrument]
4263 pub(super) async fn sequence_alter_system_set(
4264 &mut self,
4265 session: &Session,
4266 plan::AlterSystemSetPlan { name, value }: plan::AlterSystemSetPlan,
4267 ) -> Result<ExecuteResponse, AdapterError> {
4268 self.is_user_allowed_to_alter_system(session, Some(&name))?;
4269 if NETWORK_POLICY.name.to_string().to_lowercase() == name.clone().to_lowercase() {
4271 self.validate_alter_system_network_policy(session, &value)?;
4272 }
4273
4274 let op = match value {
4275 plan::VariableValue::Values(values) => catalog::Op::UpdateSystemConfiguration {
4276 name: name.clone(),
4277 value: OwnedVarInput::SqlSet(values),
4278 },
4279 plan::VariableValue::Default => {
4280 catalog::Op::ResetSystemConfiguration { name: name.clone() }
4281 }
4282 };
4283 self.catalog_transact(Some(session), vec![op]).await?;
4284
4285 Self::notice_if_startup_only(session, &name);
4286 session.add_notice(AdapterNotice::VarDefaultUpdated {
4287 role: None,
4288 var_name: Some(name),
4289 });
4290 Ok(ExecuteResponse::AlteredSystemConfiguration)
4291 }
4292
4293 #[instrument]
4294 pub(super) async fn sequence_alter_system_reset(
4295 &mut self,
4296 session: &Session,
4297 plan::AlterSystemResetPlan { name }: plan::AlterSystemResetPlan,
4298 ) -> Result<ExecuteResponse, AdapterError> {
4299 self.is_user_allowed_to_alter_system(session, Some(&name))?;
4300 let op = catalog::Op::ResetSystemConfiguration { name: name.clone() };
4301 self.catalog_transact(Some(session), vec![op]).await?;
4302 Self::notice_if_startup_only(session, &name);
4303 session.add_notice(AdapterNotice::VarDefaultUpdated {
4304 role: None,
4305 var_name: Some(name),
4306 });
4307 Ok(ExecuteResponse::AlteredSystemConfiguration)
4308 }
4309
4310 #[instrument]
4311 pub(super) async fn sequence_alter_system_reset_all(
4312 &mut self,
4313 session: &Session,
4314 _: plan::AlterSystemResetAllPlan,
4315 ) -> Result<ExecuteResponse, AdapterError> {
4316 self.is_user_allowed_to_alter_system(session, None)?;
4317 let startup_only_changed = self.startup_only_vars_changed_by_reset_all();
4320 let op = catalog::Op::ResetAllSystemConfiguration;
4321 self.catalog_transact(Some(session), vec![op]).await?;
4322 for name in startup_only_changed {
4323 session.add_notice(AdapterNotice::StartupOnlyVarUpdated {
4324 var_name: name.to_string(),
4325 });
4326 }
4327 session.add_notice(AdapterNotice::VarDefaultUpdated {
4328 role: None,
4329 var_name: None,
4330 });
4331 Ok(ExecuteResponse::AlteredSystemConfiguration)
4332 }
4333
4334 fn startup_only_vars() -> [&'static str; 3] {
4348 [
4349 FRONTEND_READ_THEN_WRITE.name(),
4350 MAX_CONCURRENT_OCC_WRITES.name(),
4351 ENABLE_EXPRESSION_CACHE.name(),
4352 ]
4353 }
4354
4355 fn notice_if_startup_only(session: &Session, name: &str) {
4358 if Self::startup_only_vars()
4359 .iter()
4360 .any(|n| n.eq_ignore_ascii_case(name))
4361 {
4362 session.add_notice(AdapterNotice::StartupOnlyVarUpdated {
4363 var_name: name.to_string(),
4364 });
4365 }
4366 }
4367
4368 fn startup_only_vars_changed_by_reset_all(&self) -> Vec<&'static str> {
4372 let config = self.catalog().system_config();
4376 let defaults = config.defaults();
4377 Self::startup_only_vars()
4378 .into_iter()
4379 .filter(|name| {
4380 let current = config
4383 .get(name)
4384 .expect("startup-only parameter is a registered system var")
4385 .value();
4386 defaults
4387 .get(*name)
4388 .is_some_and(|default| default != ¤t)
4389 })
4390 .collect()
4391 }
4392
4393 fn is_user_allowed_to_alter_system(
4395 &self,
4396 session: &Session,
4397 var_name: Option<&str>,
4398 ) -> Result<(), AdapterError> {
4399 match (session.user().kind(), var_name) {
4400 (UserKind::Superuser, None) if session.user().is_internal() => Ok(()),
4402 (UserKind::Superuser, Some(name))
4404 if session.user().is_internal()
4405 || self.catalog().system_config().user_modifiable(name) =>
4406 {
4407 let var = self.catalog().system_config().get(name)?;
4410 var.visible(session.user(), self.catalog().system_config())?;
4411 Ok(())
4412 }
4413 (UserKind::Regular, Some(name))
4416 if self.catalog().system_config().user_modifiable(name) =>
4417 {
4418 Err(AdapterError::Unauthorized(
4419 rbac::UnauthorizedError::Superuser {
4420 action: format!("toggle the '{name}' system configuration parameter"),
4421 },
4422 ))
4423 }
4424 _ => Err(AdapterError::Unauthorized(
4425 rbac::UnauthorizedError::MzSystem {
4426 action: "alter system".into(),
4427 },
4428 )),
4429 }
4430 }
4431
4432 fn validate_alter_system_network_policy(
4433 &self,
4434 session: &Session,
4435 policy_value: &plan::VariableValue,
4436 ) -> Result<(), AdapterError> {
4437 let policy_name = match &policy_value {
4438 plan::VariableValue::Default => Some(NETWORK_POLICY.default_value().format()),
4440 plan::VariableValue::Values(values) if values.len() == 1 => {
4441 values.iter().next().cloned()
4442 }
4443 plan::VariableValue::Values(values) => {
4444 tracing::warn!(?values, "can't set multiple network policies at once");
4445 None
4446 }
4447 };
4448 let maybe_network_policy = policy_name
4449 .as_ref()
4450 .and_then(|name| self.catalog.get_network_policy_by_name(name));
4451 let Some(network_policy) = maybe_network_policy else {
4452 return Err(AdapterError::PlanError(plan::PlanError::VarError(
4453 VarError::InvalidParameterValue {
4454 name: NETWORK_POLICY.name(),
4455 invalid_values: vec![policy_name.unwrap_or_else(|| "<none>".to_string())],
4456 reason: "no network policy with such name exists".to_string(),
4457 },
4458 )));
4459 };
4460 self.validate_alter_network_policy(session, &network_policy.rules)
4461 }
4462
4463 fn validate_alter_network_policy(
4468 &self,
4469 session: &Session,
4470 policy_rules: &Vec<NetworkPolicyRule>,
4471 ) -> Result<(), AdapterError> {
4472 if session.user().is_internal() {
4475 return Ok(());
4476 }
4477 if let Some(ip) = session.meta().client_ip() {
4478 validate_ip_with_policy_rules(ip, policy_rules)
4479 .map_err(|_| AdapterError::PlanError(plan::PlanError::NetworkPolicyLockoutError))?;
4480 } else {
4481 return Err(AdapterError::NetworkPolicyDenied(
4484 NetworkPolicyError::MissingIp,
4485 ));
4486 }
4487 Ok(())
4488 }
4489
4490 #[instrument]
4492 pub(super) fn sequence_execute(
4493 &self,
4494 session: &mut Session,
4495 plan: plan::ExecutePlan,
4496 ) -> Result<String, AdapterError> {
4497 Self::verify_prepared_statement(self.catalog(), session, &plan.name)?;
4499 let ps = session
4500 .get_prepared_statement_unverified(&plan.name)
4501 .expect("known to exist");
4502 let stmt = ps.stmt().cloned();
4503 let desc = ps.desc().clone();
4504 let state_revision = ps.state_revision;
4505 let logging = Arc::clone(ps.logging());
4506 session.create_new_portal(stmt, logging, desc, plan.params, Vec::new(), state_revision)
4507 }
4508
4509 #[instrument]
4510 pub(super) async fn sequence_grant_privileges(
4511 &mut self,
4512 session: &Session,
4513 plan::GrantPrivilegesPlan {
4514 update_privileges,
4515 grantees,
4516 }: plan::GrantPrivilegesPlan,
4517 ) -> Result<ExecuteResponse, AdapterError> {
4518 self.sequence_update_privileges(
4519 session,
4520 update_privileges,
4521 grantees,
4522 UpdatePrivilegeVariant::Grant,
4523 )
4524 .await
4525 }
4526
4527 #[instrument]
4528 pub(super) async fn sequence_revoke_privileges(
4529 &mut self,
4530 session: &Session,
4531 plan::RevokePrivilegesPlan {
4532 update_privileges,
4533 revokees,
4534 }: plan::RevokePrivilegesPlan,
4535 ) -> Result<ExecuteResponse, AdapterError> {
4536 self.sequence_update_privileges(
4537 session,
4538 update_privileges,
4539 revokees,
4540 UpdatePrivilegeVariant::Revoke,
4541 )
4542 .await
4543 }
4544
4545 #[instrument]
4546 async fn sequence_update_privileges(
4547 &mut self,
4548 session: &Session,
4549 update_privileges: Vec<UpdatePrivilege>,
4550 grantees: Vec<RoleId>,
4551 variant: UpdatePrivilegeVariant,
4552 ) -> Result<ExecuteResponse, AdapterError> {
4553 let mut ops = Vec::with_capacity(update_privileges.len());
4554 let mut warnings = Vec::new();
4555 let catalog = self.catalog().for_session(session);
4556
4557 for UpdatePrivilege {
4558 acl_mode,
4559 target_id,
4560 grantor,
4561 acl_from_all,
4562 } in update_privileges
4563 {
4564 let actual_object_type = catalog.get_system_object_type(&target_id);
4565 if actual_object_type.is_relation() && !acl_from_all {
4571 let applicable_privileges = rbac::all_object_privileges(actual_object_type);
4572 let non_applicable_privileges = acl_mode.difference(applicable_privileges);
4573 if !non_applicable_privileges.is_empty() {
4574 let object_description =
4575 ErrorMessageObjectDescription::from_sys_id(&target_id, &catalog);
4576 warnings.push(AdapterNotice::NonApplicablePrivilegeTypes {
4577 non_applicable_privileges,
4578 object_description,
4579 })
4580 }
4581 }
4582
4583 if let SystemObjectId::Object(object_id) = &target_id {
4584 self.catalog()
4585 .ensure_not_reserved_object(object_id, session.conn_id())?;
4586 }
4587
4588 let privileges = self
4589 .catalog()
4590 .get_privileges(&target_id, session.conn_id())
4591 .ok_or(AdapterError::Unsupported(
4594 "GRANTs/REVOKEs on an object type with no privileges",
4595 ))?;
4596
4597 let mut target_privileges = Vec::with_capacity(grantees.len());
4600 for grantee in &grantees {
4601 self.catalog().ensure_not_system_role(grantee)?;
4602 self.catalog().ensure_not_predefined_role(grantee)?;
4603 let existing_privilege = privileges
4604 .get_acl_item(grantee, &grantor)
4605 .map(Cow::Borrowed)
4606 .unwrap_or_else(|| Cow::Owned(MzAclItem::empty(*grantee, grantor)));
4607
4608 let changes = match variant {
4610 UpdatePrivilegeVariant::Grant => {
4611 !existing_privilege.acl_mode.contains(acl_mode)
4612 }
4613 UpdatePrivilegeVariant::Revoke => !existing_privilege
4614 .acl_mode
4615 .intersection(acl_mode)
4616 .is_empty(),
4617 };
4618 if changes {
4619 target_privileges.push(MzAclItem {
4620 grantee: *grantee,
4621 grantor,
4622 acl_mode,
4623 });
4624 }
4625 }
4626 if !target_privileges.is_empty() {
4627 ops.push(catalog::Op::UpdatePrivilege {
4628 target_id: target_id.clone(),
4629 privileges: target_privileges,
4630 variant,
4631 });
4632 }
4633 }
4634
4635 if ops.is_empty() {
4636 session.add_notices(warnings);
4637 return Ok(variant.into());
4638 }
4639
4640 let res = self
4641 .catalog_transact(Some(session), ops)
4642 .await
4643 .map(|_| match variant {
4644 UpdatePrivilegeVariant::Grant => ExecuteResponse::GrantedPrivilege,
4645 UpdatePrivilegeVariant::Revoke => ExecuteResponse::RevokedPrivilege,
4646 });
4647 if res.is_ok() {
4648 session.add_notices(warnings);
4649 }
4650 res
4651 }
4652
4653 #[instrument]
4654 pub(super) async fn sequence_alter_default_privileges(
4655 &mut self,
4656 session: &Session,
4657 plan::AlterDefaultPrivilegesPlan {
4658 privilege_objects,
4659 privilege_acl_items,
4660 is_grant,
4661 }: plan::AlterDefaultPrivilegesPlan,
4662 ) -> Result<ExecuteResponse, AdapterError> {
4663 let mut ops = Vec::with_capacity(privilege_objects.len() * privilege_acl_items.len());
4664 let variant = if is_grant {
4665 UpdatePrivilegeVariant::Grant
4666 } else {
4667 UpdatePrivilegeVariant::Revoke
4668 };
4669 for privilege_object in &privilege_objects {
4670 self.catalog()
4671 .ensure_not_system_role(&privilege_object.role_id)?;
4672 self.catalog()
4673 .ensure_not_predefined_role(&privilege_object.role_id)?;
4674 if let Some(database_id) = privilege_object.database_id {
4675 self.catalog()
4676 .ensure_not_reserved_object(&database_id.into(), session.conn_id())?;
4677 }
4678 if let Some(schema_id) = privilege_object.schema_id {
4679 let database_spec: ResolvedDatabaseSpecifier = privilege_object.database_id.into();
4680 let schema_spec: SchemaSpecifier = schema_id.into();
4681
4682 self.catalog().ensure_not_reserved_object(
4683 &(database_spec, schema_spec).into(),
4684 session.conn_id(),
4685 )?;
4686 }
4687 for privilege_acl_item in &privilege_acl_items {
4688 self.catalog()
4689 .ensure_not_system_role(&privilege_acl_item.grantee)?;
4690 self.catalog()
4691 .ensure_not_predefined_role(&privilege_acl_item.grantee)?;
4692 ops.push(catalog::Op::UpdateDefaultPrivilege {
4693 privilege_object: privilege_object.clone(),
4694 privilege_acl_item: privilege_acl_item.clone(),
4695 variant,
4696 })
4697 }
4698 }
4699
4700 self.catalog_transact(Some(session), ops).await?;
4701 Ok(ExecuteResponse::AlteredDefaultPrivileges)
4702 }
4703
4704 #[instrument]
4705 pub(super) async fn sequence_grant_role(
4706 &mut self,
4707 session: &Session,
4708 plan::GrantRolePlan {
4709 role_ids,
4710 member_ids,
4711 grantor_id,
4712 }: plan::GrantRolePlan,
4713 ) -> Result<ExecuteResponse, AdapterError> {
4714 let catalog = self.catalog();
4715 let mut ops = Vec::with_capacity(role_ids.len() * member_ids.len());
4716 for role_id in role_ids {
4717 for member_id in &member_ids {
4718 let member_membership: BTreeSet<_> =
4719 catalog.get_role(member_id).membership().keys().collect();
4720 if member_membership.contains(&role_id) {
4721 let role_name = catalog.get_role(&role_id).name().to_string();
4722 let member_name = catalog.get_role(member_id).name().to_string();
4723 catalog.ensure_not_reserved_role(member_id)?;
4725 catalog.ensure_grantable_role(&role_id)?;
4726 session.add_notice(AdapterNotice::RoleMembershipAlreadyExists {
4727 role_name,
4728 member_name,
4729 });
4730 } else {
4731 ops.push(catalog::Op::GrantRole {
4732 role_id,
4733 member_id: *member_id,
4734 grantor_id,
4735 });
4736 }
4737 }
4738 }
4739
4740 if ops.is_empty() {
4741 return Ok(ExecuteResponse::GrantedRole);
4742 }
4743
4744 self.catalog_transact(Some(session), ops)
4745 .await
4746 .map(|_| ExecuteResponse::GrantedRole)
4747 }
4748
4749 #[instrument]
4750 pub(super) async fn sequence_revoke_role(
4751 &mut self,
4752 session: &Session,
4753 plan::RevokeRolePlan {
4754 role_ids,
4755 member_ids,
4756 grantor_id,
4757 }: plan::RevokeRolePlan,
4758 ) -> Result<ExecuteResponse, AdapterError> {
4759 let catalog = self.catalog();
4760 let mut ops = Vec::with_capacity(role_ids.len() * member_ids.len());
4761 for role_id in role_ids {
4762 for member_id in &member_ids {
4763 let member_membership: BTreeSet<_> =
4764 catalog.get_role(member_id).membership().keys().collect();
4765 if !member_membership.contains(&role_id) {
4766 let role_name = catalog.get_role(&role_id).name().to_string();
4767 let member_name = catalog.get_role(member_id).name().to_string();
4768 catalog.ensure_not_reserved_role(member_id)?;
4770 catalog.ensure_grantable_role(&role_id)?;
4771 session.add_notice(AdapterNotice::RoleMembershipDoesNotExists {
4772 role_name,
4773 member_name,
4774 });
4775 } else {
4776 ops.push(catalog::Op::RevokeRole {
4777 role_id,
4778 member_id: *member_id,
4779 grantor_id,
4780 });
4781 }
4782 }
4783 }
4784
4785 if ops.is_empty() {
4786 return Ok(ExecuteResponse::RevokedRole);
4787 }
4788
4789 self.catalog_transact(Some(session), ops)
4790 .await
4791 .map(|_| ExecuteResponse::RevokedRole)
4792 }
4793
4794 #[instrument]
4795 pub(super) async fn sequence_alter_owner(
4796 &mut self,
4797 session: &Session,
4798 plan::AlterOwnerPlan {
4799 id,
4800 object_type,
4801 new_owner,
4802 }: plan::AlterOwnerPlan,
4803 ) -> Result<ExecuteResponse, AdapterError> {
4804 let mut ops = vec![catalog::Op::UpdateOwner {
4805 id: id.clone(),
4806 new_owner,
4807 }];
4808
4809 match &id {
4810 ObjectId::Item(global_id) => {
4811 let entry = self.catalog().get_entry(global_id);
4812
4813 if entry.is_index() {
4815 let name = self
4816 .catalog()
4817 .resolve_full_name(entry.name(), Some(session.conn_id()))
4818 .to_string();
4819 session.add_notice(AdapterNotice::AlterIndexOwner { name });
4820 return Ok(ExecuteResponse::AlteredObject(object_type));
4821 }
4822
4823 let dependent_index_ops = entry
4825 .used_by()
4826 .into_iter()
4827 .filter(|id| self.catalog().get_entry(id).is_index())
4828 .map(|id| catalog::Op::UpdateOwner {
4829 id: ObjectId::Item(*id),
4830 new_owner,
4831 });
4832 ops.extend(dependent_index_ops);
4833
4834 let dependent_subsources =
4836 entry
4837 .progress_id()
4838 .into_iter()
4839 .map(|item_id| catalog::Op::UpdateOwner {
4840 id: ObjectId::Item(item_id),
4841 new_owner,
4842 });
4843 ops.extend(dependent_subsources);
4844 }
4845 ObjectId::Cluster(cluster_id) => {
4846 let cluster = self.catalog().get_cluster(*cluster_id);
4847 let managed_cluster_replica_ops =
4849 cluster.replicas().map(|replica| catalog::Op::UpdateOwner {
4850 id: ObjectId::ClusterReplica((cluster.id(), replica.replica_id())),
4851 new_owner,
4852 });
4853 ops.extend(managed_cluster_replica_ops);
4854 }
4855 _ => {}
4856 }
4857
4858 self.catalog_transact(Some(session), ops)
4859 .await
4860 .map(|_| ExecuteResponse::AlteredObject(object_type))
4861 }
4862
4863 #[instrument]
4864 pub(super) async fn sequence_reassign_owned(
4865 &mut self,
4866 session: &Session,
4867 plan::ReassignOwnedPlan {
4868 old_roles,
4869 new_role,
4870 reassign_ids,
4871 }: plan::ReassignOwnedPlan,
4872 ) -> Result<ExecuteResponse, AdapterError> {
4873 for role_id in old_roles.iter().chain(iter::once(&new_role)) {
4874 self.catalog().ensure_not_reserved_role(role_id)?;
4875 }
4876
4877 let ops = reassign_ids
4878 .into_iter()
4879 .map(|id| catalog::Op::UpdateOwner {
4880 id,
4881 new_owner: new_role,
4882 })
4883 .collect();
4884
4885 self.catalog_transact(Some(session), ops)
4886 .await
4887 .map(|_| ExecuteResponse::ReassignOwned)
4888 }
4889
4890 #[instrument]
4891 pub(crate) async fn handle_deferred_statement(&mut self) {
4892 let Some(DeferredPlanStatement { ctx, ps }) = self.serialized_ddl.pop_front() else {
4896 return;
4897 };
4898 match ps {
4899 crate::coord::PlanStatement::Statement { stmt, params } => {
4900 self.handle_execute_inner(stmt, params, ctx).await;
4901 }
4902 crate::coord::PlanStatement::Plan {
4903 plan,
4904 resolved_ids,
4905 sql_impl_resolved_ids,
4906 } => {
4907 self.sequence_plan(ctx, plan, resolved_ids, sql_impl_resolved_ids)
4908 .await;
4909 }
4910 }
4911 }
4912
4913 #[instrument]
4914 #[allow(clippy::unused_async)]
4916 pub(super) async fn sequence_alter_table(
4917 &mut self,
4918 ctx: &mut ExecuteContext,
4919 plan: plan::AlterTablePlan,
4920 ) -> Result<ExecuteResponse, AdapterError> {
4921 let plan::AlterTablePlan {
4922 relation_id,
4923 column_name,
4924 column_type,
4925 raw_sql_type,
4926 } = plan;
4927
4928 let (_, new_global_id) = self.allocate_user_id().await?;
4930 let ops = vec![catalog::Op::AlterAddColumn {
4931 id: relation_id,
4932 new_global_id,
4933 name: column_name,
4934 typ: column_type,
4935 sql: raw_sql_type,
4936 }];
4937
4938 self.catalog_transact_with_context(None, Some(ctx), ops)
4939 .await?;
4940
4941 Ok(ExecuteResponse::AlteredObject(ObjectType::Table))
4942 }
4943
4944 #[instrument]
4946 pub(super) async fn sequence_alter_materialized_view_apply_replacement_prepare(
4947 &mut self,
4948 ctx: ExecuteContext,
4949 plan: AlterMaterializedViewApplyReplacementPlan,
4950 ) {
4951 let AlterMaterializedViewApplyReplacementPlan { id, replacement_id } = plan.clone();
4961
4962 let plan_validity = PlanValidity::new(
4963 self.catalog(),
4964 BTreeSet::from_iter([id, replacement_id]),
4965 None,
4966 None,
4967 ctx.session().role_metadata().clone(),
4968 );
4969
4970 let target = self.catalog.get_entry(&id);
4971 let target_gid = target.latest_global_id();
4972
4973 let replacement = self.catalog.get_entry(&replacement_id);
4974 let replacement_gid = replacement.latest_global_id();
4975
4976 let target_upper = self
4977 .controller
4978 .storage_collections
4979 .collection_frontiers(target_gid)
4980 .expect("target MV exists")
4981 .write_frontier;
4982 let replacement_upper = self
4983 .controller
4984 .compute
4985 .collection_frontiers(replacement_gid, replacement.cluster_id())
4986 .expect("replacement MV exists")
4987 .write_frontier;
4988
4989 info!(
4990 %id, %replacement_id, ?target_upper, ?replacement_upper,
4991 "preparing materialized view replacement application",
4992 );
4993
4994 let Some(replacement_upper_ts) = replacement_upper.into_option() else {
4995 ctx.retire(Err(AdapterError::ReplaceMaterializedViewSealed {
5004 name: target.name().item.clone(),
5005 }));
5006 return;
5007 };
5008
5009 let replacement_upper_ts = replacement_upper_ts.step_back().unwrap_or(Timestamp::MIN);
5013
5014 self.install_storage_watch_set(
5018 ctx.session().conn_id().clone(),
5019 BTreeSet::from_iter([target_gid]),
5020 replacement_upper_ts,
5021 WatchSetResponse::AlterMaterializedViewReady(AlterMaterializedViewReadyContext {
5022 ctx: Some(ctx),
5023 otel_ctx: OpenTelemetryContext::obtain(),
5024 plan,
5025 plan_validity,
5026 }),
5027 )
5028 .expect("target collection exists");
5029 }
5030
5031 #[instrument]
5033 pub async fn sequence_alter_materialized_view_apply_replacement_finish(
5034 &mut self,
5035 mut ctx: AlterMaterializedViewReadyContext,
5036 ) {
5037 ctx.otel_ctx.attach_as_parent();
5038
5039 let AlterMaterializedViewApplyReplacementPlan { id, replacement_id } = ctx.plan;
5040
5041 if let Err(err) = ctx.plan_validity.check(self.catalog()) {
5046 ctx.retire(Err(err));
5047 return;
5048 }
5049
5050 info!(
5051 %id, %replacement_id,
5052 "finishing materialized view replacement application",
5053 );
5054
5055 let invalidate_ids = self.catalog().get_entry(&id).global_ids().collect();
5061 self.catalog()
5062 .update_expression_cache(Vec::new(), Vec::new(), invalidate_ids)
5063 .await;
5064
5065 let ops = vec![catalog::Op::AlterMaterializedViewApplyReplacement { id, replacement_id }];
5066 match self
5067 .catalog_transact(Some(ctx.ctx().session_mut()), ops)
5068 .await
5069 {
5070 Ok(()) => ctx.retire(Ok(ExecuteResponse::AlteredObject(
5071 ObjectType::MaterializedView,
5072 ))),
5073 Err(err) => ctx.retire(Err(err)),
5074 }
5075 }
5076
5077 pub(super) async fn statistics_oracle(
5078 &self,
5079 session: &Session,
5080 source_ids: &BTreeSet<GlobalId>,
5081 query_as_of: &Antichain<Timestamp>,
5082 is_oneshot: bool,
5083 ) -> Result<Box<dyn mz_transform::StatisticsOracle>, AdapterError> {
5084 super::statistics_oracle(
5085 session,
5086 source_ids,
5087 query_as_of,
5088 is_oneshot,
5089 self.catalog().system_config(),
5090 self.controller.storage_collections.as_ref(),
5091 )
5092 .await
5093 }
5094}
5095
5096impl Coordinator {
5097 pub(crate) fn emit_raw_optimizer_notices_to_user(
5105 &self,
5106 ctx: &ExecuteContext,
5107 notices: &[RawOptimizerNotice],
5108 ) {
5109 emit_optimizer_notices(&*self.catalog, ctx.session(), notices);
5110 }
5111
5112 fn render_create_item_notices(
5122 &self,
5123 name: &QualifiedItemName,
5124 global_id: GlobalId,
5125 source_desc: &RelationDesc,
5126 raw_df_meta: &DataflowMetainfo,
5127 ) -> DataflowMetainfo<Arc<OptimizerNotice>> {
5128 let notice_ids = std::iter::repeat_with(|| self.allocate_transient_id())
5129 .map(|(_item_id, notice_id)| notice_id)
5130 .take(raw_df_meta.optimizer_notices.len())
5131 .collect::<Vec<_>>();
5132
5133 let system_catalog = self.catalog().for_system_session();
5134 let full_name = self.catalog().resolve_full_name(name, None);
5135 let transient_items = btreemap! {
5136 global_id => TransientItem::new(
5137 Some(full_name.into_parts()),
5138 Some(source_desc.iter_names().map(|c| c.to_string()).collect()),
5139 )
5140 };
5141 let humanizer = ExprHumanizerExt::new(transient_items, &system_catalog);
5142 CatalogState::render_notices_core(
5143 &humanizer,
5144 (self.catalog().config().now)(),
5145 raw_df_meta,
5146 notice_ids,
5147 Some(global_id),
5148 )
5149 }
5150
5151 async fn ship_new_dataflow(
5158 &mut self,
5159 id_bundle: &CollectionIdBundle,
5160 mut df_desc: DataflowDescription<LirRelationExpr>,
5161 instance: ComputeInstanceId,
5162 notice_builtin_updates_fut: Option<BuiltinTableAppendNotify>,
5163 ) {
5164 let read_holds = self.acquire_read_holds(id_bundle);
5165 let since = read_holds.least_valid_read();
5166 df_desc.set_as_of(since);
5167
5168 self.ship_dataflow_and_notice_builtin_table_updates(
5169 df_desc,
5170 instance,
5171 notice_builtin_updates_fut,
5172 None,
5173 )
5174 .await;
5175
5176 drop(read_holds);
5177 }
5178
5179 fn persist_dataflow_metainfo(
5189 &mut self,
5190 df_meta: DataflowMetainfo<Arc<OptimizerNotice>>,
5191 export_id: GlobalId,
5192 ) -> Option<BuiltinTableAppendNotify> {
5193 if self.catalog().state().system_config().enable_mz_notices()
5196 && !df_meta.optimizer_notices.is_empty()
5197 {
5198 let mut builtin_table_updates = Vec::with_capacity(df_meta.optimizer_notices.len());
5199 self.catalog().state().pack_optimizer_notices(
5200 &mut builtin_table_updates,
5201 df_meta.optimizer_notices.iter(),
5202 Diff::ONE,
5203 );
5204
5205 self.catalog_mut().set_dataflow_metainfo(export_id, df_meta);
5207
5208 Some(self.builtin_table_update().execute(builtin_table_updates))
5209 } else {
5210 self.catalog_mut().set_dataflow_metainfo(export_id, df_meta);
5212
5213 None
5214 }
5215 }
5216}