Skip to main content

mz_adapter/coord/
sequencer.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
10// Prevents anyone from accidentally exporting a method from the `inner` module.
11#![allow(clippy::pub_use)]
12
13//! Logic for executing a planned SQL query.
14
15use std::collections::{BTreeMap, BTreeSet};
16use std::str::FromStr;
17use std::sync::Arc;
18use std::time::Duration;
19
20use futures::FutureExt;
21use futures::future::LocalBoxFuture;
22use futures::stream::FuturesOrdered;
23use http::Uri;
24use inner::return_if_err;
25use maplit::btreemap;
26use mz_catalog::memory::objects::Cluster;
27use mz_controller_types::ReplicaId;
28use mz_expr::row::RowCollection;
29use mz_expr::{Eval, MapFilterProject, MirRelationExpr, ResultSpec, RowSetFinishing};
30use mz_ore::cast::CastFrom;
31use mz_ore::tracing::OpenTelemetryContext;
32use mz_persist_client::stats::SnapshotPartStats;
33use mz_repr::explain::{ExprHumanizerExt, TransientItem};
34use mz_repr::{CatalogItemId, Datum, Diff, GlobalId, IntoRowIterator, Row, RowArena, Timestamp};
35use mz_sql::catalog::{CatalogError, SessionCatalog};
36use mz_sql::names::ResolvedIds;
37use mz_sql::plan::{
38    self, AbortTransactionPlan, CommitTransactionPlan, CreateRolePlan, CreateSourcePlanBundle,
39    FetchPlan, HirScalarExpr, MutationKind, Params, Plan, PlanKind, RaisePlan, SideEffectingFunc,
40};
41use mz_sql::rbac;
42use mz_sql::session::metadata::SessionMetadata;
43use mz_sql::session::vars;
44use mz_sql::session::vars::SessionVars;
45use mz_sql_parser::ast::{Raw, Statement};
46use mz_storage_client::client::TableData;
47use mz_storage_client::storage_collections::StorageCollections;
48use mz_storage_types::connections::inline::IntoInlineConnection;
49use mz_storage_types::controller::StorageError;
50use mz_storage_types::stats::RelationPartStats;
51use mz_transform::dataflow::DataflowMetainfo;
52use mz_transform::notice::{OptimizerNoticeApi, OptimizerNoticeKind, RawOptimizerNotice};
53use mz_transform::{EmptyStatisticsOracle, StatisticsOracle};
54use timely::progress::Antichain;
55use tokio::sync::oneshot;
56use tracing::{Instrument, Level, Span, event, warn};
57
58use crate::ExecuteContext;
59use crate::catalog::{Catalog, CatalogState};
60use crate::command::{Command, ExecuteResponse, Response};
61use crate::coord::appends::{DeferredOp, DeferredPlan};
62use crate::coord::validity::PlanValidity;
63use crate::coord::{
64    Coordinator, DeferredPlanStatement, ExplainPlanContext, Message, PlanStatement, TargetCluster,
65    catalog_serving,
66};
67use crate::error::AdapterError;
68use crate::explain::insights::PlanInsightsContext;
69use crate::notice::AdapterNotice;
70use crate::optimize::dataflows::{EvalTime, ExprPrep, ExprPrepOneShot};
71use crate::optimize::peek;
72use crate::session::{
73    EndTransactionAction, Session, StateRevision, TransactionOps, TransactionStatus, WriteOp,
74};
75use crate::util::ClientTransmitter;
76
77// DO NOT make this visible in any way, i.e. do not add any version of
78// `pub` to this mod. The inner `sequence_X` methods are hidden in this
79// private module to prevent anyone from calling them directly. All
80// sequencing should be done through the `sequence_plan` method.
81// This allows us to add catch-all logic that should be applied to all
82// plans in `sequence_plan` and guarantee that no caller can circumvent
83// that logic.
84//
85// The exceptions are:
86//
87// - Creating a role during connection startup. In this scenario, the session has not been properly
88// initialized and we need to skip directly to creating role. We have a specific method,
89// `sequence_create_role_for_startup` for this purpose.
90// - Methods that continue the execution of some plan that was being run asynchronously, such as
91// `sequence_peek_stage` and `sequence_create_connection_stage_finish`.
92// - The frontend peek sequencing temporarily reaches into this module for things that are needed
93//   by both the old and new peek sequencing. TODO(peek-seq): We plan to eliminate this with a
94//   big refactoring after the old peek sequencing is removed.
95
96mod inner;
97
98impl Coordinator {
99    /// BOXED FUTURE: As of Nov 2023 the returned Future from this function was 34KB. This would
100    /// get stored on the stack which is bad for runtime performance, and blow up our stack usage.
101    /// Because of that we purposefully move this Future onto the heap (i.e. Box it).
102    pub(crate) fn sequence_plan(
103        &mut self,
104        mut ctx: ExecuteContext,
105        plan: Plan,
106        resolved_ids: ResolvedIds,
107        sql_impl_resolved_ids: ResolvedIds,
108    ) -> LocalBoxFuture<'_, ()> {
109        async move {
110            let responses = ExecuteResponse::generated_from(&PlanKind::from(&plan));
111            ctx.tx_mut().set_allowed(responses);
112
113            if self.controller.read_only() && !plan.allowed_in_read_only() {
114                ctx.retire(Err(AdapterError::ReadOnly));
115                return;
116            }
117
118            // Check if we're still waiting for any of the builtin table appends from when we
119            // started the Session to complete.
120            if let Some((dependencies, wait_future)) =
121                super::appends::waiting_on_startup_appends(self.catalog(), ctx.session_mut(), &plan)
122            {
123                let conn_id = ctx.session().conn_id();
124                tracing::debug!(%conn_id, "deferring plan for startup appends");
125
126                let role_metadata = ctx.session().role_metadata().clone();
127                let validity =
128                    PlanValidity::new(&self.catalog, dependencies, None, None, role_metadata);
129                let deferred_plan = DeferredPlan {
130                    ctx,
131                    plan,
132                    validity,
133                    requires_locks: BTreeSet::default(),
134                    resolved_ids,
135                    sql_impl_resolved_ids,
136                };
137                // Defer op accepts an optional write lock, but there aren't any writes occurring
138                // here, since the map to `None`.
139                let acquire_future = wait_future.map(|()| None);
140
141                self.defer_op(acquire_future, DeferredOp::Plan(deferred_plan));
142
143                // Return early because our op is deferred on waiting for the builtin writes to
144                // complete.
145                return;
146            };
147
148            // Scope the borrow of the Catalog because we need to mutate the Coordinator state below.
149            let target_cluster = match ctx.session().transaction().cluster() {
150                // Use the current transaction's cluster.
151                Some(cluster_id) => TargetCluster::Transaction(cluster_id),
152                // If there isn't a current cluster set for a transaction, then try to auto route.
153                None => {
154                    let session_catalog = self.catalog.for_session(ctx.session());
155                    catalog_serving::auto_run_on_catalog_server(
156                        &session_catalog,
157                        ctx.session(),
158                        &plan,
159                    )
160                }
161            };
162            let (target_cluster_id, target_cluster_name) = match self
163                .catalog()
164                .resolve_target_cluster(target_cluster, ctx.session())
165            {
166                Ok(cluster) => (Some(cluster.id), Some(cluster.name.clone())),
167                Err(_) => (None, None),
168            };
169
170            if let (Some(cluster_id), Some(cluster_name), Some(statement_id)) = (
171                target_cluster_id,
172                target_cluster_name.clone(),
173                ctx.extra().contents(),
174            ) {
175                self.set_statement_execution_cluster(statement_id, cluster_id, cluster_name);
176            }
177
178            let session_catalog = self.catalog.for_session(ctx.session());
179
180            if let Some(cluster_name) = &target_cluster_name {
181                if let Err(e) = catalog_serving::check_cluster_restrictions(
182                    cluster_name,
183                    &session_catalog,
184                    &plan,
185                ) {
186                    return ctx.retire(Err(e));
187                }
188            }
189
190            // Look up the authenticated role of the connection targeted by
191            // pg_cancel_backend, which check_plan needs for its RBAC check.
192            // Linear search through active connections is fine because this
193            // happens at most once per statement.
194            let target_conn_role = match &plan {
195                Plan::SideEffectingFunc(SideEffectingFunc::PgCancelBackend {
196                    connection_id: Some(connection_id),
197                }) => self
198                    .active_conns()
199                    .into_iter()
200                    .find(|(conn_id, _)| conn_id.unhandled() == *connection_id)
201                    .map(|(_, conn_meta)| *conn_meta.authenticated_role_id()),
202                _ => None,
203            };
204
205            if let Err(e) = rbac::check_plan(
206                &session_catalog,
207                target_conn_role,
208                ctx.session(),
209                &plan,
210                target_cluster_id,
211                &resolved_ids,
212                &sql_impl_resolved_ids,
213            ) {
214                return ctx.retire(Err(e.into()));
215            }
216
217            match plan {
218                Plan::CreateSource(plan) => {
219                    let (item_id, global_id) = return_if_err!(self.allocate_user_id().await, ctx);
220                    let result = self
221                        .sequence_create_source(
222                            &mut ctx,
223                            vec![CreateSourcePlanBundle {
224                                item_id,
225                                global_id,
226                                plan,
227                                resolved_ids,
228                                available_source_references: None,
229                            }],
230                        )
231                        .await;
232                    ctx.retire(result);
233                }
234                Plan::CreateSources(plans) => {
235                    assert!(
236                        resolved_ids.is_empty(),
237                        "each plan has separate resolved_ids"
238                    );
239                    let result = self.sequence_create_source(&mut ctx, plans).await;
240                    ctx.retire(result);
241                }
242                Plan::CreateConnection(plan) => {
243                    self.sequence_create_connection(ctx, plan, resolved_ids)
244                        .await;
245                }
246                Plan::CreateDatabase(plan) => {
247                    let result = self.sequence_create_database(ctx.session_mut(), plan).await;
248                    ctx.retire(result);
249                }
250                Plan::CreateSchema(plan) => {
251                    let result = self.sequence_create_schema(ctx.session_mut(), plan).await;
252                    ctx.retire(result);
253                }
254                Plan::CreateRole(plan) => {
255                    let result = self
256                        .sequence_create_role(Some(ctx.session().conn_id()), plan)
257                        .await;
258                    if let Some(notice) = self.should_emit_rbac_notice(ctx.session()) {
259                        ctx.session().add_notice(notice);
260                    }
261                    ctx.retire(result);
262                }
263                Plan::CreateCluster(plan) => {
264                    let result = self.sequence_create_cluster(ctx.session(), plan).await;
265                    ctx.retire(result);
266                }
267                Plan::CreateClusterReplica(plan) => {
268                    let result = self
269                        .sequence_create_cluster_replica(ctx.session(), plan)
270                        .await;
271                    ctx.retire(result);
272                }
273                Plan::CreateTable(plan) => {
274                    let result = self
275                        .sequence_create_table(&mut ctx, plan, resolved_ids)
276                        .await;
277                    ctx.retire(result);
278                }
279                Plan::CreateSecret(plan) => {
280                    self.sequence_create_secret(ctx, plan).await;
281                }
282                Plan::CreateSink(plan) => {
283                    self.sequence_create_sink(ctx, plan, resolved_ids).await;
284                }
285                Plan::CreateView(plan) => {
286                    self.sequence_create_view(ctx, plan, resolved_ids).await;
287                }
288                Plan::CreateMaterializedView(plan) => {
289                    self.sequence_create_materialized_view(ctx, plan, resolved_ids)
290                        .await;
291                }
292                Plan::CreateIndex(plan) => {
293                    self.sequence_create_index(ctx, plan, resolved_ids).await;
294                }
295                Plan::CreateMetricSink(plan) => {
296                    self.sequence_create_metric_sink(ctx, plan, resolved_ids)
297                        .await;
298                }
299                Plan::CreateType(plan) => {
300                    let result = self
301                        .sequence_create_type(ctx.session(), plan, resolved_ids)
302                        .await;
303                    ctx.retire(result);
304                }
305                Plan::CreateNetworkPolicy(plan) => {
306                    let res = self
307                        .sequence_create_network_policy(ctx.session(), plan)
308                        .await;
309                    ctx.retire(res);
310                }
311                Plan::Comment(plan) => {
312                    let result = self.sequence_comment_on(ctx.session(), plan).await;
313                    ctx.retire(result);
314                }
315                Plan::CopyTo(plan) => {
316                    self.sequence_copy_to(ctx, plan, target_cluster).await;
317                }
318                Plan::DropObjects(plan) => {
319                    let result = self.sequence_drop_objects(&mut ctx, plan).await;
320                    ctx.retire(result);
321                }
322                Plan::DropOwned(plan) => {
323                    let result = self.sequence_drop_owned(ctx.session_mut(), plan).await;
324                    ctx.retire(result);
325                }
326                Plan::EmptyQuery => {
327                    ctx.retire(Ok(ExecuteResponse::EmptyQuery));
328                }
329                Plan::ShowAllVariables => {
330                    let result = self.sequence_show_all_variables(ctx.session());
331                    ctx.retire(result);
332                }
333                Plan::ShowVariable(plan) => {
334                    let result = self.sequence_show_variable(ctx.session(), plan);
335                    ctx.retire(result);
336                }
337                Plan::InspectShard(plan) => {
338                    // TODO: Ideally, this await would happen off the main thread.
339                    let result = self.sequence_inspect_shard(ctx.session(), plan).await;
340                    ctx.retire(result);
341                }
342                Plan::SetVariable(plan) => {
343                    let result = self.sequence_set_variable(ctx.session_mut(), plan);
344                    ctx.retire(result);
345                }
346                Plan::ResetVariable(plan) => {
347                    let result = self.sequence_reset_variable(ctx.session_mut(), plan);
348                    ctx.retire(result);
349                }
350                Plan::SetTransaction(plan) => {
351                    let result = self.sequence_set_transaction(ctx.session_mut(), plan);
352                    ctx.retire(result);
353                }
354                Plan::StartTransaction(plan) => {
355                    if matches!(
356                        ctx.session().transaction(),
357                        TransactionStatus::InTransaction(_)
358                    ) {
359                        ctx.session()
360                            .add_notice(AdapterNotice::ExistingTransactionInProgress);
361                    }
362                    let result = ctx.session_mut().start_transaction(
363                        self.now_datetime(),
364                        plan.access,
365                        plan.isolation_level,
366                    );
367                    ctx.retire(result.map(|_| ExecuteResponse::StartedTransaction))
368                }
369                Plan::CommitTransaction(CommitTransactionPlan {
370                    ref transaction_type,
371                })
372                | Plan::AbortTransaction(AbortTransactionPlan {
373                    ref transaction_type,
374                }) => {
375                    // Serialize DDL transactions. Statements that use this mode must return false
376                    // in `must_serialize_ddl()`.
377                    if ctx.session().transaction().is_ddl() {
378                        if let Ok(guard) = self.serialized_ddl.try_lock_owned() {
379                            let prev = self
380                                .active_conns
381                                .get_mut(ctx.session().conn_id())
382                                .expect("connection must exist")
383                                .deferred_lock
384                                .replace(guard);
385                            assert!(
386                                prev.is_none(),
387                                "connections should have at most one lock guard"
388                            );
389                        } else {
390                            self.serialized_ddl.push_back(DeferredPlanStatement {
391                                ctx,
392                                ps: PlanStatement::Plan {
393                                    plan,
394                                    resolved_ids,
395                                    sql_impl_resolved_ids,
396                                },
397                            });
398                            return;
399                        }
400                    }
401
402                    let action = match &plan {
403                        Plan::CommitTransaction(_) => EndTransactionAction::Commit,
404                        Plan::AbortTransaction(_) => EndTransactionAction::Rollback,
405                        _ => unreachable!(),
406                    };
407                    if ctx.session().transaction().is_implicit() && !transaction_type.is_implicit()
408                    {
409                        // In Postgres, if a user sends a COMMIT or ROLLBACK in an
410                        // implicit transaction, a warning is sent warning them.
411                        // (The transaction is still closed and a new implicit
412                        // transaction started, though.)
413                        ctx.session().add_notice(
414                            AdapterNotice::ExplicitTransactionControlInImplicitTransaction,
415                        );
416                    }
417                    self.sequence_end_transaction(ctx, action).await;
418                }
419                Plan::Select(plan) => {
420                    let max = Some(ctx.session().vars().max_query_result_size());
421                    self.sequence_peek(ctx, plan, target_cluster, max).await;
422                }
423                Plan::Subscribe(plan) => {
424                    self.sequence_subscribe(ctx, plan, target_cluster).await;
425                }
426                Plan::SideEffectingFunc(plan) => {
427                    self.sequence_side_effecting_func(ctx, plan).await;
428                }
429                Plan::ShowCreate(plan) => {
430                    ctx.retire(Ok(Self::send_immediate_rows(plan.row)));
431                }
432                Plan::ShowColumns(show_columns_plan) => {
433                    let max = Some(ctx.session().vars().max_query_result_size());
434                    self.sequence_peek(ctx, show_columns_plan.select_plan, target_cluster, max)
435                        .await;
436                }
437                Plan::CopyFrom(plan) => {
438                    self.sequence_copy_from(ctx, plan, target_cluster).await;
439                }
440                Plan::ExplainPlan(plan) => {
441                    self.sequence_explain_plan(ctx, plan, target_cluster).await;
442                }
443                Plan::ExplainPushdown(plan) => {
444                    self.sequence_explain_pushdown(ctx, plan, target_cluster)
445                        .await;
446                }
447                Plan::ExplainSinkSchema(plan) => {
448                    let result = self.sequence_explain_schema(plan);
449                    ctx.retire(result);
450                }
451                Plan::ExplainTimestamp(plan) => {
452                    self.sequence_explain_timestamp(ctx, plan, target_cluster)
453                        .await;
454                }
455                Plan::Insert(plan) => {
456                    self.sequence_insert(ctx, plan).await;
457                }
458                Plan::ReadThenWrite(plan) => {
459                    self.sequence_read_then_write(ctx, plan).await;
460                }
461                Plan::AlterNoop(plan) => {
462                    ctx.retire(Ok(ExecuteResponse::AlteredObject(plan.object_type)));
463                }
464                Plan::AlterCluster(plan) => {
465                    self.sequence_alter_cluster_staged(ctx, plan).await;
466                }
467                Plan::AlterClusterRename(plan) => {
468                    let result = self.sequence_alter_cluster_rename(&mut ctx, plan).await;
469                    ctx.retire(result);
470                }
471                Plan::AlterClusterSwap(plan) => {
472                    let result = self.sequence_alter_cluster_swap(&mut ctx, plan).await;
473                    ctx.retire(result);
474                }
475                Plan::AlterClusterReplicaRename(plan) => {
476                    let result = self
477                        .sequence_alter_cluster_replica_rename(ctx.session(), plan)
478                        .await;
479                    ctx.retire(result);
480                }
481                Plan::AlterConnection(plan) => {
482                    self.sequence_alter_connection(ctx, plan).await;
483                }
484                Plan::AlterSetCluster(plan) => {
485                    let result = self.sequence_alter_set_cluster(ctx.session(), plan).await;
486                    ctx.retire(result);
487                }
488                Plan::AlterRetainHistory(plan) => {
489                    let result = self.sequence_alter_retain_history(&mut ctx, plan).await;
490                    ctx.retire(result);
491                }
492                Plan::AlterSourceTimestampInterval(plan) => {
493                    let result = self
494                        .sequence_alter_source_timestamp_interval(&mut ctx, plan)
495                        .await;
496                    ctx.retire(result);
497                }
498                Plan::AlterItemRename(plan) => {
499                    let result = self.sequence_alter_item_rename(&mut ctx, plan).await;
500                    ctx.retire(result);
501                }
502                Plan::AlterSchemaRename(plan) => {
503                    let result = self.sequence_alter_schema_rename(&mut ctx, plan).await;
504                    ctx.retire(result);
505                }
506                Plan::AlterSchemaSwap(plan) => {
507                    let result = self.sequence_alter_schema_swap(&mut ctx, plan).await;
508                    ctx.retire(result);
509                }
510                Plan::AlterRole(plan) => {
511                    let result = self.sequence_alter_role(ctx.session_mut(), plan).await;
512                    ctx.retire(result);
513                }
514                Plan::AlterSecret(plan) => {
515                    self.sequence_alter_secret(ctx, plan).await;
516                }
517                Plan::AlterSink(plan) => {
518                    self.sequence_alter_sink_prepare(ctx, plan).await;
519                }
520                Plan::AlterSource(plan) => {
521                    let result = self.sequence_alter_source(ctx.session_mut(), plan).await;
522                    ctx.retire(result);
523                }
524                Plan::AlterSystemSet(plan) => {
525                    let result = self.sequence_alter_system_set(ctx.session(), plan).await;
526                    ctx.retire(result);
527                }
528                Plan::AlterSystemReset(plan) => {
529                    let result = self.sequence_alter_system_reset(ctx.session(), plan).await;
530                    ctx.retire(result);
531                }
532                Plan::AlterSystemResetAll(plan) => {
533                    let result = self
534                        .sequence_alter_system_reset_all(ctx.session(), plan)
535                        .await;
536                    ctx.retire(result);
537                }
538                Plan::AlterTableAddColumn(plan) => {
539                    let result = self.sequence_alter_table(&mut ctx, plan).await;
540                    ctx.retire(result);
541                }
542                Plan::AlterMaterializedViewApplyReplacement(plan) => {
543                    self.sequence_alter_materialized_view_apply_replacement_prepare(ctx, plan)
544                        .await;
545                }
546                Plan::AlterNetworkPolicy(plan) => {
547                    let res = self
548                        .sequence_alter_network_policy(ctx.session(), plan)
549                        .await;
550                    ctx.retire(res);
551                }
552                Plan::DiscardTemp => {
553                    self.drop_temp_items(ctx.session().conn_id()).await;
554                    ctx.retire(Ok(ExecuteResponse::DiscardedTemp));
555                }
556                Plan::DiscardAll => {
557                    // Clearing the transaction would silently discard writes staged by an
558                    // earlier statement of the same pipeline.
559                    let txn = ctx.session().transaction();
560                    let discardable =
561                        matches!(txn, TransactionStatus::Started(_)) && !txn.contains_ops();
562                    let ret = if discardable {
563                        let (_, retire_notify) = self.clear_transaction(ctx.session_mut()).await;
564                        ctx.delay_response_until(retire_notify);
565                        self.drop_temp_items(ctx.session().conn_id()).await;
566                        // NOTE: `reset()` resets session variables durably (see
567                        // `SessionVars::reset_all`). This must stay durable and
568                        // must not be reordered to depend on a transaction
569                        // commit: the transaction was just cleared above, so
570                        // there is no commit left to promote a staged reset.
571                        ctx.session_mut().reset();
572                        Ok(ExecuteResponse::DiscardedAll)
573                    } else {
574                        Err(AdapterError::OperationProhibitsTransaction(
575                            "DISCARD ALL".into(),
576                        ))
577                    };
578                    ctx.retire(ret);
579                }
580                Plan::Declare(plan) => {
581                    self.declare(ctx, plan.name, plan.stmt, plan.sql, plan.params);
582                }
583                Plan::Fetch(FetchPlan {
584                    name,
585                    count,
586                    timeout,
587                }) => {
588                    let ctx_extra = std::mem::take(ctx.extra_mut());
589                    ctx.retire(Ok(ExecuteResponse::Fetch {
590                        name,
591                        count,
592                        timeout,
593                        ctx_extra,
594                    }));
595                }
596                Plan::Close(plan) => {
597                    if ctx.session_mut().remove_portal(&plan.name) {
598                        ctx.retire(Ok(ExecuteResponse::ClosedCursor));
599                    } else {
600                        ctx.retire(Err(AdapterError::UnknownCursor(plan.name)));
601                    }
602                }
603                Plan::Prepare(plan) => {
604                    if ctx
605                        .session()
606                        .get_prepared_statement_unverified(&plan.name)
607                        .is_some()
608                    {
609                        ctx.retire(Err(AdapterError::PreparedStatementExists(plan.name)));
610                    } else {
611                        let state_revision = StateRevision {
612                            catalog_revision: self.catalog().transient_revision(),
613                            session_state_revision: ctx.session().state_revision(),
614                        };
615                        ctx.session_mut().set_prepared_statement(
616                            plan.name,
617                            Some(plan.stmt),
618                            plan.sql,
619                            plan.desc,
620                            state_revision,
621                            self.now(),
622                        );
623                        ctx.retire(Ok(ExecuteResponse::Prepare));
624                    }
625                }
626                Plan::Execute(plan) => {
627                    match self.sequence_execute(ctx.session_mut(), plan) {
628                        Ok(portal_name) => {
629                            let (tx, _, session, extra, response_barriers) = ctx.into_parts();
630                            // The obligation travels as data and
631                            // `handle_execute` arms it again. It cannot be
632                            // dropped in between: the command goes to the
633                            // channel this loop drains, and a spawned barrier
634                            // task is only dropped when the process is going
635                            // down, at which point nothing records anything.
636                            let command = Message::Command(
637                                OpenTelemetryContext::obtain(),
638                                Command::Execute {
639                                    portal_name,
640                                    session,
641                                    tx: tx.take(),
642                                    outer_ctx_extra: Some(extra.defuse()),
643                                },
644                            );
645                            if response_barriers.is_empty() {
646                                self.internal_cmd_tx
647                                    .send(command)
648                                    .expect("sending to self.internal_cmd_tx cannot fail");
649                            } else {
650                                let internal_cmd_tx = self.internal_cmd_tx.clone();
651                                mz_ore::task::spawn(
652                                    || "execute_after_response_barriers",
653                                    async move {
654                                        for barrier in response_barriers {
655                                            barrier.await;
656                                        }
657                                        let _ = internal_cmd_tx.send(command);
658                                    },
659                                );
660                            }
661                        }
662                        Err(err) => ctx.retire(Err(err)),
663                    };
664                }
665                Plan::Deallocate(plan) => match plan.name {
666                    Some(name) => {
667                        if ctx.session_mut().remove_prepared_statement(&name) {
668                            ctx.retire(Ok(ExecuteResponse::Deallocate { all: false }));
669                        } else {
670                            ctx.retire(Err(AdapterError::UnknownPreparedStatement(name)));
671                        }
672                    }
673                    None => {
674                        ctx.session_mut().remove_all_prepared_statements();
675                        ctx.retire(Ok(ExecuteResponse::Deallocate { all: true }));
676                    }
677                },
678                Plan::Raise(RaisePlan { severity }) => {
679                    ctx.session()
680                        .add_notice(AdapterNotice::UserRequested { severity });
681                    ctx.retire(Ok(ExecuteResponse::Raised));
682                }
683                Plan::GrantPrivileges(plan) => {
684                    let result = self
685                        .sequence_grant_privileges(ctx.session_mut(), plan)
686                        .await;
687                    ctx.retire(result);
688                }
689                Plan::RevokePrivileges(plan) => {
690                    let result = self
691                        .sequence_revoke_privileges(ctx.session_mut(), plan)
692                        .await;
693                    ctx.retire(result);
694                }
695                Plan::AlterDefaultPrivileges(plan) => {
696                    let result = self
697                        .sequence_alter_default_privileges(ctx.session_mut(), plan)
698                        .await;
699                    ctx.retire(result);
700                }
701                Plan::GrantRole(plan) => {
702                    let result = self.sequence_grant_role(ctx.session_mut(), plan).await;
703                    ctx.retire(result);
704                }
705                Plan::RevokeRole(plan) => {
706                    let result = self.sequence_revoke_role(ctx.session_mut(), plan).await;
707                    ctx.retire(result);
708                }
709                Plan::AlterOwner(plan) => {
710                    let result = self.sequence_alter_owner(ctx.session_mut(), plan).await;
711                    ctx.retire(result);
712                }
713                Plan::ReassignOwned(plan) => {
714                    let result = self.sequence_reassign_owned(ctx.session_mut(), plan).await;
715                    ctx.retire(result);
716                }
717                Plan::ValidateConnection(plan) => {
718                    let connection = plan
719                        .connection
720                        .into_inline_connection(self.catalog().state());
721                    let current_storage_configuration = self.controller.storage.config().clone();
722                    mz_ore::task::spawn(|| "coord::validate_connection", async move {
723                        let res = match connection
724                            .validate(plan.id, &current_storage_configuration)
725                            .await
726                        {
727                            Ok(()) => Ok(ExecuteResponse::ValidatedConnection),
728                            Err(err) => Err(err.into()),
729                        };
730                        ctx.retire(res);
731                    });
732                }
733            }
734        }
735        .instrument(tracing::debug_span!("coord::sequencer::sequence_plan"))
736        .boxed_local()
737    }
738
739    #[mz_ore::instrument(level = "debug")]
740    pub(crate) async fn sequence_execute_single_statement_transaction(
741        &mut self,
742        ctx: ExecuteContext,
743        stmt: Arc<Statement<Raw>>,
744        params: Params,
745    ) {
746        // Put the session into single statement implicit so anything can execute.
747        let (tx, internal_cmd_tx, mut session, extra, response_barriers) = ctx.into_parts();
748        assert!(matches!(session.transaction(), TransactionStatus::Default));
749        session.start_transaction_single_stmt(self.now_datetime());
750        let conn_id = session.conn_id().unhandled();
751
752        // Execute the saved statement in a temp transmitter so we can run COMMIT.
753        let (sub_tx, sub_rx) = oneshot::channel();
754        let sub_ct = ClientTransmitter::new(sub_tx, self.internal_cmd_tx.clone());
755        let sub_ctx = ExecuteContext::from_parts_with_response_barriers(
756            sub_ct,
757            internal_cmd_tx,
758            session,
759            extra,
760            response_barriers,
761        );
762        self.handle_execute_inner(stmt, params, sub_ctx).await;
763
764        // The response can need off-thread processing. Wait for it elsewhere so the coordinator can
765        // continue processing.
766        let internal_cmd_tx = self.internal_cmd_tx.clone();
767        mz_ore::task::spawn(
768            || format!("execute_single_statement:{conn_id}"),
769            async move {
770                let Ok(Response {
771                    result,
772                    session,
773                    otel_ctx,
774                }) = sub_rx.await
775                else {
776                    // Coordinator went away.
777                    return;
778                };
779                otel_ctx.attach_as_parent();
780                let (sub_tx, sub_rx) = oneshot::channel();
781                let _ = internal_cmd_tx.send(Message::Command(
782                    otel_ctx,
783                    Command::Commit {
784                        action: EndTransactionAction::Commit,
785                        session,
786                        tx: sub_tx,
787                    },
788                ));
789                let Ok(commit_response) = sub_rx.await else {
790                    // Coordinator went away.
791                    return;
792                };
793                assert!(matches!(
794                    commit_response.session.transaction(),
795                    TransactionStatus::Default
796                ));
797                // The fake, generated response was already sent to the user and we don't need to
798                // ever send an `Ok(result)` to the user, because they are expecting a response from
799                // a `COMMIT`. So, always send the `COMMIT`'s result if the original statement
800                // succeeded. If it failed, we can send an error and don't need to wrap it or send a
801                // later COMMIT or ROLLBACK.
802                let result = match (result, commit_response.result) {
803                    (Ok(_), commit) => commit,
804                    (Err(result), _) => Err(result),
805                };
806                // We ignore the resp.result because it's not clear what to do if it failed since we
807                // can only send a single ExecuteResponse to tx.
808                tx.send(result, commit_response.session);
809            }
810            .instrument(Span::current()),
811        );
812    }
813
814    /// Creates a role during connection startup.
815    ///
816    /// This should not be called from anywhere except connection startup.
817    #[mz_ore::instrument(level = "debug")]
818    pub(crate) async fn sequence_create_role_for_startup(
819        &mut self,
820        plan: CreateRolePlan,
821    ) -> Result<ExecuteResponse, AdapterError> {
822        // This does not set conn_id because it's not yet in active_conns. That is because we can't
823        // make a ConnMeta until we have a role id which we don't have until after the catalog txn
824        // is committed. Passing None here means the audit log won't have a user set in the event's
825        // user field. This seems fine because it is indeed the system that is creating this role,
826        // not a user request, and the user name is still recorded in the plan, so we aren't losing
827        // information.
828        self.sequence_create_role(None, plan).await
829    }
830
831    pub(crate) fn allocate_transient_id(&self) -> (CatalogItemId, GlobalId) {
832        self.transient_id_gen.allocate_id()
833    }
834
835    fn should_emit_rbac_notice(&self, session: &Session) -> Option<AdapterNotice> {
836        if !rbac::is_rbac_enabled_for_session(self.catalog.system_config(), session) {
837            Some(AdapterNotice::RbacUserDisabled)
838        } else {
839            None
840        }
841    }
842
843    /// Inserts the rows from `constants` into the table identified by `target_id`.
844    ///
845    /// # Panics
846    ///
847    /// Panics if `target_id` doesn't refer to a table.
848    /// Panics if `constants` is not an `MirRelationExpr::Constant`.
849    pub(crate) fn insert_constant(
850        catalog: &Catalog,
851        session: &mut Session,
852        target_id: CatalogItemId,
853        constants: MirRelationExpr,
854    ) -> Result<ExecuteResponse, AdapterError> {
855        // Insert can be queued, so we need to re-verify the id exists.
856        let desc = match catalog.try_get_entry(&target_id) {
857            Some(table) => {
858                // Inserts always happen at the latest version of a table.
859                table.relation_desc_latest().expect("table has desc")
860            }
861            None => {
862                return Err(AdapterError::Catalog(mz_catalog::memory::error::Error {
863                    kind: mz_catalog::memory::error::ErrorKind::Sql(CatalogError::UnknownItem(
864                        target_id.to_string(),
865                    )),
866                }));
867            }
868        };
869
870        match constants.as_const() {
871            Some((rows, ..)) => {
872                let rows = rows.clone()?;
873                for (row, _) in &rows {
874                    for (i, datum) in row.iter().enumerate() {
875                        desc.constraints_met(i, &datum)?;
876                    }
877                }
878                let diffs_plan = plan::SendDiffsPlan {
879                    id: target_id,
880                    updates: rows,
881                    kind: MutationKind::Insert,
882                    returning: Vec::new(),
883                    max_result_size: catalog.system_config().max_result_size(),
884                };
885                Self::send_diffs(session, diffs_plan)
886            }
887            None => panic!(
888                "tried using sequence_insert_constant on non-constant MirRelationExpr\n{}",
889                constants.pretty(),
890            ),
891        }
892    }
893
894    #[mz_ore::instrument(level = "debug")]
895    pub(crate) fn send_diffs(
896        session: &mut Session,
897        mut plan: plan::SendDiffsPlan,
898    ) -> Result<ExecuteResponse, AdapterError> {
899        let affected_rows = {
900            let mut affected_rows = Diff::from(0);
901            let mut all_positive_diffs = true;
902            // If all diffs are positive, the number of affected rows is just the
903            // sum of all unconsolidated diffs.
904            for (_, diff) in plan.updates.iter() {
905                if diff.is_negative() {
906                    all_positive_diffs = false;
907                    break;
908                }
909
910                affected_rows += diff;
911            }
912
913            if !all_positive_diffs {
914                // Consolidate rows. This is useful e.g. for an UPDATE where the row
915                // doesn't change, and we need to reflect that in the number of
916                // affected rows.
917                //
918                // NOTE: This differs from PostgreSQL, where `UPDATE t SET x = x`
919                // reports the number of rows matching the WHERE clause even when
920                // no value changes. Because Materialize works in differential
921                // dataflow, the +1 and -1 diffs for an unchanged row cancel out
922                // during consolidation, so it reports 0 affected rows. This is
923                // longstanding behavior and both read-then-write paths agree on
924                // it.
925                differential_dataflow::consolidation::consolidate(&mut plan.updates);
926
927                affected_rows = Diff::ZERO;
928                // With retractions, the number of affected rows is not the number
929                // of rows we see, but the sum of the absolute value of their diffs,
930                // e.g. if one row is retracted and another is added, the total
931                // number of rows affected is 2.
932                for (_, diff) in plan.updates.iter() {
933                    affected_rows += diff.abs();
934                }
935            }
936
937            usize::try_from(affected_rows.into_inner()).expect("positive Diff must fit")
938        };
939        event!(
940            Level::TRACE,
941            affected_rows,
942            id = format!("{:?}", plan.id),
943            kind = format!("{:?}", plan.kind),
944            updates = plan.updates.len(),
945            returning = plan.returning.len(),
946        );
947
948        session.add_transaction_ops(TransactionOps::Writes(vec![WriteOp {
949            id: plan.id,
950            rows: TableData::Rows(plan.updates),
951        }]))?;
952        if !plan.returning.is_empty() {
953            let finishing = RowSetFinishing {
954                order_by: Vec::new(),
955                limit: None,
956                offset: 0,
957                project: (0..plan.returning[0].0.iter().count()).collect(),
958            };
959            let max_returned_query_size = session.vars().max_query_result_size();
960            let duration_histogram = session.metrics().row_set_finishing_seconds();
961
962            return match finishing.finish(
963                RowCollection::new(plan.returning, &finishing.order_by),
964                plan.max_result_size,
965                Some(max_returned_query_size),
966                duration_histogram,
967            ) {
968                Ok((rows, _size_bytes)) => Ok(Self::send_immediate_rows(rows)),
969                Err(e) => Err(AdapterError::ResultSize(e)),
970            };
971        }
972        Ok(match plan.kind {
973            MutationKind::Delete => ExecuteResponse::Deleted(affected_rows),
974            MutationKind::Insert => ExecuteResponse::Inserted(affected_rows),
975            MutationKind::Update => ExecuteResponse::Updated(affected_rows / 2),
976        })
977    }
978}
979
980/// Checks whether we should emit diagnostic
981/// information associated with reading per-replica sources.
982///
983/// If an unrecoverable error is found (today: an untargeted read on a
984/// cluster with a non-1 number of replicas), return that.  Otherwise,
985/// return a list of associated notices (today: we always emit exactly
986/// one notice if there are any per-replica log dependencies and if
987/// `emit_introspection_query_notice` is set, and none otherwise.)
988pub(crate) fn check_log_reads(
989    catalog: &Catalog,
990    cluster: &Cluster,
991    source_ids: &BTreeSet<GlobalId>,
992    target_replica: &mut Option<ReplicaId>,
993    vars: &SessionVars,
994) -> Result<impl IntoIterator<Item = AdapterNotice>, AdapterError>
995where
996{
997    let log_names = source_ids
998        .iter()
999        .map(|gid| catalog.resolve_item_id(gid))
1000        .flat_map(|item_id| catalog.introspection_dependencies(item_id))
1001        .map(|item_id| catalog.get_entry(&item_id).name().item.clone())
1002        .collect::<Vec<_>>();
1003
1004    if log_names.is_empty() {
1005        return Ok(None);
1006    }
1007
1008    // Reading from log sources on replicated clusters is only allowed if a
1009    // target replica is selected. Otherwise, we have no way of knowing which
1010    // replica we read the introspection data from.
1011    let num_replicas = cluster.replicas().count();
1012    if target_replica.is_none() {
1013        if num_replicas == 1 {
1014            *target_replica = cluster.replicas().map(|r| r.replica_id).next();
1015        } else {
1016            return Err(AdapterError::UntargetedLogRead { log_names });
1017        }
1018    }
1019
1020    // Ensure that logging is initialized for the target replica, lest
1021    // we try to read from a non-existing arrangement.
1022    let replica_id = target_replica.expect("set to `Some` above");
1023    let replica = &cluster.replica(replica_id).expect("Replica must exist");
1024    if !replica.config.compute.logging.enabled() {
1025        return Err(AdapterError::IntrospectionDisabled { log_names });
1026    }
1027
1028    Ok(vars
1029        .emit_introspection_query_notice()
1030        .then_some(AdapterNotice::PerReplicaLogRead { log_names }))
1031}
1032
1033/// Forward notices that we got from the optimizer.
1034pub(crate) fn emit_optimizer_notices(
1035    catalog: &Catalog,
1036    session: &Session,
1037    notices: &[RawOptimizerNotice],
1038) {
1039    // `for_session` below is expensive, so return early if there's nothing to do.
1040    if notices.is_empty() {
1041        return;
1042    }
1043    let humanizer = catalog.for_session(session);
1044    let system_vars = catalog.system_config();
1045    for notice in notices {
1046        let kind = OptimizerNoticeKind::from(notice);
1047        let notice_enabled = match kind {
1048            OptimizerNoticeKind::EqualsNull => system_vars.enable_notices_for_equals_null(),
1049            OptimizerNoticeKind::IndexAlreadyExists => {
1050                system_vars.enable_notices_for_index_already_exists()
1051            }
1052            OptimizerNoticeKind::IndexTooWideForLiteralConstraints => {
1053                system_vars.enable_notices_for_index_too_wide_for_literal_constraints()
1054            }
1055            OptimizerNoticeKind::IndexKeyEmpty => system_vars.enable_notices_for_index_empty_key(),
1056        };
1057        if notice_enabled {
1058            // We don't need to redact the notice parts because
1059            // `emit_optimizer_notices` is only called by the `sequence_~`
1060            // method for the statement that produces that notice.
1061            session.add_notice(AdapterNotice::OptimizerNotice {
1062                notice: notice.message(&humanizer, false).to_string(),
1063                hint: notice.hint(&humanizer, false).to_string(),
1064            });
1065        }
1066        session
1067            .metrics()
1068            .optimization_notices(&[kind.metric_label()])
1069            .inc_by(1);
1070    }
1071}
1072
1073/// Evaluates a COPY TO target URI expression and validates it.
1074///
1075/// This function is shared between the old peek sequencing (sequence_copy_to)
1076/// and the new frontend peek sequencing to avoid code duplication.
1077pub fn eval_copy_to_uri(
1078    to: HirScalarExpr,
1079    session: &Session,
1080    catalog_state: &CatalogState,
1081) -> Result<Uri, AdapterError> {
1082    let style = ExprPrepOneShot {
1083        logical_time: EvalTime::NotAvailable,
1084        session,
1085        catalog_state,
1086    };
1087    let mut to = to.lower_uncorrelated(catalog_state.system_config())?;
1088    style.prep_scalar_expr(&mut to)?;
1089    let temp_storage = RowArena::new();
1090    let evaled = to.eval(&[], &temp_storage)?;
1091    if evaled == Datum::Null {
1092        coord_bail!("COPY TO target value can not be null");
1093    }
1094    let to_url = match Uri::from_str(evaled.unwrap_str()) {
1095        Ok(url) => {
1096            if url.scheme_str() != Some("s3") && url.scheme_str() != Some("gs") {
1097                coord_bail!("only 's3://...' and 'gs://...' urls are supported as COPY TO target");
1098            }
1099            url
1100        }
1101        Err(e) => coord_bail!("could not parse COPY TO target url: {}", e),
1102    };
1103    Ok(to_url)
1104}
1105
1106/// Returns a future that will execute EXPLAIN FILTER PUSHDOWN, i.e., compute the filter pushdown
1107/// statistics for the given collections with the given MFPs.
1108///
1109/// (Shared helper fn between the old and new sequencing. This doesn't take the Coordinator as a
1110/// parameter, but instead just the specifically necessary things are passed in, so that the
1111/// frontend peek sequencing can also call it.)
1112pub(crate) async fn explain_pushdown_future_inner<
1113    I: IntoIterator<Item = (GlobalId, MapFilterProject)>,
1114>(
1115    session: &Session,
1116    catalog: &Catalog,
1117    storage_collections: &Arc<dyn StorageCollections + Send + Sync>,
1118    as_of: Antichain<Timestamp>,
1119    mz_now: ResultSpec<'static>,
1120    imports: I,
1121) -> impl Future<Output = Result<ExecuteResponse, AdapterError>> + use<I> {
1122    let mut explain_timeout = *session.vars().statement_timeout();
1123    // Timeout of 0 is equivalent to "off", meaning we will wait "forever."
1124    if explain_timeout == Duration::ZERO {
1125        explain_timeout = Duration::MAX;
1126    }
1127    let mut futures = FuturesOrdered::new();
1128    for (id, mfp) in imports {
1129        let catalog_entry = catalog.get_entry_by_global_id(&id);
1130        let full_name = catalog
1131            .for_session(session)
1132            .resolve_full_name(&catalog_entry.name);
1133        let name = format!("{}", full_name);
1134        let relation_desc = catalog_entry
1135            .relation_desc()
1136            .expect("source should have a proper desc")
1137            .into_owned();
1138        let stats_future = storage_collections
1139            .snapshot_parts_stats(id, as_of.clone())
1140            .await;
1141
1142        let mz_now = mz_now.clone();
1143        // These futures may block if the source is not yet readable at the as-of;
1144        // stash them in `futures` and only block on them in a separate task.
1145        // TODO(peek-seq): This complication won't be needed once this function will only be called
1146        // from the new peek sequencing, in which case it will be fine to block the current task.
1147        futures.push_back(async move {
1148            let snapshot_stats = match stats_future.await {
1149                Ok(stats) => stats,
1150                Err(e) => return Err(e),
1151            };
1152            let mut total_bytes = 0;
1153            let mut total_parts = 0;
1154            let mut selected_bytes = 0;
1155            let mut selected_parts = 0;
1156            for SnapshotPartStats {
1157                encoded_size_bytes: bytes,
1158                stats,
1159            } in &snapshot_stats.parts
1160            {
1161                let bytes = u64::cast_from(*bytes);
1162                total_bytes += bytes;
1163                total_parts += 1u64;
1164                let selected = match stats.as_ref().and_then(|x| x.try_decode().ok()) {
1165                    // Also the arm for stats that do not decode, which a
1166                    // newer writer's stats kind can produce. Both report the
1167                    // part as selected, matching what a read of it would do.
1168                    None => true,
1169                    Some(stats) => {
1170                        let stats = RelationPartStats::new(
1171                            name.as_str(),
1172                            &snapshot_stats.metrics.pushdown.part_stats,
1173                            &relation_desc,
1174                            &stats,
1175                        );
1176                        stats.may_match_mfp(mz_now.clone(), &mfp)
1177                    }
1178                };
1179
1180                if selected {
1181                    selected_bytes += bytes;
1182                    selected_parts += 1u64;
1183                }
1184            }
1185            Ok(Row::pack_slice(&[
1186                name.as_str().into(),
1187                total_bytes.into(),
1188                selected_bytes.into(),
1189                total_parts.into(),
1190                selected_parts.into(),
1191            ]))
1192        });
1193    }
1194
1195    let fut = async move {
1196        match tokio::time::timeout(
1197            explain_timeout,
1198            futures::TryStreamExt::try_collect::<Vec<_>>(futures),
1199        )
1200        .await
1201        {
1202            Ok(Ok(rows)) => Ok(ExecuteResponse::SendingRowsImmediate {
1203                rows: Box::new(rows.into_row_iter()),
1204            }),
1205            Ok(Err(err)) => Err(err.into()),
1206            Err(_) => Err(AdapterError::StatementTimeout),
1207        }
1208    };
1209    fut
1210}
1211
1212/// Generates EXPLAIN PLAN output.
1213/// (Shared helper fn between the old and new sequencing.)
1214pub(crate) async fn explain_plan_inner(
1215    session: &Session,
1216    catalog: &Catalog,
1217    df_meta: DataflowMetainfo,
1218    explain_ctx: ExplainPlanContext,
1219    optimizer: peek::Optimizer,
1220    insights_ctx: Option<Box<PlanInsightsContext>>,
1221) -> Result<Vec<Row>, AdapterError> {
1222    let ExplainPlanContext {
1223        config,
1224        format,
1225        stage,
1226        desc,
1227        optimizer_trace,
1228        ..
1229    } = explain_ctx;
1230
1231    let desc = desc.expect("RelationDesc for SelectPlan in EXPLAIN mode");
1232
1233    let session_catalog = catalog.for_session(session);
1234    let expr_humanizer = {
1235        let transient_items = btreemap! {
1236            optimizer.select_id() => TransientItem::new(
1237                Some(vec![GlobalId::Explain.to_string()]),
1238                Some(desc.iter_names().map(|c| c.to_string()).collect()),
1239            )
1240        };
1241        ExprHumanizerExt::new(transient_items, &session_catalog)
1242    };
1243
1244    let finishing = if optimizer.finishing().is_trivial(desc.arity()) {
1245        None
1246    } else {
1247        Some(optimizer.finishing().clone())
1248    };
1249
1250    let target_cluster = catalog.get_cluster(optimizer.cluster_id());
1251    let features = optimizer.config().features.clone();
1252
1253    let rows = optimizer_trace
1254        .into_rows(
1255            format,
1256            &config,
1257            &features,
1258            &expr_humanizer,
1259            finishing,
1260            Some(target_cluster),
1261            df_meta,
1262            stage,
1263            plan::ExplaineeStatementKind::Select,
1264            insights_ctx,
1265        )
1266        .await?;
1267
1268    Ok(rows)
1269}
1270
1271/// Creates a statistics oracle for query optimization.
1272///
1273/// This is a free-standing function that can be called from both the old peek sequencing
1274/// and the new frontend peek sequencing.
1275pub(crate) async fn statistics_oracle(
1276    session: &Session,
1277    source_ids: &BTreeSet<GlobalId>,
1278    query_as_of: &Antichain<Timestamp>,
1279    is_oneshot: bool,
1280    system_config: &vars::SystemVars,
1281    storage_collections: &dyn StorageCollections,
1282) -> Result<Box<dyn StatisticsOracle>, AdapterError> {
1283    if !session.vars().enable_session_cardinality_estimates() {
1284        let stats: Box<dyn StatisticsOracle> = Box::new(EmptyStatisticsOracle);
1285        return Ok(stats);
1286    }
1287
1288    let timeout = if is_oneshot {
1289        // TODO(mgree): ideally, we would shorten the timeout even more if we think the query could take the fast path
1290        system_config.optimizer_oneshot_stats_timeout()
1291    } else {
1292        system_config.optimizer_stats_timeout()
1293    };
1294
1295    let cached_stats = mz_ore::future::timeout(
1296        timeout,
1297        CachedStatisticsOracle::new(source_ids, query_as_of, storage_collections),
1298    )
1299    .await;
1300
1301    match cached_stats {
1302        Ok(stats) => Ok(Box::new(stats)),
1303        Err(mz_ore::future::TimeoutError::DeadlineElapsed) => {
1304            warn!(
1305                is_oneshot = is_oneshot,
1306                "optimizer statistics collection timed out after {}ms",
1307                timeout.as_millis()
1308            );
1309
1310            Ok(Box::new(EmptyStatisticsOracle))
1311        }
1312        Err(mz_ore::future::TimeoutError::Inner(e)) => Err(AdapterError::Storage(e)),
1313    }
1314}
1315
1316#[derive(Debug)]
1317struct CachedStatisticsOracle {
1318    cache: BTreeMap<GlobalId, usize>,
1319}
1320
1321impl CachedStatisticsOracle {
1322    pub async fn new(
1323        ids: &BTreeSet<GlobalId>,
1324        as_of: &Antichain<Timestamp>,
1325        storage_collections: &dyn StorageCollections,
1326    ) -> Result<Self, StorageError> {
1327        let mut cache = BTreeMap::new();
1328
1329        for id in ids {
1330            let stats = storage_collections.snapshot_stats(*id, as_of.clone()).await;
1331
1332            match stats {
1333                Ok(stats) => {
1334                    cache.insert(*id, stats.num_updates);
1335                }
1336                Err(StorageError::IdentifierMissing(id)) => {
1337                    ::tracing::debug!("no statistics for {id}")
1338                }
1339                Err(e) => return Err(e),
1340            }
1341        }
1342
1343        Ok(Self { cache })
1344    }
1345}
1346
1347impl StatisticsOracle for CachedStatisticsOracle {
1348    fn cardinality_estimate(&self, id: GlobalId) -> Option<usize> {
1349        self.cache.get(&id).map(|estimate| *estimate)
1350    }
1351
1352    fn as_map(&self) -> BTreeMap<GlobalId, usize> {
1353        self.cache.clone()
1354    }
1355}