1use std::collections::BTreeMap;
11use std::collections::BTreeSet;
12use std::future::Future;
13use std::str::FromStr;
14use std::sync::Arc;
15use std::time::Duration;
16
17use futures::StreamExt;
18use http::Uri;
19use itertools::Itertools;
20use maplit::btreemap;
21use mz_catalog::memory::objects::Cluster;
22use mz_compute_types::ComputeInstanceId;
23use mz_controller_types::{ClusterId, ReplicaId};
24use mz_expr::{CollectionPlan, Eval, ResultSpec, RowSetFinishing};
25use mz_ore::cast::{CastFrom, CastLossy};
26use mz_ore::collections::CollectionExt;
27use mz_ore::now::EpochMillis;
28use mz_ore::task::JoinHandle;
29use mz_ore::{soft_assert_eq_or_log, soft_assert_or_log, soft_panic_or_log};
30use mz_repr::explain::{ExplainFormat, ExprHumanizerExt, TransientItem};
31use mz_repr::optimize::{OptimizerFeatures, OverrideFrom};
32use mz_repr::{Datum, GlobalId, IntoRowIterator, Row, RowArena, RowIterator, Timestamp};
33use mz_sql::ast::Raw;
34use mz_sql::catalog::CatalogCluster;
35use mz_sql::plan::Params;
36use mz_sql::plan::{
37 self, Explainee, ExplaineeStatement, HirScalarExpr, Plan, QueryWhen, SelectPlan,
38 SideEffectingFunc, SubscribeFrom, SubscribePlan,
39};
40use mz_sql::rbac;
41use mz_sql::session::metadata::SessionMetadata;
42use mz_sql::session::user::MZ_SYSTEM_ROLE_ID;
43use mz_sql::session::vars::{
44 self, CLUSTER, CLUSTER_REPLICA, IsolationLevel, SessionVars, TRANSACTION_ISOLATION, Var,
45 VarInput,
46};
47use mz_sql_parser::ast::{CopyDirection, ExplainStage, ShowStatement, Statement, StatementKind};
48use mz_storage_client::storage_collections::StorageCollections;
49use mz_storage_types::controller::StorageError;
50use mz_transform::dataflow::DataflowMetainfo;
51use mz_transform::{EmptyStatisticsOracle, StatisticsOracle};
52use opentelemetry::trace::TraceContextExt;
53use timely::progress::Antichain;
54use tracing::{Span, debug, warn};
55use tracing_opentelemetry::OpenTelemetrySpanExt;
56
57use crate::catalog::{Catalog, CatalogState};
58use crate::command::{CatalogSnapshot, Command};
59use crate::coord;
60use crate::coord::peek::{FastPathPlan, PeekPlan, PeekResponseUnary};
61use crate::coord::timeline::timedomain_for;
62use crate::coord::timestamp_selection::TimestampDetermination;
63use crate::coord::{Coordinator, CopyToContext, ExplainContext, ExplainPlanContext, TargetCluster};
64use crate::explain::insights::PlanInsightsContext;
65use crate::explain::optimizer_trace::OptimizerTrace;
66use crate::optimize::Optimize;
67use crate::optimize::dataflows::{
68 ComputeInstanceSnapshot, DataflowBuilder, EvalTime, ExprPrep, ExprPrepOneShot,
69};
70use crate::optimize::peek;
71use crate::peek_client::{ExecutionLogging, TakeOver};
72use crate::session::{Session, TransactionOps, TransactionStatus};
73use crate::statement_logging::StatementLifecycleEvent;
74use crate::statement_logging::WatchSetCreation;
75use crate::{
76 AdapterError, AdapterNotice, CollectionIdBundle, ExecuteResponse, PeekClient, ReadHolds,
77 TimelineContext, TimestampContext, TimestampProvider, optimize,
78};
79
80impl PeekClient {
81 pub(crate) async fn try_peek(
93 &mut self,
94 portal_name: &str,
95 session: &mut Session,
96 logging: &mut ExecutionLogging,
97 connection_closed: impl Future<Output = ()> + Send,
98 ) -> Result<Option<ExecuteResponse>, AdapterError> {
99 if session.vars().emit_trace_id_notice() {
102 let span_context = tracing::Span::current()
103 .context()
104 .span()
105 .span_context()
106 .clone();
107 if span_context.is_valid() {
108 session.add_notice(AdapterNotice::QueryTrace {
109 trace_id: span_context.trace_id(),
110 });
111 }
112 }
113
114 let catalog = self.catalog_snapshot("try_peek").await;
115
116 let (stmt, params, logging_info, lifecycle_timestamps) = {
121 Coordinator::verify_portal(&*catalog, session, portal_name)?;
122 let portal = session
123 .get_portal_unverified(portal_name)
124 .expect("called verify_portal above");
127 let params = portal.parameters.clone();
128 let stmt = portal.stmt.clone();
129 let logging_info = Arc::clone(&portal.logging);
130 let lifecycle_timestamps = portal.lifecycle_timestamps.clone();
131 (stmt, params, logging_info, lifecycle_timestamps)
132 };
133
134 if let Some(ref stmt) = stmt {
137 if StatementKind::from(&**stmt).is_explain() {
138 session.transaction_mut().record_explain()?;
139 }
140 match &**stmt {
141 Statement::Select(_)
142 | Statement::ExplainAnalyzeObject(_)
143 | Statement::ExplainAnalyzeCluster(_)
144 | Statement::Show(ShowStatement::ShowObjects(_))
145 | Statement::Show(ShowStatement::ShowColumns(_)) => {
146 }
151 Statement::ExplainPlan(explain_stmt) => {
152 match &explain_stmt.explainee {
157 mz_sql_parser::ast::Explainee::Select(..)
158 | mz_sql_parser::ast::Explainee::Subscribe(..) => {}
159 _ => {
160 debug!(
161 "Bailing out from try_peek, because EXPLAIN is not for a SELECT or SUBSCRIBE"
162 );
163 return Ok(None);
164 }
165 }
166 }
167 Statement::ExplainPushdown(explain_stmt) => {
168 match &explain_stmt.explainee {
170 mz_sql_parser::ast::Explainee::Select(_, false) => {}
171 _ => {
172 debug!(
173 "Bailing out from try_peek, because EXPLAIN FILTER PUSHDOWN is not for a SELECT query or is for EXPLAIN BROKEN"
174 );
175 return Ok(None);
176 }
177 }
178 }
179 Statement::Copy(copy_stmt) => {
180 match ©_stmt.direction {
181 CopyDirection::To => {
182 }
184 CopyDirection::From => {
185 debug!("Bailing out from try_peek, because COPY FROM is not supported");
186 return Ok(None);
187 }
188 }
189 }
190
191 Statement::Subscribe(_) | Statement::ExplainTimestamp(_) => {}
192 _ => {
193 debug!("Bailing out from try_peek, because statement type is not supported");
194 return Ok(None);
195 }
196 }
197 }
198
199 logging.take_over(
203 self,
204 session,
205 stmt.as_deref(),
206 ¶ms,
207 &logging_info,
208 &catalog,
209 lifecycle_timestamps,
210 TakeOver::StatementToRun,
211 );
212
213 self.try_peek_inner(session, catalog, stmt, params, logging, connection_closed)
214 .await
215 }
216
217 pub(crate) async fn background_peek(
232 &mut self,
233 sql: &str,
234 cluster_id: ClusterId,
235 replica_id: ReplicaId,
236 ) -> Result<Vec<Row>, AdapterError> {
237 let CatalogSnapshot { catalog } = self
240 .call_coordinator(|tx| Command::CatalogSnapshot { tx })
241 .await?;
242 let (cluster_name, replica_name) = {
243 let cluster = catalog
244 .try_get_cluster(cluster_id)
245 .ok_or_else(|| AdapterError::Internal(format!("unknown cluster {cluster_id}")))?;
246 let replica = cluster
247 .replica(replica_id)
248 .ok_or_else(|| AdapterError::Internal(format!("unknown replica {replica_id}")))?;
249 (cluster.name.clone(), replica.name.clone())
250 };
251 let stmt = mz_sql::parse::parse(sql)?.into_element().ast;
252
253 let mut session = Session::dummy();
254 session.initialize_role_metadata(MZ_SYSTEM_ROLE_ID);
255 let system_vars = catalog.system_config();
256 for (var, value) in [
262 (&CLUSTER, cluster_name.as_str()),
263 (&CLUSTER_REPLICA, replica_name.as_str()),
264 (&TRANSACTION_ISOLATION, "serializable"),
265 ] {
266 session
267 .vars_mut()
268 .set(system_vars, var.name(), VarInput::Flat(value), false)?;
269 }
270 session.start_transaction_single_stmt(mz_ore::now::to_datetime((catalog.config().now)()));
271
272 let mut logging = ExecutionLogging::adopt(None, self);
273 let response = self
274 .try_peek_inner(
275 &mut session,
276 catalog,
277 Some(Arc::new(stmt)),
278 Params::empty(),
279 &mut logging,
280 futures::future::pending(),
282 )
283 .await?;
284
285 let mut rows = Vec::new();
286 let mut collect = |mut iter: Box<dyn RowIterator + Send + Sync>| {
287 while let Some(row) = iter.next() {
288 rows.push(row.to_owned());
289 }
290 };
291 match response {
292 Some(ExecuteResponse::SendingRowsImmediate { rows }) => collect(rows),
293 Some(ExecuteResponse::SendingRowsStreaming { mut rows, .. }) => {
294 while let Some(response) = rows.next().await {
295 match response {
296 PeekResponseUnary::Rows(iter) => collect(iter),
297 PeekResponseUnary::Error(error) => return Err(error),
298 PeekResponseUnary::Canceled => return Err(AdapterError::Canceled),
299 PeekResponseUnary::DependencyDropped(dependency) => {
300 return Err(dependency.to_concurrent_dependency_drop());
301 }
302 }
303 }
304 }
305 response => {
306 return Err(AdapterError::Internal(format!(
307 "background peek returned an unexpected response: {response:?}"
308 )));
309 }
310 }
311 Ok(rows)
312 }
313
314 #[mz_ore::instrument(level = "debug")]
323 async fn try_peek_inner(
324 &mut self,
325 session: &mut Session,
326 catalog: Arc<Catalog>,
327 stmt: Option<Arc<Statement<Raw>>>,
328 params: Params,
329 logging: &mut ExecutionLogging,
330 connection_closed: impl Future<Output = ()> + Send,
331 ) -> Result<Option<ExecuteResponse>, AdapterError> {
332 let stmt = match stmt {
333 Some(stmt) => stmt,
334 None => {
335 debug!("try_peek_inner succeeded on an empty query");
336 return Ok(Some(ExecuteResponse::EmptyQuery));
337 }
338 };
339
340 let conn_catalog = catalog.for_session(session);
343 let (stmt, resolved_ids) = mz_sql::names::resolve(&conn_catalog, (*stmt).clone())?;
346
347 let pcx = session.pcx();
348 let (plan, sql_impl_ids) =
349 mz_sql::plan::plan(Some(pcx), &conn_catalog, stmt, ¶ms, &resolved_ids)?;
350
351 enum QueryPlan<'a> {
353 Select(&'a SelectPlan),
354 CopyTo(&'a SelectPlan, CopyToContext),
355 Subscribe(&'a SubscribePlan),
356 ExplainTimestamp(&'a plan::ExplainTimestampPlan),
357 }
358
359 impl QueryPlan<'_> {
360 fn logs_execution_timestamp(&self, explain_ctx: &ExplainContext) -> bool {
364 let executes = match explain_ctx {
365 ExplainContext::None | ExplainContext::PlanInsightsNotice(_) => true,
366 ExplainContext::Plan(_)
367 | ExplainContext::Pushdown
368 | ExplainContext::Timestamp => false,
369 };
370 executes
371 && match self {
372 QueryPlan::Select(_) | QueryPlan::Subscribe(_) => true,
373 QueryPlan::CopyTo(..) | QueryPlan::ExplainTimestamp(_) => false,
374 }
375 }
376 }
377
378 enum OptimizerInput<'a> {
380 Select(&'a SelectPlan),
381 CopyTo(&'a SelectPlan, CopyToContext),
382 Subscribe(&'a SubscribePlan),
383 }
384
385 let (query_plan, explain_ctx) = match &plan {
386 Plan::Select(select_plan) => {
387 let explain_ctx = if session.vars().emit_plan_insights_notice() {
388 let optimizer_trace = OptimizerTrace::new(ExplainStage::PlanInsights.paths());
389 ExplainContext::PlanInsightsNotice(optimizer_trace)
390 } else {
391 ExplainContext::None
392 };
393 (QueryPlan::Select(select_plan), explain_ctx)
394 }
395 Plan::ShowColumns(show_columns_plan) => {
396 (
398 QueryPlan::Select(&show_columns_plan.select_plan),
399 ExplainContext::None,
400 )
401 }
402 Plan::ExplainPlan(plan::ExplainPlanPlan {
403 stage,
404 format,
405 config,
406 explainee: Explainee::Statement(ExplaineeStatement::Select { broken, plan, desc }),
407 }) => {
408 let optimizer_trace = OptimizerTrace::new(stage.paths());
410 let explain_ctx = ExplainContext::Plan(ExplainPlanContext {
411 broken: *broken,
412 config: config.clone(),
413 format: *format,
414 stage: *stage,
415 replan: None,
416 desc: Some(desc.clone()),
417 optimizer_trace,
418 });
419 (QueryPlan::Select(plan), explain_ctx)
420 }
421 Plan::CopyTo(plan::CopyToPlan {
423 select_plan,
424 desc,
425 to,
426 connection,
427 connection_id,
428 format,
429 max_file_size,
430 }) => {
431 let uri = eval_copy_to_uri(to.clone(), session, catalog.state())?;
432
433 let copy_to_ctx = CopyToContext {
435 desc: desc.clone(),
436 uri,
437 connection: connection.clone(),
438 connection_id: *connection_id,
439 format: format.clone(),
440 max_file_size: *max_file_size,
441 output_batch_count: None,
442 };
443
444 (
445 QueryPlan::CopyTo(select_plan, copy_to_ctx),
446 ExplainContext::None,
447 )
448 }
449 Plan::ExplainPushdown(plan::ExplainPushdownPlan { explainee }) => {
450 match explainee {
452 plan::Explainee::Statement(plan::ExplaineeStatement::Select {
453 broken: false,
454 plan,
455 desc: _,
456 }) => {
457 let explain_ctx = ExplainContext::Pushdown;
458 (QueryPlan::Select(plan), explain_ctx)
459 }
460 _ => {
461 soft_panic_or_log!(
466 "unexpected EXPLAIN FILTER PUSHDOWN plan kind in frontend peek sequencing: {:?}",
467 explainee
468 );
469 return Err(AdapterError::Internal(
470 "unexpected EXPLAIN FILTER PUSHDOWN plan kind in frontend peek sequencing"
471 .into(),
472 ));
473 }
474 }
475 }
476 Plan::SideEffectingFunc(sef_plan) => {
477 let target_conn = match sef_plan {
486 SideEffectingFunc::PgCancelBackend {
487 connection_id: Some(connection_id),
488 } => {
489 self.call_coordinator(|tx| Command::LookupConnection {
490 connection_id: *connection_id,
491 tx,
492 })
493 .await?
494 }
495 SideEffectingFunc::PgCancelBackend {
496 connection_id: None,
497 } => None,
498 };
499 let target_conn_role = target_conn.as_ref().map(|(_, role)| *role);
500
501 rbac::check_plan(
502 &conn_catalog,
503 target_conn_role,
504 session,
505 &plan,
506 None,
507 &resolved_ids,
508 &sql_impl_ids,
509 )?;
510
511 let response = self
513 .call_coordinator(|tx| Command::ExecuteSideEffectingFunc {
514 plan: sef_plan.clone(),
515 conn_id: session.conn_id().clone(),
516 tx,
517 })
518 .await??;
519
520 drop(target_conn);
526
527 return Ok(Some(response));
528 }
529 Plan::Subscribe(subscribe) => (QueryPlan::Subscribe(subscribe), ExplainContext::None),
530 Plan::ExplainTimestamp(explain_timestamp_plan) => {
531 if explain_timestamp_plan.format == ExplainFormat::Dot {
532 return Err(AdapterError::Unsupported("EXPLAIN TIMESTAMP AS DOT"));
533 }
534 (
535 QueryPlan::ExplainTimestamp(explain_timestamp_plan),
536 ExplainContext::Timestamp,
537 )
538 }
539 Plan::ExplainPlan(plan::ExplainPlanPlan {
540 stage,
541 format,
542 config,
543 explainee: Explainee::Statement(ExplaineeStatement::Subscribe { broken, plan }),
544 }) => {
545 let desc = match &plan.from {
546 SubscribeFrom::Id(_) => None,
547 SubscribeFrom::Query { desc, .. } => Some(desc.clone()),
548 };
549 let explain_ctx = ExplainContext::Plan(ExplainPlanContext {
550 broken: *broken,
551 config: config.clone(),
552 format: *format,
553 stage: *stage,
554 replan: None,
555 desc,
556 optimizer_trace: OptimizerTrace::new(stage.paths()),
557 });
558 (QueryPlan::Subscribe(plan), explain_ctx)
559 }
560 Plan::CreateConnection(_)
561 | Plan::CreateDatabase(_)
562 | Plan::CreateSchema(_)
563 | Plan::CreateRole(_)
564 | Plan::CreateCluster(_)
565 | Plan::CreateClusterReplica(_)
566 | Plan::CreateSource(_)
567 | Plan::CreateSources(_)
568 | Plan::CreateSecret(_)
569 | Plan::CreateSink(_)
570 | Plan::CreateTable(_)
571 | Plan::CreateView(_)
572 | Plan::CreateMaterializedView(_)
573 | Plan::CreateNetworkPolicy(_)
574 | Plan::CreateIndex(_)
575 | Plan::CreateMetricSink(_)
576 | Plan::CreateType(_)
577 | Plan::Comment(_)
578 | Plan::DiscardTemp
579 | Plan::DiscardAll
580 | Plan::DropObjects(_)
581 | Plan::DropOwned(_)
582 | Plan::EmptyQuery
583 | Plan::ShowAllVariables
584 | Plan::ShowCreate(_)
585 | Plan::ShowVariable(_)
586 | Plan::InspectShard(_)
587 | Plan::SetVariable(_)
588 | Plan::ResetVariable(_)
589 | Plan::SetTransaction(_)
590 | Plan::StartTransaction(_)
591 | Plan::CommitTransaction(_)
592 | Plan::AbortTransaction(_)
593 | Plan::CopyFrom(_)
594 | Plan::ExplainPlan(_)
595 | Plan::ExplainSinkSchema(_)
596 | Plan::Insert(_)
597 | Plan::AlterCluster(_)
598 | Plan::AlterClusterSwap(_)
599 | Plan::AlterNoop(_)
600 | Plan::AlterSetCluster(_)
601 | Plan::AlterConnection(_)
602 | Plan::AlterSource(_)
603 | Plan::AlterClusterRename(_)
604 | Plan::AlterClusterReplicaRename(_)
605 | Plan::AlterItemRename(_)
606 | Plan::AlterSchemaRename(_)
607 | Plan::AlterSchemaSwap(_)
608 | Plan::AlterSecret(_)
609 | Plan::AlterSink(_)
610 | Plan::AlterSystemSet(_)
611 | Plan::AlterSystemReset(_)
612 | Plan::AlterSystemResetAll(_)
613 | Plan::AlterRole(_)
614 | Plan::AlterOwner(_)
615 | Plan::AlterTableAddColumn(_)
616 | Plan::AlterMaterializedViewApplyReplacement(_)
617 | Plan::AlterNetworkPolicy(_)
618 | Plan::Declare(_)
619 | Plan::Fetch(_)
620 | Plan::Close(_)
621 | Plan::ReadThenWrite(_)
622 | Plan::Prepare(_)
623 | Plan::Execute(_)
624 | Plan::Deallocate(_)
625 | Plan::Raise(_)
626 | Plan::GrantRole(_)
627 | Plan::RevokeRole(_)
628 | Plan::GrantPrivileges(_)
629 | Plan::RevokePrivileges(_)
630 | Plan::AlterDefaultPrivileges(_)
631 | Plan::ReassignOwned(_)
632 | Plan::ValidateConnection(_)
633 | Plan::AlterRetainHistory(_)
634 | Plan::AlterSourceTimestampInterval(_) => {
635 soft_panic_or_log!(
640 "Unexpected plan kind in frontend peek sequencing: {:?}",
641 plan
642 );
643 return Err(AdapterError::Internal(
644 "unexpected plan kind in frontend peek sequencing".into(),
645 ));
646 }
647 };
648
649 let when = match &query_plan {
650 QueryPlan::Select(s) => &s.when,
651 QueryPlan::CopyTo(s, _) => &s.when,
652 QueryPlan::Subscribe(s) => &s.when,
653 QueryPlan::ExplainTimestamp(p) => &p.when,
654 };
655
656 assert!(plan.allowed_in_read_only());
660
661 let (cluster, target_cluster_id, target_cluster_name) = {
662 let target_cluster = match session.transaction().cluster() {
663 Some(cluster_id) => TargetCluster::Transaction(cluster_id),
665 None => coord::catalog_serving::auto_run_on_catalog_server(
667 &conn_catalog,
668 session,
669 &plan,
670 ),
671 };
672 let cluster = catalog.resolve_target_cluster(target_cluster, session)?;
673 (cluster, cluster.id, &cluster.name)
674 };
675
676 if let Some(logging_id) = logging.id() {
678 self.log_set_cluster(logging_id, target_cluster_id, target_cluster_name.clone());
679 }
680
681 coord::catalog_serving::check_cluster_restrictions(
682 target_cluster_name.as_str(),
683 &conn_catalog,
684 &plan,
685 )?;
686
687 rbac::check_plan(
688 &conn_catalog,
689 None,
692 session,
693 &plan,
694 Some(target_cluster_id),
695 &resolved_ids,
696 &sql_impl_ids,
697 )?;
698
699 if let Some(wait_future) =
700 coord::appends::waiting_on_startup_appends(&*catalog, session, &plan)
701 {
702 wait_future.await;
703 }
704
705 let (depends_on, contains_temporal) = match &query_plan {
706 QueryPlan::Select(s) | QueryPlan::CopyTo(s, _) => {
707 (s.source.depends_on(), s.source.contains_temporal())
708 }
709 QueryPlan::Subscribe(s) => (s.from.depends_on(), s.from.contains_temporal()),
710 QueryPlan::ExplainTimestamp(p) => {
711 let mut optimizer = optimize::view::Optimizer::new(
714 optimize::OptimizerConfig::from(catalog.system_config()),
715 None,
716 );
717 let raw_plan = p.raw_plan.clone();
718 let optimized = mz_ore::task::spawn_blocking(
720 || "optimize explain timestamp",
721 move || optimizer.catch_unwind_optimize(raw_plan),
722 )
723 .await?;
724 (optimized.depends_on(), optimized.contains_temporal())
725 }
726 };
727
728 let max_query_result_size = Some(session.vars().max_query_result_size());
729
730 let compute_instance_snapshot =
731 ComputeInstanceSnapshot::new_without_collections(cluster.id());
732
733 let optimizer_config = optimize::OptimizerConfig::from(catalog.system_config())
734 .override_from(&catalog.get_cluster(cluster.id()).config.features())
735 .override_from(
737 &catalog
738 .state()
739 .cluster_scoped_optimizer_overrides(cluster.id()),
740 )
741 .override_from(&explain_ctx);
742
743 if cluster.replicas().next().is_none() && explain_ctx.needs_cluster() {
744 return Err(AdapterError::NoClusterReplicasAvailable {
745 name: cluster.name.clone(),
746 is_managed: cluster.is_managed(),
747 });
748 }
749
750 let (_, view_id) = self.transient_id_gen.allocate_id();
751 let (_, index_id) = self.transient_id_gen.allocate_id();
752
753 let target_replica_name = session.vars().cluster_replica();
754 let mut target_replica = target_replica_name
755 .map(|name| {
756 cluster
757 .replica_id(name)
758 .ok_or(AdapterError::UnknownClusterReplica {
759 cluster_name: cluster.name.clone(),
760 replica_name: name.to_string(),
761 })
762 })
763 .transpose()?;
764
765 let source_ids = depends_on;
766 let mut timeline_context = catalog.validate_timeline_context(source_ids.iter().copied())?;
770 if matches!(timeline_context, TimelineContext::TimestampIndependent) && contains_temporal {
771 timeline_context = TimelineContext::TimestampDependent;
775 }
776
777 if explain_ctx.checks_log_reads() {
778 let notices = check_log_reads(
779 &catalog,
780 cluster,
781 &source_ids,
782 &mut target_replica,
783 session.vars(),
784 )?;
785 session.add_notices(notices);
786 }
787
788 let isolation_level = session.vars().transaction_isolation().clone();
789 let timeline = Coordinator::get_timeline(&timeline_context);
790 let needs_linearized_read_ts =
791 Coordinator::needs_linearized_read_ts(&isolation_level, when);
792
793 let oracle_read_ts = match timeline {
794 Some(timeline) if needs_linearized_read_ts => {
795 let oracle = self.ensure_oracle(timeline).await?;
796 let oracle_read_ts = oracle.read_ts().await;
797 #[cfg(debug_assertions)]
798 fail::fail_point!("frontend_peek_after_oracle_read");
799 Some(oracle_read_ts)
800 }
801 Some(_) | None => None,
802 };
803
804 let vars = session.vars();
805 let real_time_recency_ts: Option<Timestamp> = if vars.real_time_recency()
806 && vars.transaction_isolation() == &IsolationLevel::StrictSerializable
807 && !session.contains_read_timestamp()
808 {
809 self.call_coordinator(|tx| Command::DetermineRealTimeRecentTimestamp {
811 source_ids: source_ids.clone(),
812 real_time_recency_timeout: *vars.real_time_recency_timeout(),
813 tx,
814 })
815 .await??
816 } else {
817 None
818 };
819
820 let dataflow_builder =
821 DataflowBuilder::new(catalog.state(), compute_instance_snapshot.clone());
822 let input_id_bundle = dataflow_builder.sufficient_collections(source_ids.clone());
823
824 let in_immediate_multi_stmt_txn = session.transaction().in_immediate_multi_stmt_txn(when)
834 && !matches!(query_plan, QueryPlan::Subscribe { .. });
835
836 let (determination, read_holds) = match session.get_transaction_timestamp_determination() {
838 Some(
841 determination @ TimestampDetermination {
842 timestamp_context: TimestampContext::TimelineTimestamp { .. },
843 ..
844 },
845 ) if in_immediate_multi_stmt_txn => {
846 let txn_read_holds_opt = self
854 .call_coordinator(|tx| Command::GetTransactionReadHoldsBundle {
855 conn_id: session.conn_id().clone(),
856 tx,
857 })
858 .await?;
859
860 if let Some(txn_read_holds) = txn_read_holds_opt {
861 let allowed_id_bundle = txn_read_holds.id_bundle();
862 let outside = input_id_bundle.difference(&allowed_id_bundle);
863
864 if determination.timestamp_context.contains_timestamp() && !outside.is_empty() {
866 let valid_names =
867 allowed_id_bundle.resolve_names(&*catalog, session.conn_id());
868 let invalid_names = outside.resolve_names(&*catalog, session.conn_id());
869 return Err(AdapterError::RelationOutsideTimeDomain {
870 relations: invalid_names,
871 names: valid_names,
872 });
873 }
874
875 let read_holds = txn_read_holds.subset(&input_id_bundle);
877
878 (determination, read_holds)
879 } else {
880 return Err(AdapterError::Internal(
885 "Missing transaction read holds for multi-statement transaction"
886 .to_string(),
887 ));
888 }
889 }
890 _ => {
891 let timedomain_bundle;
898 let determine_bundle = if in_immediate_multi_stmt_txn {
899 timedomain_bundle = timedomain_for(
903 &*catalog,
904 &dataflow_builder,
905 &source_ids,
906 &timeline_context,
907 session.conn_id(),
908 target_cluster_id,
909 )?;
910 &timedomain_bundle
911 } else {
912 &input_id_bundle
914 };
915 let (determination, read_holds) = self
916 .determine_timestamp(
917 session,
918 determine_bundle,
919 when,
920 target_cluster_id,
921 &timeline_context,
922 oracle_read_ts,
923 real_time_recency_ts,
924 )
925 .await?;
926
927 if in_immediate_multi_stmt_txn
935 && determination.timestamp_context.contains_timestamp()
936 {
937 session.require_coordinator_completion();
938 self.call_coordinator(|tx| Command::StoreTransactionReadHolds {
939 conn_id: session.conn_id().clone(),
940 read_holds: read_holds.clone(),
941 tx,
942 })
943 .await?;
944 }
945
946 (determination, read_holds)
947 }
948 };
949
950 {
951 for id in input_id_bundle.iter() {
953 let s = read_holds.storage_holds.contains_key(&id);
954 let c = read_holds
955 .compute_ids()
956 .map(|(_instance, coll)| coll)
957 .contains(&id);
958 soft_assert_or_log!(
959 s || c,
960 "missing read hold for collection {} in `input_id_bundle`; (in_immediate_multi_stmt_txn: {})",
961 id,
962 in_immediate_multi_stmt_txn,
963 );
964 }
965
966 for id in input_id_bundle.storage_ids.iter() {
969 soft_assert_or_log!(
970 read_holds.storage_holds.contains_key(id),
971 "missing storage read hold for collection {} in `input_id_bundle`; (in_immediate_multi_stmt_txn: {})",
972 id,
973 in_immediate_multi_stmt_txn,
974 );
975 }
976 for id in input_id_bundle
977 .compute_ids
978 .iter()
979 .flat_map(|(_instance, colls)| colls)
980 {
981 soft_assert_or_log!(
982 read_holds
983 .compute_ids()
984 .map(|(_instance, coll)| coll)
985 .contains(id),
986 "missing compute read hold for collection {} in `input_id_bundle`; (in_immediate_multi_stmt_txn: {})",
987 id,
988 in_immediate_multi_stmt_txn,
989 );
990 }
991 }
992
993 if let (Some(logging_id), Some(timestamp), true) = (
994 logging.id(),
995 determination.timestamp_context.timestamp(),
996 query_plan.logs_execution_timestamp(&explain_ctx),
997 ) {
998 self.log_set_timestamp(logging_id, *timestamp);
999 }
1000
1001 let requires_linearization = (&explain_ctx).into();
1012 let mut transaction_determination = determination.clone();
1013 match &query_plan {
1014 QueryPlan::Subscribe { .. } => {
1015 if when.is_transactional() && explain_ctx.needs_cluster() {
1016 session.add_transaction_ops(TransactionOps::Subscribe)?;
1017 }
1018 }
1019 QueryPlan::Select(..) | QueryPlan::CopyTo(..) | QueryPlan::ExplainTimestamp(..) => {
1020 if when.is_transactional() {
1021 session.add_transaction_ops(TransactionOps::Peeks {
1022 determination: transaction_determination,
1023 cluster_id: target_cluster_id,
1024 requires_linearization,
1025 })?;
1026 } else if matches!(session.transaction(), &TransactionStatus::InTransaction(_)) {
1027 transaction_determination.timestamp_context = TimestampContext::NoTimestamp;
1029 session.add_transaction_ops(TransactionOps::Peeks {
1030 determination: transaction_determination,
1031 cluster_id: target_cluster_id,
1032 requires_linearization,
1033 })?;
1034 }
1035 }
1036 }
1037
1038 let query_plan = match query_plan {
1040 QueryPlan::Select(select_plan) => OptimizerInput::Select(select_plan),
1041 QueryPlan::CopyTo(select_plan, copy_to_ctx) => {
1042 OptimizerInput::CopyTo(select_plan, copy_to_ctx)
1043 }
1044 QueryPlan::Subscribe(plan) => OptimizerInput::Subscribe(plan),
1045 QueryPlan::ExplainTimestamp(explain_timestamp_plan) => {
1046 fail::fail_point!("explain_timestamp_before_dispatch");
1050 let explanation = self
1051 .call_coordinator(|tx| Command::ExplainTimestamp {
1052 conn_id: session.conn_id().clone(),
1053 session_wall_time: session.pcx().wall_time,
1054 cluster_id: target_cluster_id,
1055 id_bundle: input_id_bundle.clone(),
1056 determination,
1057 tx,
1058 })
1059 .await??;
1060 let s = match explain_timestamp_plan.format {
1061 ExplainFormat::Json => serde_json::to_string_pretty(&explanation)
1062 .expect("failed to serialize explanation"),
1063 ExplainFormat::Text | ExplainFormat::Dot => explanation.to_string(),
1065 };
1066 let rows = vec![Row::pack_slice(&[Datum::from(s.as_str())])];
1067 return Ok(Some(Coordinator::send_immediate_rows(rows)));
1068 }
1069 };
1070
1071 let stats = statistics_oracle(
1072 session,
1073 &source_ids,
1074 &determination.timestamp_context.antichain(),
1075 true,
1076 catalog.system_config(),
1077 &*self.storage_collections,
1078 )
1079 .await
1080 .unwrap_or_else(|_| Box::new(EmptyStatisticsOracle));
1081
1082 let timestamp_context = determination.timestamp_context.clone();
1085 let session_meta = session.meta();
1086 let now = catalog.config().now.clone();
1087 let target_cluster_name = target_cluster_name.clone();
1088 let needs_plan_insights = explain_ctx.needs_plan_insights();
1089 let determination_for_pushdown = if matches!(explain_ctx, ExplainContext::Pushdown) {
1090 Some(determination.clone())
1093 } else {
1094 None
1095 };
1096
1097 let span = Span::current();
1098
1099 let catalog_for_insights = if needs_plan_insights {
1101 Some(Arc::clone(&catalog))
1102 } else {
1103 None
1104 };
1105 let mut compute_instances = BTreeMap::new();
1106 if needs_plan_insights {
1107 for user_cluster in catalog.user_clusters() {
1108 let snapshot = ComputeInstanceSnapshot::new_without_collections(user_cluster.id);
1109 compute_instances.insert(user_cluster.name.clone(), snapshot);
1110 }
1111 }
1112
1113 let source_ids_for_closure = source_ids.clone();
1114
1115 let optimization_future: JoinHandle<Result<_, AdapterError>> = match query_plan {
1116 OptimizerInput::CopyTo(select_plan, mut copy_to_ctx) => {
1117 let raw_expr = select_plan.source.clone();
1118
1119 let worker_counts = cluster.replicas().map(|r| {
1121 let loc = &r.config.location;
1122 loc.workers().unwrap_or_else(|| loc.num_processes())
1123 });
1124 let max_worker_count = match worker_counts.max() {
1125 Some(count) => u64::cast_from(count),
1126 None => {
1127 return Err(AdapterError::NoClusterReplicasAvailable {
1128 name: cluster.name.clone(),
1129 is_managed: cluster.is_managed(),
1130 });
1131 }
1132 };
1133 copy_to_ctx.output_batch_count = Some(max_worker_count);
1134
1135 let mut optimizer = optimize::copy_to::Optimizer::new(
1136 Arc::clone(&catalog),
1137 compute_instance_snapshot,
1138 view_id,
1139 copy_to_ctx,
1140 optimizer_config,
1141 self.optimizer_metrics.clone(),
1142 );
1143
1144 mz_ore::task::spawn_blocking(
1145 || "optimize copy-to",
1146 move || {
1147 span.in_scope(|| {
1148 let _dispatch_guard = explain_ctx.dispatch_guard();
1149
1150 let global_lir_plan = optimize::optimize_oneshot(
1152 &mut optimizer,
1153 raw_expr.clone(),
1154 |local_mir_plan| {
1155 local_mir_plan.resolve(
1156 timestamp_context.clone(),
1157 &session_meta,
1158 stats,
1159 )
1160 },
1161 )?;
1162 Ok(Execution::CopyToS3 {
1163 global_lir_plan,
1164 source_ids: source_ids_for_closure,
1165 })
1166 })
1167 },
1168 )
1169 }
1170 OptimizerInput::Select(select_plan) => {
1171 let select_plan = select_plan.clone();
1172 let raw_expr = select_plan.source.clone();
1173
1174 let mut optimizer = optimize::peek::Optimizer::new(
1176 Arc::clone(&catalog),
1177 compute_instance_snapshot,
1178 select_plan.finishing.clone(),
1179 view_id,
1180 index_id,
1181 optimizer_config,
1182 self.optimizer_metrics.clone(),
1183 );
1184
1185 mz_ore::task::spawn_blocking(
1186 || "optimize peek",
1187 move || {
1188 fail::fail_point!("peek_before_optimize");
1189 span.in_scope(|| {
1190 let _dispatch_guard = explain_ctx.dispatch_guard();
1191
1192 let global_lir_plan_result = optimize::optimize_oneshot(
1197 &mut optimizer,
1198 raw_expr.clone(),
1199 |local_mir_plan| {
1200 local_mir_plan.resolve(
1201 timestamp_context.clone(),
1202 &session_meta,
1203 stats,
1204 )
1205 },
1206 )
1207 .map_err(AdapterError::from);
1208 let optimization_finished_at = now();
1209
1210 let create_insights_ctx =
1211 |optimizer: &optimize::peek::Optimizer,
1212 is_notice: bool|
1213 -> Option<Box<PlanInsightsContext>> {
1214 if !needs_plan_insights {
1215 return None;
1216 }
1217
1218 let catalog = catalog_for_insights.as_ref()?;
1219
1220 let enable_re_optimize = if needs_plan_insights {
1221 let dyncfgs = catalog.system_config().dyncfgs();
1229 let opt_limit = mz_adapter_types::dyncfgs
1230 ::PLAN_INSIGHTS_NOTICE_FAST_PATH_CLUSTERS_OPTIMIZE_DURATION
1231 .get(dyncfgs);
1232 !(is_notice && optimizer.duration() > opt_limit)
1233 } else {
1234 false
1235 };
1236
1237 Some(Box::new(PlanInsightsContext {
1238 stmt: select_plan
1239 .select
1240 .as_deref()
1241 .map(Clone::clone)
1242 .map(Statement::Select),
1243 raw_expr: raw_expr.clone(),
1244 catalog: Arc::clone(catalog),
1245 compute_instances,
1246 target_instance: target_cluster_name,
1247 metrics: optimizer.metrics().clone(),
1248 finishing: optimizer.finishing().clone(),
1249 optimizer_config: optimizer.config().clone(),
1250 session: session_meta,
1251 timestamp_context,
1252 view_id: optimizer.select_id(),
1253 index_id: optimizer.index_id(),
1254 enable_re_optimize,
1255 }))
1256 };
1257
1258 let global_lir_plan = match global_lir_plan_result {
1259 Ok(plan) => plan,
1260 Err(err) => {
1261 let result = if let ExplainContext::Plan(explain_ctx) =
1262 explain_ctx
1263 && explain_ctx.broken
1264 {
1265 tracing::error!(
1267 "error while handling EXPLAIN statement: {}",
1268 err
1269 );
1270 Ok(Execution::ExplainPlan {
1271 df_meta: Default::default(),
1272 explain_ctx,
1273 optimizer,
1274 insights_ctx: None,
1275 })
1276 } else {
1277 Err(err)
1278 };
1279 return result;
1280 }
1281 };
1282
1283 match explain_ctx {
1284 ExplainContext::Timestamp => {
1286 soft_panic_or_log!(
1287 "EXPLAIN TIMESTAMP reached peek optimization"
1288 );
1289 Err(AdapterError::Internal(
1290 "EXPLAIN TIMESTAMP reached peek optimization".into(),
1291 ))
1292 }
1293 ExplainContext::Plan(explain_ctx) => {
1294 let (_, df_meta, _) = global_lir_plan.unapply();
1295 let insights_ctx = create_insights_ctx(&optimizer, false);
1296 Ok(Execution::ExplainPlan {
1297 df_meta,
1298 explain_ctx,
1299 optimizer,
1300 insights_ctx,
1301 })
1302 }
1303 ExplainContext::None => Ok(Execution::Peek {
1304 global_lir_plan,
1305 optimization_finished_at,
1306 plan_insights_optimizer_trace: None,
1307 finishing: select_plan.finishing,
1308 copy_to: select_plan.copy_to,
1309 insights_ctx: None,
1310 }),
1311 ExplainContext::PlanInsightsNotice(optimizer_trace) => {
1312 let insights_ctx = create_insights_ctx(&optimizer, true);
1313 Ok(Execution::Peek {
1314 global_lir_plan,
1315 optimization_finished_at,
1316 plan_insights_optimizer_trace: Some(optimizer_trace),
1317 finishing: select_plan.finishing,
1318 copy_to: select_plan.copy_to,
1319 insights_ctx,
1320 })
1321 }
1322 ExplainContext::Pushdown => {
1323 let (plan, _, _) = global_lir_plan.unapply();
1324 let imports = match plan {
1325 PeekPlan::SlowPath(plan) => plan
1326 .desc
1327 .source_imports
1328 .into_iter()
1329 .filter_map(|(id, import)| {
1330 import
1331 .desc
1332 .arguments
1333 .operators
1334 .map(|plan| (id, plan))
1335 })
1336 .collect(),
1337 PeekPlan::FastPath(_) => {
1338 std::collections::BTreeMap::default()
1339 }
1340 };
1341 Ok(Execution::ExplainPushdown {
1342 imports,
1343 determination: determination_for_pushdown
1344 .expect("it's present for the ExplainPushdown case"),
1345 })
1346 }
1347 }
1348 })
1349 },
1350 )
1351 }
1352 OptimizerInput::Subscribe(plan) => {
1353 let plan = plan.clone();
1354 let catalog: Arc<Catalog> = Arc::clone(&catalog);
1355 let debug_name = format!("subscribe-{}", index_id);
1356 let mut optimizer = optimize::subscribe::Optimizer::new(
1357 catalog,
1358 compute_instance_snapshot.clone(),
1359 view_id,
1360 index_id,
1361 plan.with_snapshot,
1362 plan.up_to,
1363 debug_name,
1364 optimizer_config,
1365 self.optimizer_metrics.clone(),
1366 );
1367 mz_ore::task::spawn_blocking(
1368 || "optimize subscribe",
1369 move || {
1370 span.in_scope(|| {
1371 let _dispatch_guard = explain_ctx.dispatch_guard();
1372
1373 let global_mir_plan = optimizer.catch_unwind_optimize(plan.clone())?;
1374 let as_of = timestamp_context.timestamp_or_default();
1375
1376 if let Some(up_to) = optimizer.up_to() {
1377 if as_of > up_to {
1378 return Err(AdapterError::AbsurdSubscribeBounds {
1379 as_of,
1380 up_to,
1381 });
1382 }
1383 }
1384 let local_mir_plan =
1385 global_mir_plan.resolve(Antichain::from_elem(as_of));
1386
1387 let global_lir_plan = optimizer.catch_unwind_optimize(local_mir_plan);
1388 if let ExplainContext::Plan(explain_ctx) = explain_ctx {
1389 let df_meta = match global_lir_plan {
1390 Ok(global_lir_plan) => global_lir_plan.unapply().1,
1391 Err(err) if explain_ctx.broken => {
1393 tracing::error!(
1394 "error while handling EXPLAIN statement: {}",
1395 err
1396 );
1397 Default::default()
1398 }
1399 Err(err) => return Err(err.into()),
1400 };
1401 return Ok(Execution::ExplainSubscribe {
1402 df_meta,
1403 explain_ctx,
1404 sink_id: optimizer.sink_id(),
1405 cluster_id: optimizer.cluster_id(),
1406 features: optimizer.config().features.clone(),
1407 equal_bounds: optimizer.up_to().filter(|up_to| *up_to == as_of),
1408 });
1409 }
1410 let global_lir_plan = global_lir_plan?;
1411 let optimization_finished_at = now();
1412
1413 let (df_desc, df_meta) = global_lir_plan.unapply();
1414 Ok(Execution::Subscribe {
1415 subscribe_plan: plan,
1416 df_desc,
1417 df_meta,
1418 optimization_finished_at,
1419 })
1420 })
1421 },
1422 )
1423 }
1424 };
1425
1426 let mut optimization_timeout = *session.vars().statement_timeout();
1427 if optimization_timeout == Duration::ZERO {
1429 optimization_timeout = Duration::MAX;
1430 }
1431 let optimization_wait = tokio::time::timeout(optimization_timeout, optimization_future);
1436 let optimization_wait = tokio::select! {
1437 result = optimization_wait => result,
1438 () = connection_closed => return Err(AdapterError::Canceled),
1439 };
1440 let optimization_result = match optimization_wait {
1441 Ok(Ok(result)) => result,
1442 Ok(Err(AdapterError::Optimizer(err))) => {
1443 return Err(AdapterError::Internal(format!(
1444 "internal error in optimizer: {}",
1445 err
1446 )));
1447 }
1448 Ok(Err(err)) => {
1449 return Err(err);
1450 }
1451 Err(_elapsed) => {
1452 warn!("optimize peek timed out after {:?}", optimization_timeout);
1453 return Err(AdapterError::StatementTimeout);
1454 }
1455 };
1456
1457 if let Some(logging_id) = logging.id() {
1459 self.log_lifecycle_event(logging_id, StatementLifecycleEvent::OptimizationFinished);
1460 }
1461
1462 Self::assert_read_holds_correct(
1464 &read_holds,
1465 &optimization_result,
1466 &determination,
1467 target_cluster_id,
1468 in_immediate_multi_stmt_txn,
1469 );
1470
1471 match optimization_result {
1473 Execution::ExplainPlan {
1474 df_meta,
1475 explain_ctx,
1476 optimizer,
1477 insights_ctx,
1478 } => {
1479 let rows = explain_plan_inner(
1480 session,
1481 &catalog,
1482 df_meta,
1483 explain_ctx,
1484 optimizer,
1485 insights_ctx,
1486 )
1487 .await?;
1488
1489 Ok(Some(ExecuteResponse::SendingRowsImmediate {
1490 rows: Box::new(rows.into_row_iter()),
1491 }))
1492 }
1493 Execution::ExplainSubscribe {
1494 df_meta,
1495 explain_ctx,
1496 sink_id,
1497 cluster_id,
1498 features,
1499 equal_bounds,
1500 } => {
1501 if let Some(bound) = equal_bounds {
1502 session.add_notice(AdapterNotice::EqualSubscribeBounds { bound });
1503 }
1504 let rows = explain_subscribe_inner(
1505 session,
1506 &catalog,
1507 df_meta,
1508 explain_ctx,
1509 sink_id,
1510 cluster_id,
1511 &features,
1512 )
1513 .await?;
1514
1515 Ok(Some(ExecuteResponse::SendingRowsImmediate {
1516 rows: Box::new(rows.into_row_iter()),
1517 }))
1518 }
1519 Execution::ExplainPushdown {
1520 imports,
1521 determination,
1522 } => {
1523 let as_of = determination.timestamp_context.antichain();
1524 let mz_now = determination
1525 .timestamp_context
1526 .timestamp()
1527 .map(|t| ResultSpec::value(Datum::MzTimestamp(*t)))
1528 .unwrap_or_else(ResultSpec::value_all);
1529
1530 Ok(Some(
1531 coord::sequencer::explain_pushdown_future_inner(
1532 session,
1533 &*catalog,
1534 &self.storage_collections,
1535 as_of,
1536 mz_now,
1537 imports,
1538 )
1539 .await
1540 .await?,
1541 ))
1542 }
1543 Execution::Peek {
1544 global_lir_plan,
1545 optimization_finished_at: _optimization_finished_at,
1546 plan_insights_optimizer_trace,
1547 finishing,
1548 copy_to,
1549 insights_ctx,
1550 } => {
1551 let (peek_plan, df_meta, typ) = global_lir_plan.unapply();
1555
1556 coord::sequencer::emit_optimizer_notices(
1557 &*catalog,
1558 session,
1559 &df_meta.optimizer_notices,
1560 );
1561
1562 if let Some(trace) = plan_insights_optimizer_trace {
1564 let target_cluster = catalog.get_cluster(target_cluster_id);
1565 let features = OptimizerFeatures::from(catalog.system_config())
1566 .override_from(&target_cluster.config.features())
1567 .override_from(
1570 &catalog
1571 .state()
1572 .cluster_scoped_optimizer_overrides(target_cluster_id),
1573 );
1574 let insights = trace
1575 .into_plan_insights(
1576 &features,
1577 &catalog.for_session(session),
1578 Some(finishing.clone()),
1579 Some(target_cluster),
1580 df_meta.clone(),
1581 insights_ctx,
1582 )
1583 .await?;
1584 session.add_notice(AdapterNotice::PlanInsights(insights));
1585 }
1586
1587 let watch_set = logging.id().map(|logging_id| {
1588 WatchSetCreation::new(
1589 logging_id,
1590 catalog.state(),
1591 &input_id_bundle,
1592 determination.timestamp_context.timestamp_or_default(),
1593 )
1594 });
1595
1596 let max_result_size = catalog.system_config().max_result_size();
1597
1598 let determination_for_notice = if session.vars().emit_timestamp_notice() {
1601 Some(determination.clone())
1602 } else {
1603 None
1604 };
1605
1606 let response = match peek_plan {
1607 PeekPlan::FastPath(fast_path_plan) => {
1608 let row_set_finishing_seconds =
1609 session.metrics().row_set_finishing_seconds().clone();
1610
1611 let peek_stash_read_batch_size_bytes =
1612 mz_compute_types::dyncfgs::PEEK_RESPONSE_STASH_READ_BATCH_SIZE_BYTES
1613 .get(catalog.system_config().dyncfgs());
1614 let peek_stash_read_memory_budget_bytes =
1615 mz_compute_types::dyncfgs::PEEK_RESPONSE_STASH_READ_MEMORY_BUDGET_BYTES
1616 .get(catalog.system_config().dyncfgs());
1617
1618 self.implement_fast_path_peek_plan(
1619 fast_path_plan,
1620 determination.timestamp_context.timestamp_or_default(),
1621 finishing,
1622 target_cluster_id,
1623 target_replica,
1624 typ,
1625 max_result_size,
1626 max_query_result_size,
1627 row_set_finishing_seconds,
1628 read_holds,
1629 peek_stash_read_batch_size_bytes,
1630 peek_stash_read_memory_budget_bytes,
1631 session.conn_id().clone(),
1632 source_ids,
1633 watch_set,
1634 logging,
1635 )
1636 .await?
1637 }
1638 PeekPlan::SlowPath(dataflow_plan) => {
1639 session.require_coordinator_completion();
1640 if let Some(logging_id) = logging.id() {
1641 self.log_set_transient_index_id(logging_id, dataflow_plan.id);
1642 }
1643
1644 let response = self
1645 .call_coordinator(|tx| Command::ExecuteSlowPathPeek {
1646 dataflow_plan: Box::new(dataflow_plan),
1647 determination,
1648 finishing,
1649 compute_instance: target_cluster_id,
1650 target_replica,
1651 intermediate_result_type: typ,
1652 source_ids,
1653 conn_id: session.conn_id().clone(),
1654 max_result_size,
1655 max_query_result_size,
1656 watch_set,
1657 tx,
1658 })
1659 .await??;
1660 logging.defuse();
1666 response
1667 }
1668 };
1669
1670 if let Some(determination) = determination_for_notice {
1672 self.add_timestamp_notice(
1673 session,
1674 target_cluster_id,
1675 input_id_bundle.clone(),
1676 determination,
1677 )
1678 .await?;
1679 }
1680
1681 Ok(Some(match copy_to {
1682 None => response,
1683 Some(format) => ExecuteResponse::CopyTo {
1685 format,
1686 resp: Box::new(response),
1687 },
1688 }))
1689 }
1690 Execution::Subscribe {
1691 subscribe_plan,
1692 df_desc,
1693 df_meta,
1694 optimization_finished_at: _optimization_finished_at,
1695 } => {
1696 session.require_coordinator_completion();
1697 if df_desc.as_of.as_ref().expect("as of set") == &df_desc.until {
1698 session.add_notice(AdapterNotice::EqualSubscribeBounds {
1699 bound: *df_desc.until.as_option().expect("as of set"),
1700 });
1701 }
1702 coord::sequencer::emit_optimizer_notices(
1703 &*catalog,
1704 session,
1705 &df_meta.optimizer_notices,
1706 );
1707
1708 fail::fail_point!("subscribe_before_dispatch");
1713
1714 let response = self
1715 .call_coordinator(|tx| Command::ExecuteSubscribe {
1716 df_desc,
1717 dependency_ids: subscribe_plan.from.depends_on(),
1718 cluster_id: target_cluster_id,
1719 replica_id: target_replica,
1720 conn_id: session.conn_id().clone(),
1721 session_uuid: session.uuid(),
1722 read_holds,
1723 plan: subscribe_plan,
1724 statement_logging_id: logging.id(),
1725 tx,
1726 })
1727 .await??;
1728 logging.defuse();
1735 Ok(Some(response))
1736 }
1737 Execution::CopyToS3 {
1738 global_lir_plan,
1739 source_ids,
1740 } => {
1741 let (df_desc, df_meta) = global_lir_plan.unapply();
1742
1743 coord::sequencer::emit_optimizer_notices(
1744 &*catalog,
1745 session,
1746 &df_meta.optimizer_notices,
1747 );
1748
1749 let sink_id = df_desc.sink_id();
1751 let sinks = &df_desc.sink_exports;
1752 if sinks.len() != 1 {
1753 return Err(AdapterError::Internal(
1754 "expected exactly one copy to s3 sink".into(),
1755 ));
1756 }
1757 let (_, sink_desc) = sinks
1758 .first_key_value()
1759 .expect("known to be exactly one copy to s3 sink");
1760 let s3_sink_connection = match &sink_desc.connection {
1761 mz_compute_types::sinks::ComputeSinkConnection::CopyToS3Oneshot(conn) => {
1762 conn.clone()
1763 }
1764 _ => {
1765 return Err(AdapterError::Internal(
1766 "expected copy to s3 oneshot sink".into(),
1767 ));
1768 }
1769 };
1770
1771 session.require_coordinator_completion();
1774 self.call_coordinator(|tx| Command::CopyToPreflight {
1775 s3_sink_connection,
1776 sink_id,
1777 tx,
1778 })
1779 .await??;
1780
1781 let watch_set = logging.id().map(|logging_id| {
1783 WatchSetCreation::new(
1784 logging_id,
1785 catalog.state(),
1786 &input_id_bundle,
1787 determination.timestamp_context.timestamp_or_default(),
1788 )
1789 });
1790
1791 let response = self
1796 .call_coordinator(|tx| Command::ExecuteCopyTo {
1797 df_desc: Box::new(df_desc),
1798 compute_instance: target_cluster_id,
1799 target_replica,
1800 source_ids,
1801 conn_id: session.conn_id().clone(),
1802 watch_set,
1803 tx,
1804 })
1805 .await??;
1806
1807 Ok(Some(response))
1808 }
1809 }
1810 }
1811
1812 pub(crate) async fn determine_timestamp(
1818 &mut self,
1819 session: &Session,
1820 id_bundle: &CollectionIdBundle,
1821 when: &QueryWhen,
1822 compute_instance: ComputeInstanceId,
1823 timeline_context: &TimelineContext,
1824 oracle_read_ts: Option<Timestamp>,
1825 real_time_recency_ts: Option<Timestamp>,
1826 ) -> Result<(TimestampDetermination, ReadHolds), AdapterError> {
1827 let isolation_level = session.vars().transaction_isolation();
1828
1829 let (read_holds, upper) = self
1830 .acquire_read_holds_and_least_valid_write(id_bundle)
1831 .await
1832 .map_err(|err| {
1833 AdapterError::concurrent_dependency_drop_from_collection_lookup_error(
1834 err,
1835 compute_instance,
1836 )
1837 })?;
1838 let (det, read_holds) = <Coordinator as TimestampProvider>::determine_timestamp_for_inner(
1839 session,
1840 id_bundle,
1841 when,
1842 timeline_context,
1843 oracle_read_ts,
1844 real_time_recency_ts,
1845 isolation_level,
1846 read_holds,
1847 upper.clone(),
1848 )?;
1849
1850 session
1851 .metrics()
1852 .by_cluster()
1853 .determine_timestamp(
1854 compute_instance,
1855 det.respond_immediately(),
1856 *isolation_level,
1857 )
1858 .inc();
1859 if !det.respond_immediately()
1860 && isolation_level.is_bounded_staleness()
1861 && real_time_recency_ts.is_none()
1862 {
1863 if let Some(bs_ts) = det.timestamp_context.timestamp() {
1865 let (serializable_det, _tmp_read_holds) =
1866 <Coordinator as TimestampProvider>::determine_timestamp_for_inner(
1867 session,
1868 id_bundle,
1869 when,
1870 timeline_context,
1871 oracle_read_ts,
1872 real_time_recency_ts,
1873 &IsolationLevel::Serializable,
1874 read_holds.clone(),
1875 upper,
1876 )?;
1877 if let Some(serializable) = serializable_det.timestamp_context.timestamp() {
1878 session
1879 .metrics()
1880 .by_cluster()
1881 .timestamp_difference_for_bounded_staleness_ms(compute_instance)
1882 .observe(f64::cast_lossy(u64::from(
1883 serializable.saturating_sub(*bs_ts),
1884 )));
1885 }
1886 }
1887 }
1888
1889 Ok((det, read_holds))
1890 }
1891
1892 fn assert_read_holds_correct(
1893 read_holds: &ReadHolds,
1894 execution: &Execution,
1895 determination: &TimestampDetermination,
1896 target_cluster_id: ClusterId,
1897 in_immediate_multi_stmt_txn: bool,
1898 ) {
1899 let (source_imports, index_imports, as_of, execution_name): (
1901 Vec<GlobalId>,
1902 Vec<GlobalId>,
1903 Timestamp,
1904 &str,
1905 ) = match execution {
1906 Execution::Peek {
1907 global_lir_plan, ..
1908 } => match global_lir_plan.peek_plan() {
1909 PeekPlan::FastPath(fast_path_plan) => {
1910 let (sources, indexes) = match fast_path_plan {
1911 FastPathPlan::Constant(..) => (vec![], vec![]),
1912 FastPathPlan::PeekExisting(_coll_id, idx_id, ..) => (vec![], vec![*idx_id]),
1913 FastPathPlan::PeekPersist(global_id, ..) => (vec![*global_id], vec![]),
1914 };
1915 (
1916 sources,
1917 indexes,
1918 determination.timestamp_context.timestamp_or_default(),
1919 "FastPath",
1920 )
1921 }
1922 PeekPlan::SlowPath(dataflow_plan) => {
1923 let as_of = dataflow_plan
1924 .desc
1925 .as_of
1926 .clone()
1927 .expect("dataflow has an as_of")
1928 .into_element();
1929 (
1930 dataflow_plan.desc.source_imports.keys().cloned().collect(),
1931 dataflow_plan.desc.index_imports.keys().cloned().collect(),
1932 as_of,
1933 "SlowPath",
1934 )
1935 }
1936 },
1937 Execution::CopyToS3 {
1938 global_lir_plan, ..
1939 } => {
1940 let df_desc = global_lir_plan.df_desc();
1941 let as_of = df_desc
1942 .as_of
1943 .clone()
1944 .expect("dataflow has an as_of")
1945 .into_element();
1946 (
1947 df_desc.source_imports.keys().cloned().collect(),
1948 df_desc.index_imports.keys().cloned().collect(),
1949 as_of,
1950 "CopyToS3",
1951 )
1952 }
1953 Execution::ExplainPlan { .. }
1954 | Execution::ExplainSubscribe { .. }
1955 | Execution::ExplainPushdown { .. } => {
1956 return;
1958 }
1959 Execution::Subscribe { df_desc, .. } => {
1960 let as_of = df_desc
1961 .as_of
1962 .clone()
1963 .expect("dataflow has an as_of")
1964 .into_element();
1965 (
1966 df_desc.source_imports.keys().cloned().collect(),
1967 df_desc.index_imports.keys().cloned().collect(),
1968 as_of,
1969 "Subscribe",
1970 )
1971 }
1972 };
1973
1974 for id in source_imports.iter() {
1976 soft_assert_or_log!(
1977 read_holds.storage_holds.contains_key(id),
1978 "[{}] missing read hold for the source import {}; (in_immediate_multi_stmt_txn: {})",
1979 execution_name,
1980 id,
1981 in_immediate_multi_stmt_txn,
1982 );
1983 }
1984 for id in index_imports.iter() {
1985 soft_assert_or_log!(
1986 read_holds
1987 .compute_ids()
1988 .map(|(_instance, coll)| coll)
1989 .contains(id),
1990 "[{}] missing read hold for the index import {}; (in_immediate_multi_stmt_txn: {})",
1991 execution_name,
1992 id,
1993 in_immediate_multi_stmt_txn,
1994 );
1995 }
1996
1997 for (id, h) in read_holds.storage_holds.iter() {
1999 soft_assert_or_log!(
2000 h.since().less_equal(&as_of),
2001 "[{}] storage read hold at {:?} for collection {} is not enough for as_of {:?}, determination: {:?}; (in_immediate_multi_stmt_txn: {})",
2002 execution_name,
2003 h.since(),
2004 id,
2005 as_of,
2006 determination,
2007 in_immediate_multi_stmt_txn,
2008 );
2009 }
2010 for ((instance, id), h) in read_holds.compute_holds.iter() {
2011 soft_assert_eq_or_log!(
2012 *instance,
2013 target_cluster_id,
2014 "[{}] the read hold on {} is on the wrong cluster; (in_immediate_multi_stmt_txn: {})",
2015 execution_name,
2016 id,
2017 in_immediate_multi_stmt_txn,
2018 );
2019 soft_assert_or_log!(
2020 h.since().less_equal(&as_of),
2021 "[{}] compute read hold at {:?} for collection {} is not enough for as_of {:?}, determination: {:?}; (in_immediate_multi_stmt_txn: {})",
2022 execution_name,
2023 h.since(),
2024 id,
2025 as_of,
2026 determination,
2027 in_immediate_multi_stmt_txn,
2028 );
2029 }
2030 }
2031}
2032
2033enum Execution {
2035 Peek {
2036 global_lir_plan: optimize::peek::GlobalLirPlan,
2037 optimization_finished_at: EpochMillis,
2038 plan_insights_optimizer_trace: Option<OptimizerTrace>,
2039 finishing: RowSetFinishing,
2040 copy_to: Option<plan::CopyFormat>,
2041 insights_ctx: Option<Box<PlanInsightsContext>>,
2042 },
2043 Subscribe {
2044 subscribe_plan: SubscribePlan,
2045 df_desc: mz_compute_types::dataflows::LirDataflowDescription,
2046 df_meta: DataflowMetainfo,
2047 optimization_finished_at: EpochMillis,
2048 },
2049 CopyToS3 {
2050 global_lir_plan: optimize::copy_to::GlobalLirPlan,
2051 source_ids: BTreeSet<GlobalId>,
2052 },
2053 ExplainPlan {
2054 df_meta: DataflowMetainfo,
2055 explain_ctx: ExplainPlanContext,
2056 optimizer: optimize::peek::Optimizer,
2057 insights_ctx: Option<Box<PlanInsightsContext>>,
2058 },
2059 ExplainSubscribe {
2060 df_meta: DataflowMetainfo,
2061 explain_ctx: ExplainPlanContext,
2062 sink_id: GlobalId,
2063 cluster_id: ComputeInstanceId,
2064 features: OptimizerFeatures,
2065 equal_bounds: Option<Timestamp>,
2066 },
2067 ExplainPushdown {
2068 imports:
2069 BTreeMap<GlobalId, mz_expr::MfpPlan<mz_compute_types::plan::scalar::LirScalarExpr>>,
2070 determination: TimestampDetermination,
2071 },
2072}
2073
2074fn eval_copy_to_uri(
2076 to: HirScalarExpr,
2077 session: &Session,
2078 catalog_state: &CatalogState,
2079) -> Result<Uri, AdapterError> {
2080 let style = ExprPrepOneShot {
2081 logical_time: EvalTime::NotAvailable,
2082 session,
2083 catalog_state,
2084 };
2085 let mut to = to.lower_uncorrelated(catalog_state.system_config())?;
2086 style.prep_scalar_expr(&mut to)?;
2087 let temp_storage = RowArena::new();
2088 let evaled = to.eval(&[], &temp_storage)?;
2089 if evaled == Datum::Null {
2090 coord_bail!("COPY TO target value can not be null");
2091 }
2092 let to_url = match Uri::from_str(evaled.unwrap_str()) {
2093 Ok(url) => {
2094 if url.scheme_str() != Some("s3") && url.scheme_str() != Some("gs") {
2095 coord_bail!("only 's3://...' and 'gs://...' urls are supported as COPY TO target");
2096 }
2097 url
2098 }
2099 Err(e) => coord_bail!("could not parse COPY TO target url: {}", e),
2100 };
2101 Ok(to_url)
2102}
2103
2104async fn explain_plan_inner(
2106 session: &Session,
2107 catalog: &Catalog,
2108 df_meta: DataflowMetainfo,
2109 explain_ctx: ExplainPlanContext,
2110 optimizer: peek::Optimizer,
2111 insights_ctx: Option<Box<PlanInsightsContext>>,
2112) -> Result<Vec<Row>, AdapterError> {
2113 let ExplainPlanContext {
2114 config,
2115 format,
2116 stage,
2117 desc,
2118 optimizer_trace,
2119 ..
2120 } = explain_ctx;
2121
2122 let desc = desc.expect("RelationDesc for SelectPlan in EXPLAIN mode");
2123
2124 let session_catalog = catalog.for_session(session);
2125 let expr_humanizer = {
2126 let transient_items = btreemap! {
2127 optimizer.select_id() => TransientItem::new(
2128 Some(vec![GlobalId::Explain.to_string()]),
2129 Some(desc.iter_names().map(|c| c.to_string()).collect()),
2130 )
2131 };
2132 ExprHumanizerExt::new(transient_items, &session_catalog)
2133 };
2134
2135 let finishing = if optimizer.finishing().is_trivial(desc.arity()) {
2136 None
2137 } else {
2138 Some(optimizer.finishing().clone())
2139 };
2140
2141 let target_cluster = catalog.get_cluster(optimizer.cluster_id());
2142 let features = optimizer.config().features.clone();
2143
2144 let rows = optimizer_trace
2145 .into_rows(
2146 format,
2147 &config,
2148 &features,
2149 &expr_humanizer,
2150 finishing,
2151 Some(target_cluster),
2152 df_meta,
2153 stage,
2154 plan::ExplaineeStatementKind::Select,
2155 insights_ctx,
2156 )
2157 .await?;
2158
2159 Ok(rows)
2160}
2161
2162async fn explain_subscribe_inner(
2166 session: &Session,
2167 catalog: &Catalog,
2168 df_meta: DataflowMetainfo,
2169 explain_ctx: ExplainPlanContext,
2170 sink_id: GlobalId,
2171 cluster_id: ComputeInstanceId,
2172 features: &OptimizerFeatures,
2173) -> Result<Vec<Row>, AdapterError> {
2174 let ExplainPlanContext {
2175 config,
2176 format,
2177 stage,
2178 desc,
2179 optimizer_trace,
2180 ..
2181 } = explain_ctx;
2182
2183 let session_catalog = catalog.for_session(session);
2184 let expr_humanizer = {
2185 let transient_items = btreemap! {
2186 sink_id => TransientItem::new(
2187 Some(vec![GlobalId::Explain.to_string()]),
2188 desc.map(|d| d.iter_names().map(|c| c.to_string()).collect()),
2189 )
2190 };
2191 ExprHumanizerExt::new(transient_items, &session_catalog)
2192 };
2193
2194 let target_cluster = catalog.get_cluster(cluster_id);
2195
2196 let rows = optimizer_trace
2197 .into_rows(
2198 format,
2199 &config,
2200 features,
2201 &expr_humanizer,
2202 None,
2203 Some(target_cluster),
2204 df_meta,
2205 stage,
2206 plan::ExplaineeStatementKind::Subscribe,
2207 None,
2208 )
2209 .await?;
2210
2211 Ok(rows)
2212}
2213
2214async fn statistics_oracle(
2216 session: &Session,
2217 source_ids: &BTreeSet<GlobalId>,
2218 query_as_of: &Antichain<Timestamp>,
2219 is_oneshot: bool,
2220 system_config: &vars::SystemVars,
2221 storage_collections: &dyn StorageCollections,
2222) -> Result<Box<dyn StatisticsOracle>, AdapterError> {
2223 if !session.vars().enable_session_cardinality_estimates() {
2224 let stats: Box<dyn StatisticsOracle> = Box::new(EmptyStatisticsOracle);
2225 return Ok(stats);
2226 }
2227
2228 let timeout = if is_oneshot {
2229 system_config.optimizer_oneshot_stats_timeout()
2231 } else {
2232 system_config.optimizer_stats_timeout()
2233 };
2234
2235 let cached_stats = mz_ore::future::timeout(
2236 timeout,
2237 CachedStatisticsOracle::new(source_ids, query_as_of, storage_collections),
2238 )
2239 .await;
2240
2241 match cached_stats {
2242 Ok(stats) => Ok(Box::new(stats)),
2243 Err(mz_ore::future::TimeoutError::DeadlineElapsed) => {
2244 warn!(
2245 is_oneshot = is_oneshot,
2246 "optimizer statistics collection timed out after {}ms",
2247 timeout.as_millis()
2248 );
2249
2250 Ok(Box::new(EmptyStatisticsOracle))
2251 }
2252 Err(mz_ore::future::TimeoutError::Inner(e)) => Err(AdapterError::Storage(e)),
2253 }
2254}
2255
2256fn check_log_reads(
2265 catalog: &Catalog,
2266 cluster: &Cluster,
2267 source_ids: &BTreeSet<GlobalId>,
2268 target_replica: &mut Option<ReplicaId>,
2269 vars: &SessionVars,
2270) -> Result<impl IntoIterator<Item = AdapterNotice>, AdapterError>
2271where
2272{
2273 let log_names = source_ids
2274 .iter()
2275 .map(|gid| catalog.resolve_item_id(gid))
2276 .flat_map(|item_id| catalog.introspection_dependencies(item_id))
2277 .map(|item_id| catalog.get_entry(&item_id).name().item.clone())
2278 .collect::<Vec<_>>();
2279
2280 if log_names.is_empty() {
2281 return Ok(None);
2282 }
2283
2284 let num_replicas = cluster.replicas().count();
2288 if target_replica.is_none() {
2289 if num_replicas == 1 {
2290 *target_replica = cluster.replicas().map(|r| r.replica_id).next();
2291 } else {
2292 return Err(AdapterError::UntargetedLogRead { log_names });
2293 }
2294 }
2295
2296 let replica_id = target_replica.expect("set to `Some` above");
2299 let replica = &cluster.replica(replica_id).expect("Replica must exist");
2300 if !replica.config.compute.logging.enabled() {
2301 return Err(AdapterError::IntrospectionDisabled { log_names });
2302 }
2303
2304 Ok(vars
2305 .emit_introspection_query_notice()
2306 .then_some(AdapterNotice::PerReplicaLogRead { log_names }))
2307}
2308
2309#[derive(Debug)]
2310struct CachedStatisticsOracle {
2311 cache: BTreeMap<GlobalId, usize>,
2312}
2313
2314impl CachedStatisticsOracle {
2315 pub async fn new(
2316 ids: &BTreeSet<GlobalId>,
2317 as_of: &Antichain<Timestamp>,
2318 storage_collections: &dyn StorageCollections,
2319 ) -> Result<Self, StorageError> {
2320 let mut cache = BTreeMap::new();
2321
2322 for id in ids {
2323 let stats = storage_collections.snapshot_stats(*id, as_of.clone()).await;
2324
2325 match stats {
2326 Ok(stats) => {
2327 cache.insert(*id, stats.num_updates);
2328 }
2329 Err(StorageError::IdentifierMissing(id)) => {
2330 ::tracing::debug!("no statistics for {id}")
2331 }
2332 Err(e) => return Err(e),
2333 }
2334 }
2335
2336 Ok(Self { cache })
2337 }
2338}
2339
2340impl StatisticsOracle for CachedStatisticsOracle {
2341 fn cardinality_estimate(&self, id: GlobalId) -> Option<usize> {
2342 self.cache.get(&id).map(|estimate| *estimate)
2343 }
2344
2345 fn as_map(&self) -> BTreeMap<GlobalId, usize> {
2346 self.cache.clone()
2347 }
2348}