Skip to main content

mz_adapter/coord/sequencer/inner/
peek.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10use std::collections::{BTreeMap, BTreeSet};
11use std::sync::Arc;
12
13use mz_adapter_types::dyncfgs::PLAN_INSIGHTS_NOTICE_FAST_PATH_CLUSTERS_OPTIMIZE_DURATION;
14use mz_compute_types::sinks::ComputeSinkConnection;
15use mz_controller_types::ClusterId;
16use mz_expr::{CollectionPlan, ResultSpec};
17use mz_ore::cast::CastFrom;
18use mz_ore::instrument;
19use mz_repr::optimize::{OptimizerFeatures, OverrideFrom};
20use mz_repr::{Datum, GlobalId, Timestamp};
21use mz_sql::ast::{ExplainStage, Statement};
22use mz_sql::catalog::CatalogCluster;
23// Import `plan` module, but only import select elements to avoid merge conflicts on use statements.
24use mz_sql::plan::QueryWhen;
25use mz_sql::plan::{self};
26use mz_sql::session::metadata::SessionMetadata;
27use mz_transform::EmptyStatisticsOracle;
28use tokio::sync::oneshot;
29use tracing::warn;
30use tracing::{Instrument, Span};
31
32use crate::active_compute_sink::{ActiveComputeSink, ActiveCopyTo};
33use crate::command::ExecuteResponse;
34use crate::coord::id_bundle::CollectionIdBundle;
35use crate::coord::peek::{self, PeekDataflowPlan, PeekPlan, PlannedPeek};
36use crate::coord::sequencer::inner::{return_if_err, spawn_linearized_read_ts};
37use crate::coord::sequencer::{check_log_reads, emit_optimizer_notices, eval_copy_to_uri};
38use crate::coord::timeline::{TimelineContext, timedomain_for};
39use crate::coord::timestamp_selection::{TimestampContext, TimestampDetermination};
40use crate::coord::{
41    Coordinator, CopyToContext, ExecuteContext, ExplainContext, ExplainPlanContext, Message,
42    PeekStage, PeekStageCopyTo, PeekStageExplainPlan, PeekStageExplainPushdown, PeekStageFinish,
43    PeekStageLinearizeTimestamp, PeekStageOptimize, PeekStageRealTimeRecency,
44    PeekStageTimestampReadHold, PlanValidity, StageResult, Staged, TargetCluster,
45};
46use crate::error::AdapterError;
47use crate::explain::insights::PlanInsightsContext;
48use crate::explain::optimizer_trace::OptimizerTrace;
49use crate::notice::AdapterNotice;
50use crate::optimize;
51use crate::session::{RequireLinearization, Session, TransactionOps, TransactionStatus};
52use crate::statement_logging::StatementLifecycleEvent;
53use crate::statement_logging::WatchSetCreation;
54
55impl Staged for PeekStage {
56    type Ctx = ExecuteContext;
57
58    fn validity(&mut self) -> &mut PlanValidity {
59        match self {
60            PeekStage::LinearizeTimestamp(stage) => &mut stage.validity,
61            PeekStage::RealTimeRecency(stage) => &mut stage.validity,
62            PeekStage::TimestampReadHold(stage) => &mut stage.validity,
63            PeekStage::Optimize(stage) => &mut stage.validity,
64            PeekStage::Finish(stage) => &mut stage.validity,
65            PeekStage::ExplainPlan(stage) => &mut stage.validity,
66            PeekStage::ExplainPushdown(stage) => &mut stage.validity,
67            PeekStage::CopyToPreflight(stage) => &mut stage.validity,
68            PeekStage::CopyToDataflow(stage) => &mut stage.validity,
69        }
70    }
71
72    async fn stage(
73        self,
74        coord: &mut Coordinator,
75        ctx: &mut ExecuteContext,
76    ) -> Result<StageResult<Box<Self>>, AdapterError> {
77        match self {
78            PeekStage::LinearizeTimestamp(stage) => {
79                coord.peek_linearize_timestamp(ctx.session(), stage).await
80            }
81            PeekStage::RealTimeRecency(stage) => {
82                coord.peek_real_time_recency(ctx.session(), stage).await
83            }
84            PeekStage::TimestampReadHold(stage) => {
85                coord.peek_timestamp_read_hold(ctx.session_mut(), stage)
86            }
87            PeekStage::Optimize(stage) => coord.peek_optimize(ctx.session(), stage).await,
88            PeekStage::Finish(stage) => coord.peek_finish(ctx, stage).await,
89            PeekStage::ExplainPlan(stage) => coord.peek_explain_plan(ctx.session(), stage).await,
90            PeekStage::ExplainPushdown(stage) => {
91                coord.peek_explain_pushdown(ctx.session(), stage).await
92            }
93            PeekStage::CopyToPreflight(stage) => coord.peek_copy_to_preflight(stage).await,
94            PeekStage::CopyToDataflow(stage) => coord.peek_copy_to_dataflow(ctx, stage).await,
95        }
96    }
97
98    fn message(self, ctx: ExecuteContext, span: Span) -> Message {
99        Message::PeekStageReady {
100            ctx,
101            span,
102            stage: self,
103        }
104    }
105
106    fn cancel_enabled(&self) -> bool {
107        true
108    }
109}
110
111impl Coordinator {
112    /// Sequence a peek, determining a timestamp and the most efficient dataflow interaction.
113    ///
114    /// Peeks are sequenced by assigning a timestamp for evaluation, and then determining and
115    /// deploying the most efficient evaluation plan. The peek could evaluate to a constant,
116    /// be a simple read out of an existing arrangement, or required a new dataflow to build
117    /// the results to return.
118    #[instrument]
119    pub(crate) async fn sequence_peek(
120        &mut self,
121        ctx: ExecuteContext,
122        plan: plan::SelectPlan,
123        target_cluster: TargetCluster,
124        max_query_result_size: Option<u64>,
125    ) {
126        let explain_ctx = if ctx.session().vars().emit_plan_insights_notice() {
127            let optimizer_trace = OptimizerTrace::new(ExplainStage::PlanInsights.paths());
128            ExplainContext::PlanInsightsNotice(optimizer_trace)
129        } else {
130            ExplainContext::None
131        };
132
133        let stage = return_if_err!(
134            self.peek_validate(
135                ctx.session(),
136                plan,
137                target_cluster,
138                None,
139                explain_ctx,
140                max_query_result_size
141            ),
142            ctx
143        );
144        self.sequence_staged(ctx, Span::current(), stage).await;
145    }
146
147    #[instrument]
148    pub(crate) async fn sequence_copy_to(
149        &mut self,
150        ctx: ExecuteContext,
151        plan::CopyToPlan {
152            select_plan,
153            desc,
154            to,
155            connection,
156            connection_id,
157            format,
158            max_file_size,
159        }: plan::CopyToPlan,
160        target_cluster: TargetCluster,
161    ) {
162        let uri = return_if_err!(
163            eval_copy_to_uri(to, ctx.session(), self.catalog().state()),
164            ctx
165        );
166
167        let stage = return_if_err!(
168            self.peek_validate(
169                ctx.session(),
170                select_plan,
171                target_cluster,
172                Some(CopyToContext {
173                    desc,
174                    uri,
175                    connection,
176                    connection_id,
177                    format,
178                    max_file_size,
179                    // This will be set in `peek_stage_validate` stage below.
180                    output_batch_count: None,
181                }),
182                ExplainContext::None,
183                Some(ctx.session().vars().max_query_result_size()),
184            ),
185            ctx
186        );
187        self.sequence_staged(ctx, Span::current(), stage).await;
188    }
189
190    #[instrument]
191    pub(crate) async fn explain_peek(
192        &mut self,
193        ctx: ExecuteContext,
194        plan::ExplainPlanPlan {
195            stage,
196            format,
197            config,
198            explainee,
199        }: plan::ExplainPlanPlan,
200        target_cluster: TargetCluster,
201    ) {
202        let plan::Explainee::Statement(stmt) = explainee else {
203            // This is currently asserted in the `sequence_explain_plan` code that
204            // calls this method.
205            unreachable!()
206        };
207        let plan::ExplaineeStatement::Select { broken, plan, desc } = stmt else {
208            // This is currently asserted in the `sequence_explain_plan` code that
209            // calls this method.
210            unreachable!()
211        };
212
213        // Create an OptimizerTrace instance to collect plans emitted when
214        // executing the optimizer pipeline.
215        let optimizer_trace = OptimizerTrace::new(stage.paths());
216
217        let stage = return_if_err!(
218            self.peek_validate(
219                ctx.session(),
220                plan,
221                target_cluster,
222                None,
223                ExplainContext::Plan(ExplainPlanContext {
224                    broken,
225                    config,
226                    format,
227                    stage,
228                    replan: None,
229                    desc: Some(desc),
230                    optimizer_trace,
231                }),
232                Some(ctx.session().vars().max_query_result_size()),
233            ),
234            ctx
235        );
236        self.sequence_staged(ctx, Span::current(), stage).await;
237    }
238
239    /// Do some simple validation. We must defer most of it until after any off-thread work.
240    #[instrument]
241    pub fn peek_validate(
242        &self,
243        session: &Session,
244        plan: mz_sql::plan::SelectPlan,
245        target_cluster: TargetCluster,
246        copy_to_ctx: Option<CopyToContext>,
247        explain_ctx: ExplainContext,
248        max_query_result_size: Option<u64>,
249    ) -> Result<PeekStage, AdapterError> {
250        // Collect optimizer parameters.
251        let catalog = self.owned_catalog();
252        let cluster = catalog.resolve_target_cluster(target_cluster, session)?;
253        let compute_instance = self
254            .instance_snapshot(cluster.id())
255            .expect("compute instance does not exist");
256        let optimizer_config = optimize::OptimizerConfig::from(self.catalog().system_config())
257            .override_from(&self.catalog.get_cluster(cluster.id()).config.features())
258            .override_from(&self.cluster_scoped_optimizer_overrides(cluster.id()))
259            .override_from(&explain_ctx);
260
261        if cluster.replicas().next().is_none() && explain_ctx.needs_cluster() {
262            return Err(AdapterError::NoClusterReplicasAvailable {
263                name: cluster.name.clone(),
264                is_managed: cluster.is_managed(),
265            });
266        }
267
268        let optimizer = match copy_to_ctx {
269            None => {
270                // Collect optimizer parameters specific to the peek::Optimizer.
271                let (_, view_id) = self.allocate_transient_id();
272                let (_, index_id) = self.allocate_transient_id();
273
274                // Build an optimizer for this SELECT.
275                optimize::PeekOptimizer::Select(optimize::peek::Optimizer::new(
276                    Arc::clone(&catalog),
277                    compute_instance,
278                    plan.finishing.clone(),
279                    view_id,
280                    index_id,
281                    optimizer_config,
282                    self.optimizer_metrics(),
283                ))
284            }
285            Some(mut copy_to_ctx) => {
286                // Getting the max worker count across replicas
287                // and using that value for the number of batches to
288                // divide the copy output into.
289                let worker_counts = cluster.replicas().map(|r| {
290                    let loc = &r.config.location;
291                    loc.workers().unwrap_or_else(|| loc.num_processes())
292                });
293                let max_worker_count = match worker_counts.max() {
294                    Some(count) => u64::cast_from(count),
295                    None => {
296                        return Err(AdapterError::NoClusterReplicasAvailable {
297                            name: cluster.name.clone(),
298                            is_managed: cluster.is_managed(),
299                        });
300                    }
301                };
302                copy_to_ctx.output_batch_count = Some(max_worker_count);
303                let (_, view_id) = self.allocate_transient_id();
304                // Build an optimizer for this COPY TO.
305                optimize::PeekOptimizer::CopyTo(optimize::copy_to::Optimizer::new(
306                    Arc::clone(&catalog),
307                    compute_instance,
308                    view_id,
309                    copy_to_ctx,
310                    optimizer_config,
311                    self.optimizer_metrics(),
312                ))
313            }
314        };
315
316        let target_replica_name = session.vars().cluster_replica();
317        let mut target_replica = target_replica_name
318            .map(|name| {
319                cluster
320                    .replica_id(name)
321                    .ok_or(AdapterError::UnknownClusterReplica {
322                        cluster_name: cluster.name.clone(),
323                        replica_name: name.to_string(),
324                    })
325            })
326            .transpose()?;
327
328        let source_ids = plan.source.depends_on();
329        let mut timeline_context = self
330            .catalog()
331            .validate_timeline_context(source_ids.iter().copied())?;
332        if matches!(timeline_context, TimelineContext::TimestampIndependent)
333            && plan.source.contains_temporal()
334        {
335            // If the source IDs are timestamp independent but the query contains temporal functions,
336            // then the timeline context needs to be upgraded to timestamp dependent. This is
337            // required because `source_ids` doesn't contain functions.
338            timeline_context = TimelineContext::TimestampDependent;
339        }
340
341        let notices = check_log_reads(
342            &catalog,
343            cluster,
344            &source_ids,
345            &mut target_replica,
346            session.vars(),
347        )?;
348        session.add_notices(notices);
349
350        let dependencies = source_ids
351            .iter()
352            .map(|id| self.catalog.resolve_item_id(id))
353            .collect();
354        let validity = PlanValidity::new(
355            &self.catalog,
356            dependencies,
357            Some(cluster.id()),
358            target_replica,
359            session.role_metadata().clone(),
360        );
361
362        Ok(PeekStage::LinearizeTimestamp(PeekStageLinearizeTimestamp {
363            validity,
364            plan,
365            max_query_result_size,
366            source_ids,
367            target_replica,
368            timeline_context,
369            optimizer,
370            explain_ctx,
371        }))
372    }
373
374    /// Possibly linearize a timestamp from a `TimestampOracle`.
375    #[instrument]
376    async fn peek_linearize_timestamp(
377        &self,
378        session: &Session,
379        PeekStageLinearizeTimestamp {
380            validity,
381            source_ids,
382            plan,
383            max_query_result_size,
384            target_replica,
385            timeline_context,
386            optimizer,
387            explain_ctx,
388        }: PeekStageLinearizeTimestamp,
389    ) -> Result<StageResult<Box<PeekStage>>, AdapterError> {
390        let oracle = self.linearized_read_ts_oracle(session, &timeline_context, &plan.when);
391
392        let build_stage = move |oracle_read_ts: Option<Timestamp>| PeekStageRealTimeRecency {
393            validity,
394            plan,
395            max_query_result_size,
396            source_ids,
397            target_replica,
398            timeline_context,
399            oracle_read_ts,
400            optimizer,
401            explain_ctx,
402        };
403
404        Ok(spawn_linearized_read_ts(
405            oracle,
406            "linearize timestamp",
407            move |oracle_read_ts| PeekStage::RealTimeRecency(build_stage(oracle_read_ts)),
408        ))
409    }
410
411    /// Determine a read timestamp and create appropriate read holds.
412    #[instrument]
413    fn peek_timestamp_read_hold(
414        &mut self,
415        session: &mut Session,
416        PeekStageTimestampReadHold {
417            mut validity,
418            plan,
419            max_query_result_size,
420            source_ids,
421            target_replica,
422            timeline_context,
423            oracle_read_ts,
424            real_time_recency_ts,
425            optimizer,
426            explain_ctx,
427        }: PeekStageTimestampReadHold,
428    ) -> Result<StageResult<Box<PeekStage>>, AdapterError> {
429        let cluster_id = optimizer.cluster_id();
430        let id_bundle = self
431            .dataflow_builder(cluster_id)
432            .sufficient_collections(source_ids.iter().copied());
433
434        // Although we have added `sources.depends_on()` to the validity already, also add the
435        // sufficient collections for safety.
436        let item_ids = id_bundle
437            .iter()
438            .map(|id| self.catalog().resolve_item_id(&id));
439        validity.extend_dependencies(self.catalog(), item_ids);
440
441        let determination = self.sequence_peek_timestamp(
442            session,
443            &plan.when,
444            cluster_id,
445            timeline_context,
446            oracle_read_ts,
447            &id_bundle,
448            &source_ids,
449            real_time_recency_ts,
450            (&explain_ctx).into(),
451        )?;
452
453        let stage = PeekStage::Optimize(PeekStageOptimize {
454            validity,
455            plan,
456            max_query_result_size,
457            source_ids,
458            id_bundle,
459            target_replica,
460            determination,
461            optimizer,
462            explain_ctx,
463        });
464        Ok(StageResult::Immediate(Box::new(stage)))
465    }
466
467    #[instrument]
468    async fn peek_optimize(
469        &self,
470        session: &Session,
471        PeekStageOptimize {
472            validity,
473            plan,
474            max_query_result_size,
475            source_ids,
476            id_bundle,
477            target_replica,
478            determination,
479            mut optimizer,
480            explain_ctx,
481        }: PeekStageOptimize,
482    ) -> Result<StageResult<Box<PeekStage>>, AdapterError> {
483        // Generate data structures that can be moved to another task where we will perform possibly
484        // expensive optimizations.
485        let timestamp_context = determination.timestamp_context.clone();
486        let stats = self
487            .statistics_oracle(session, &source_ids, &timestamp_context.antichain(), true)
488            .await
489            .unwrap_or_else(|_| Box::new(EmptyStatisticsOracle));
490        let session = session.meta();
491        let now = self.catalog().config().now.clone();
492        let catalog = self.owned_catalog();
493        let mut compute_instances = BTreeMap::new();
494        if explain_ctx.needs_plan_insights() {
495            // There's a chance for index skew (indexes were created/deleted between stages) from the
496            // original plan, but that seems acceptable for insights.
497            for cluster in self.catalog().user_clusters() {
498                let snapshot = self.instance_snapshot(cluster.id).expect("must exist");
499                compute_instances.insert(cluster.name.clone(), snapshot);
500            }
501        }
502
503        let span = Span::current();
504        Ok(StageResult::Handle(mz_ore::task::spawn_blocking(
505            || "optimize peek",
506            move || {
507                span.in_scope(|| {
508                    // Run the optimization pipeline. The dispatch guard borrows
509                    // `explain_ctx`, so we scope it in a block to drop it before
510                    // `explain_ctx` is inspected below. A failed optimization is
511                    // captured in `pipeline_result` rather than returned, so that
512                    // `EXPLAIN BROKEN` can still be handled in the match.
513                    let pipeline_result = {
514                        let _dispatch_guard = explain_ctx.dispatch_guard();
515
516                        let raw_expr = plan.source.clone();
517
518                        optimizer
519                            .optimize(raw_expr, timestamp_context.clone(), &session, stats)
520                            .map_err(AdapterError::from)
521                    };
522
523                    let optimization_finished_at = now();
524
525                    let stage = match pipeline_result {
526                        Ok(optimize::PeekGlobalLirPlan::Select(global_lir_plan)) => {
527                            let optimizer =
528                                optimizer.into_select().expect("a SELECT/EXPLAIN optimizer");
529                            // Enable fast path cluster calculation for slow path plans.
530                            let needs_plan_insights = explain_ctx.needs_plan_insights();
531                            // Disable anything that uses the optimizer if we only want the notice and
532                            // plan optimization took longer than the threshold. This is to prevent a
533                            // situation where optimizing takes a while and there a lots of clusters,
534                            // which would delay peek execution by the product of those.
535                            let opt_limit =
536                                PLAN_INSIGHTS_NOTICE_FAST_PATH_CLUSTERS_OPTIMIZE_DURATION
537                                    .get(catalog.system_config().dyncfgs());
538                            let target_instance =
539                                catalog.get_cluster(optimizer.cluster_id()).name.clone();
540                            let enable_re_optimize =
541                                !(matches!(explain_ctx, ExplainContext::PlanInsightsNotice(_))
542                                    && optimizer.duration() > opt_limit);
543                            let insights_ctx = needs_plan_insights
544                                .then(|| PlanInsightsContext {
545                                    stmt: plan
546                                        .select
547                                        .as_deref()
548                                        .map(Clone::clone)
549                                        .map(Statement::Select),
550                                    raw_expr: plan.source.clone(),
551                                    catalog,
552                                    compute_instances,
553                                    target_instance,
554                                    metrics: optimizer.metrics().clone(),
555                                    finishing: optimizer.finishing().clone(),
556                                    optimizer_config: optimizer.config().clone(),
557                                    session,
558                                    timestamp_context,
559                                    view_id: optimizer.select_id(),
560                                    index_id: optimizer.index_id(),
561                                    enable_re_optimize,
562                                })
563                                .map(Box::new);
564                            match explain_ctx {
565                                ExplainContext::Plan(explain_ctx) => {
566                                    let (_, df_meta, _) = global_lir_plan.unapply();
567                                    PeekStage::ExplainPlan(PeekStageExplainPlan {
568                                        validity,
569                                        optimizer,
570                                        df_meta,
571                                        explain_ctx,
572                                        insights_ctx,
573                                    })
574                                }
575                                ExplainContext::PlanInsightsNotice(optimizer_trace) => {
576                                    PeekStage::Finish(PeekStageFinish {
577                                        validity,
578                                        plan,
579                                        max_query_result_size,
580                                        id_bundle,
581                                        target_replica,
582                                        source_ids,
583                                        determination,
584                                        cluster_id: optimizer.cluster_id(),
585                                        finishing: optimizer.finishing().clone(),
586                                        plan_insights_optimizer_trace: Some(optimizer_trace),
587                                        global_lir_plan,
588                                        optimization_finished_at,
589                                        insights_ctx,
590                                    })
591                                }
592                                ExplainContext::None => PeekStage::Finish(PeekStageFinish {
593                                    validity,
594                                    plan,
595                                    max_query_result_size,
596                                    id_bundle,
597                                    target_replica,
598                                    source_ids,
599                                    determination,
600                                    cluster_id: optimizer.cluster_id(),
601                                    finishing: optimizer.finishing().clone(),
602                                    plan_insights_optimizer_trace: None,
603                                    global_lir_plan,
604                                    optimization_finished_at,
605                                    insights_ctx,
606                                }),
607                                ExplainContext::Pushdown => {
608                                    let (plan, _, _) = global_lir_plan.unapply();
609                                    let imports = match plan {
610                                        PeekPlan::SlowPath(plan) => plan
611                                            .desc
612                                            .source_imports
613                                            .into_iter()
614                                            .filter_map(|(id, import)| {
615                                                import.desc.arguments.operators.map(|mfp| (id, mfp))
616                                            })
617                                            .collect(),
618                                        PeekPlan::FastPath(_) => BTreeMap::default(),
619                                    };
620                                    PeekStage::ExplainPushdown(PeekStageExplainPushdown {
621                                        validity,
622                                        determination,
623                                        imports,
624                                    })
625                                }
626                            }
627                        }
628                        Ok(optimize::PeekGlobalLirPlan::CopyTo(global_lir_plan)) => {
629                            let optimizer = optimizer.into_copy_to().expect("a COPY TO optimizer");
630                            PeekStage::CopyToPreflight(PeekStageCopyTo {
631                                validity,
632                                optimizer,
633                                global_lir_plan,
634                                optimization_finished_at,
635                                target_replica,
636                                source_ids,
637                            })
638                        }
639                        // Internal optimizer errors are handled differently
640                        // depending on the caller.
641                        Err(err) => {
642                            let Some(optimizer) = optimizer.into_select() else {
643                                // In `COPY TO` contexts, immediately retire the
644                                // execution with the error.
645                                return Err(err);
646                            };
647                            let ExplainContext::Plan(explain_ctx) = explain_ctx else {
648                                // In `sequence_~` contexts, immediately retire the
649                                // execution with the error.
650                                return Err(err);
651                            };
652
653                            if explain_ctx.broken {
654                                // In `EXPLAIN BROKEN` contexts, just log the error
655                                // and move to the next stage with default
656                                // parameters.
657                                tracing::error!("error while handling EXPLAIN statement: {}", err);
658                                PeekStage::ExplainPlan(PeekStageExplainPlan {
659                                    validity,
660                                    optimizer,
661                                    df_meta: Default::default(),
662                                    explain_ctx,
663                                    insights_ctx: None,
664                                })
665                            } else {
666                                // In regular `EXPLAIN` contexts, immediately retire
667                                // the execution with the error.
668                                return Err(err);
669                            }
670                        }
671                    };
672                    Ok(Box::new(stage))
673                })
674            },
675        )))
676    }
677
678    #[instrument]
679    async fn peek_real_time_recency(
680        &self,
681        session: &Session,
682        PeekStageRealTimeRecency {
683            validity,
684            plan,
685            max_query_result_size,
686            source_ids,
687            target_replica,
688            timeline_context,
689            oracle_read_ts,
690            optimizer,
691            explain_ctx,
692        }: PeekStageRealTimeRecency,
693    ) -> Result<StageResult<Box<PeekStage>>, AdapterError> {
694        let fut = self
695            .determine_real_time_recent_timestamp_if_needed(session, source_ids.iter().copied())
696            .await?;
697
698        match fut {
699            Some(fut) => {
700                let catalog = Arc::clone(&self.catalog);
701                let span = Span::current();
702                Ok(StageResult::Handle(mz_ore::task::spawn(
703                    || "peek real time recency",
704                    async move {
705                        let real_time_recency_ts =
706                            Coordinator::await_real_time_recent_timestamp(catalog, fut).await?;
707                        let stage = PeekStage::TimestampReadHold(PeekStageTimestampReadHold {
708                            validity,
709                            plan,
710                            max_query_result_size,
711                            target_replica,
712                            timeline_context,
713                            source_ids,
714                            optimizer,
715                            explain_ctx,
716                            oracle_read_ts,
717                            real_time_recency_ts: Some(real_time_recency_ts),
718                        });
719                        Ok(Box::new(stage))
720                    }
721                    .instrument(span),
722                )))
723            }
724            None => Ok(StageResult::Immediate(Box::new(
725                PeekStage::TimestampReadHold(PeekStageTimestampReadHold {
726                    validity,
727                    plan,
728                    max_query_result_size,
729                    target_replica,
730                    timeline_context,
731                    source_ids,
732                    optimizer,
733                    explain_ctx,
734                    oracle_read_ts,
735                    real_time_recency_ts: None,
736                }),
737            ))),
738        }
739    }
740
741    #[instrument]
742    async fn peek_finish(
743        &mut self,
744        ctx: &mut ExecuteContext,
745        PeekStageFinish {
746            validity: _,
747            plan,
748            max_query_result_size,
749            id_bundle,
750            target_replica,
751            source_ids,
752            determination,
753            cluster_id,
754            finishing,
755            plan_insights_optimizer_trace,
756            global_lir_plan,
757            optimization_finished_at,
758            insights_ctx,
759        }: PeekStageFinish,
760    ) -> Result<StageResult<Box<PeekStage>>, AdapterError> {
761        if let Some(id) = ctx.extra.contents() {
762            self.record_statement_lifecycle_event(
763                &id,
764                &StatementLifecycleEvent::OptimizationFinished,
765                optimization_finished_at,
766            );
767        }
768
769        let session = ctx.session_mut();
770        let conn_id = session.conn_id().clone();
771
772        let (peek_plan, df_meta, typ) = global_lir_plan.unapply();
773        let source_arity = typ.arity();
774
775        emit_optimizer_notices(&*self.catalog, &*session, &df_meta.optimizer_notices);
776
777        if let Some(trace) = plan_insights_optimizer_trace {
778            let target_cluster = self.catalog().get_cluster(cluster_id);
779            let features = OptimizerFeatures::from(self.catalog().system_config())
780                .override_from(&target_cluster.config.features())
781                .override_from(&self.cluster_scoped_optimizer_overrides(cluster_id));
782            let insights = trace
783                .into_plan_insights(
784                    &features,
785                    &self.catalog().for_session(session),
786                    Some(plan.finishing),
787                    Some(target_cluster),
788                    df_meta,
789                    insights_ctx,
790                )
791                .await?;
792            session.add_notice(AdapterNotice::PlanInsights(insights));
793        }
794
795        let planned_peek = PlannedPeek {
796            plan: peek_plan,
797            determination: determination.clone(),
798            conn_id: conn_id.clone(),
799            intermediate_result_type: typ,
800            source_arity,
801            source_ids,
802        };
803
804        if let Some(transient_index_id) = match &planned_peek.plan {
805            peek::PeekPlan::FastPath(_) => None,
806            peek::PeekPlan::SlowPath(PeekDataflowPlan { id, .. }) => Some(id),
807        } {
808            if let Some(statement_logging_id) = ctx.extra.contents() {
809                self.set_transient_index_id(statement_logging_id, *transient_index_id);
810            }
811        }
812
813        if let Some(logging_id) = ctx.extra().contents() {
814            let watch_set = WatchSetCreation::new(
815                logging_id,
816                self.catalog.state(),
817                &id_bundle,
818                determination.timestamp_context.timestamp_or_default(),
819            );
820            self.install_peek_watch_sets(conn_id.clone(), watch_set).expect("the old peek sequencing re-verifies the dependencies' existence before installing the new watch sets");
821        }
822
823        let max_result_size = self.catalog().system_config().max_result_size();
824
825        // Implement the peek, and capture the response.
826        let resp = self
827            .implement_peek_plan(
828                ctx.extra_mut(),
829                planned_peek,
830                finishing,
831                cluster_id,
832                target_replica,
833                max_result_size,
834                max_query_result_size,
835            )
836            .await?;
837
838        if ctx.session().vars().emit_timestamp_notice() {
839            let explanation = self.explain_timestamp(
840                ctx.session().conn_id(),
841                ctx.session().pcx().wall_time,
842                cluster_id,
843                &id_bundle,
844                determination,
845            );
846            ctx.session()
847                .add_notice(AdapterNotice::QueryTimestamp { explanation });
848        }
849
850        let resp = match plan.copy_to {
851            None => resp,
852            Some(format) => ExecuteResponse::CopyTo {
853                format,
854                resp: Box::new(resp),
855            },
856        };
857        Ok(StageResult::Response(resp))
858    }
859
860    #[instrument]
861    async fn peek_copy_to_preflight(
862        &self,
863        copy_to: PeekStageCopyTo,
864    ) -> Result<StageResult<Box<PeekStage>>, AdapterError> {
865        let connection_context = self.connection_context().clone();
866        let enforce_external_addresses = mz_storage_types::dyncfgs::ENFORCE_EXTERNAL_ADDRESSES
867            .get(self.controller.storage.config().config_set());
868        Ok(StageResult::Handle(mz_ore::task::spawn(
869            || "peek copy to preflight",
870            async move {
871                let sinks = &copy_to.global_lir_plan.df_desc().sink_exports;
872                if sinks.len() != 1 {
873                    return Err(AdapterError::Internal(
874                        "expected exactly one copy to s3 sink".into(),
875                    ));
876                }
877                let (sink_id, sink_desc) = sinks
878                    .first_key_value()
879                    .expect("known to be exactly one copy to s3 sink");
880                match &sink_desc.connection {
881                    ComputeSinkConnection::CopyToS3Oneshot(conn) => {
882                        mz_storage_types::sinks::s3_oneshot_sink::preflight(
883                            connection_context,
884                            &conn.aws_connection,
885                            &conn.upload_info,
886                            conn.connection_id,
887                            *sink_id,
888                            enforce_external_addresses,
889                        )
890                        .await?;
891                        Ok(Box::new(PeekStage::CopyToDataflow(copy_to)))
892                    }
893                    _ => Err(AdapterError::Internal(
894                        "expected copy to s3 oneshot sink".into(),
895                    )),
896                }
897            },
898        )))
899    }
900
901    #[instrument]
902    async fn peek_copy_to_dataflow(
903        &mut self,
904        ctx: &ExecuteContext,
905        PeekStageCopyTo {
906            validity: _,
907            optimizer,
908            global_lir_plan,
909            optimization_finished_at,
910            target_replica,
911            source_ids,
912        }: PeekStageCopyTo,
913    ) -> Result<StageResult<Box<PeekStage>>, AdapterError> {
914        if let Some(id) = ctx.extra.contents() {
915            self.record_statement_lifecycle_event(
916                &id,
917                &StatementLifecycleEvent::OptimizationFinished,
918                optimization_finished_at,
919            );
920        }
921
922        let sink_id = global_lir_plan.sink_id();
923        let cluster_id = optimizer.cluster_id();
924
925        let (df_desc, df_meta) = global_lir_plan.unapply();
926
927        emit_optimizer_notices(&*self.catalog, ctx.session(), &df_meta.optimizer_notices);
928
929        // Callback for the active copy to.
930        let (tx, rx) = oneshot::channel();
931        let active_copy_to = ActiveCopyTo {
932            conn_id: ctx.session().conn_id().clone(),
933            tx,
934            cluster_id,
935            depends_on: source_ids,
936        };
937        // Add metadata for the new COPY TO. CopyTo returns a `ready` future, so it is safe to drop.
938        drop(self.add_active_compute_sink(sink_id, ActiveComputeSink::CopyTo(active_copy_to)));
939
940        // Ship dataflow.
941        self.ship_dataflow(df_desc, cluster_id, target_replica)
942            .await;
943
944        let span = Span::current();
945        Ok(StageResult::HandleRetire(mz_ore::task::spawn(
946            || "peek copy to dataflow",
947            async {
948                let res = rx.await;
949                match res {
950                    Ok(res) => res,
951                    Err(_) => Err(AdapterError::Internal("copy to sender dropped".into())),
952                }
953            }
954            .instrument(span),
955        )))
956    }
957
958    #[instrument]
959    async fn peek_explain_plan(
960        &self,
961        session: &Session,
962        PeekStageExplainPlan {
963            optimizer,
964            insights_ctx,
965            df_meta,
966            explain_ctx,
967            ..
968        }: PeekStageExplainPlan,
969    ) -> Result<StageResult<Box<PeekStage>>, AdapterError> {
970        let rows = super::super::explain_plan_inner(
971            session,
972            self.catalog(),
973            df_meta,
974            explain_ctx,
975            optimizer,
976            insights_ctx,
977        )
978        .await?;
979
980        Ok(StageResult::Response(Self::send_immediate_rows(rows)))
981    }
982
983    #[instrument]
984    async fn peek_explain_pushdown(
985        &self,
986        session: &Session,
987        stage: PeekStageExplainPushdown,
988    ) -> Result<StageResult<Box<PeekStage>>, AdapterError> {
989        let as_of = stage.determination.timestamp_context.antichain();
990        let mz_now = stage
991            .determination
992            .timestamp_context
993            .timestamp()
994            .map(|t| ResultSpec::value(Datum::MzTimestamp(*t)))
995            .unwrap_or_else(ResultSpec::value_all);
996        let fut = self
997            .explain_pushdown_future(session, as_of, mz_now, stage.imports)
998            .await;
999        let span = Span::current();
1000        Ok(StageResult::HandleRetire(mz_ore::task::spawn(
1001            || "peek explain pushdown",
1002            fut.instrument(span),
1003        )))
1004    }
1005
1006    /// Determines the query timestamp and acquires read holds on dependent sources
1007    /// if necessary.
1008    #[instrument]
1009    pub(super) fn sequence_peek_timestamp(
1010        &mut self,
1011        session: &mut Session,
1012        when: &QueryWhen,
1013        cluster_id: ClusterId,
1014        timeline_context: TimelineContext,
1015        oracle_read_ts: Option<Timestamp>,
1016        source_bundle: &CollectionIdBundle,
1017        source_ids: &BTreeSet<GlobalId>,
1018        real_time_recency_ts: Option<Timestamp>,
1019        requires_linearization: RequireLinearization,
1020    ) -> Result<TimestampDetermination, AdapterError> {
1021        let in_immediate_multi_stmt_txn = session.transaction().in_immediate_multi_stmt_txn(when);
1022        let timedomain_bundle;
1023
1024        // Fetch or generate a timestamp for this query and what the read holds would be if we need to set
1025        // them.
1026        let (determination, read_holds) = match session.get_transaction_timestamp_determination() {
1027            // Use the transaction's timestamp if it exists and this isn't an AS OF query.
1028            Some(
1029                determination @ TimestampDetermination {
1030                    timestamp_context: TimestampContext::TimelineTimestamp { .. },
1031                    ..
1032                },
1033            ) if in_immediate_multi_stmt_txn => (determination, None),
1034            _ => {
1035                let determine_bundle = if in_immediate_multi_stmt_txn {
1036                    // In a transaction, determine a timestamp that will be valid for anything in
1037                    // any schema referenced by the first query.
1038                    timedomain_bundle = timedomain_for(
1039                        self.catalog(),
1040                        &self.index_oracle(cluster_id),
1041                        source_ids,
1042                        &timeline_context,
1043                        session.conn_id(),
1044                        cluster_id,
1045                    )?;
1046
1047                    &timedomain_bundle
1048                } else {
1049                    // If not in a transaction, use the source.
1050                    source_bundle
1051                };
1052                let (determination, read_holds) = self.determine_timestamp(
1053                    session,
1054                    determine_bundle,
1055                    when,
1056                    cluster_id,
1057                    &timeline_context,
1058                    oracle_read_ts,
1059                    real_time_recency_ts,
1060                )?;
1061                // We only need read holds if the read depends on a timestamp.
1062                let read_holds = match determination.timestamp_context.timestamp() {
1063                    Some(_ts) => Some(read_holds),
1064                    None => {
1065                        // We don't need the read holds and shouldn't add them
1066                        // to the txn.
1067                        //
1068                        // TODO: Handle this within determine_timestamp.
1069                        drop(read_holds);
1070                        None
1071                    }
1072                };
1073                (determination, read_holds)
1074            }
1075        };
1076
1077        // Always either verify the current statement ids are within the existing
1078        // transaction's read hold set (timedomain), or create the read holds if this is the
1079        // first statement in a transaction (or this is a single statement transaction).
1080        // This must happen even if this is an `AS OF` query as well. There are steps after
1081        // this that happen off thread, so no matter the kind of statement or transaction,
1082        // we must acquire read holds here so they are held until the off-thread work
1083        // returns to the coordinator.
1084
1085        if let Some(txn_reads) = self.txn_read_holds.get(session.conn_id()) {
1086            // Find referenced ids not in the read hold. A reference could be caused by a
1087            // user specifying an object in a different schema than the first query. An
1088            // index could be caused by a CREATE INDEX after the transaction started.
1089            let allowed_id_bundle = txn_reads.id_bundle();
1090
1091            // We don't need the read holds that determine_timestamp acquired
1092            // for us.
1093            drop(read_holds);
1094
1095            let outside = source_bundle.difference(&allowed_id_bundle);
1096            // Queries without a timestamp and timeline can belong to any existing timedomain.
1097            if determination.timestamp_context.contains_timestamp() && !outside.is_empty() {
1098                let valid_names =
1099                    allowed_id_bundle.resolve_names(self.catalog(), session.conn_id());
1100                let invalid_names = outside.resolve_names(self.catalog(), session.conn_id());
1101                return Err(AdapterError::RelationOutsideTimeDomain {
1102                    relations: invalid_names,
1103                    names: valid_names,
1104                });
1105            }
1106        } else if let Some(read_holds) = read_holds {
1107            self.store_transaction_read_holds(session.conn_id().clone(), read_holds);
1108        }
1109
1110        // TODO: Checking for only `InTransaction` and not `Implied` (also `Started`?) seems
1111        // arbitrary and we don't recall why we did it (possibly an error!). Change this to always
1112        // set the transaction ops. Decide and document what our policy should be on AS OF queries.
1113        // Maybe they shouldn't be allowed in transactions at all because it's hard to explain
1114        // what's going on there. This should probably get a small design document.
1115
1116        // We only track the peeks in the session if the query doesn't use AS
1117        // OF or we're inside an explicit transaction. The latter case is
1118        // necessary to support PG's `BEGIN` semantics, whose behavior can
1119        // depend on whether or not reads have occurred in the txn.
1120        let mut transaction_determination = determination.clone();
1121        if when.is_transactional() {
1122            session.add_transaction_ops(TransactionOps::Peeks {
1123                determination: transaction_determination,
1124                cluster_id,
1125                requires_linearization,
1126            })?;
1127        } else if matches!(session.transaction(), &TransactionStatus::InTransaction(_)) {
1128            // If the query uses AS OF, then ignore the timestamp.
1129            transaction_determination.timestamp_context = TimestampContext::NoTimestamp;
1130            session.add_transaction_ops(TransactionOps::Peeks {
1131                determination: transaction_determination,
1132                cluster_id,
1133                requires_linearization,
1134            })?;
1135        };
1136
1137        Ok(determination)
1138    }
1139}