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