Skip to main content

mz_adapter/coord/sequencer/
inner.rs

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