1use std::sync::Arc;
11
12use chrono::{DateTime, Utc};
13use itertools::Itertools;
14use mz_adapter_types::connection::ConnectionId;
15use mz_controller_types::ClusterId;
16use mz_expr::CollectionPlan;
17use mz_ore::instrument;
18use mz_repr::explain::ExplainFormat;
19use mz_repr::{Datum, Row, Timestamp};
20use mz_sql::plan::{self};
21use mz_sql::session::metadata::SessionMetadata;
22use tracing::{Instrument, Span};
23
24use crate::coord::sequencer::inner::{return_if_err, spawn_linearized_read_ts};
25use crate::coord::timestamp_selection::{TimestampDetermination, TimestampSource};
26use crate::coord::{
27 Coordinator, ExplainTimestampFinish, ExplainTimestampLinearizeTimestamp,
28 ExplainTimestampOptimize, ExplainTimestampRealTimeRecency, ExplainTimestampStage, Message,
29 PlanValidity, StageResult, Staged, TargetCluster,
30};
31use crate::error::AdapterError;
32use crate::optimize::{self, Optimize};
33use crate::session::{RequireLinearization, Session};
34use crate::{CollectionIdBundle, ExecuteContext, TimelineContext, TimestampExplanation};
35
36impl Staged for ExplainTimestampStage {
37 type Ctx = ExecuteContext;
38
39 fn validity(&mut self) -> &mut PlanValidity {
40 match self {
41 ExplainTimestampStage::Optimize(stage) => &mut stage.validity,
42 ExplainTimestampStage::RealTimeRecency(stage) => &mut stage.validity,
43 ExplainTimestampStage::LinearizeTimestamp(stage) => &mut stage.validity,
44 ExplainTimestampStage::Finish(stage) => &mut stage.validity,
45 }
46 }
47
48 async fn stage(
49 self,
50 coord: &mut Coordinator,
51 ctx: &mut ExecuteContext,
52 ) -> Result<StageResult<Box<Self>>, AdapterError> {
53 match self {
54 ExplainTimestampStage::Optimize(stage) => coord.explain_timestamp_optimize(stage),
55 ExplainTimestampStage::RealTimeRecency(stage) => {
56 coord
57 .explain_timestamp_real_time_recency(ctx.session(), stage)
58 .await
59 }
60 ExplainTimestampStage::LinearizeTimestamp(stage) => {
61 coord
62 .explain_timestamp_linearize_timestamp(ctx.session(), stage)
63 .await
64 }
65 ExplainTimestampStage::Finish(stage) => {
66 coord.explain_timestamp_finish(ctx.session_mut(), stage)
67 }
68 }
69 }
70
71 fn message(self, ctx: ExecuteContext, span: Span) -> Message {
72 Message::ExplainTimestampStageReady {
73 ctx,
74 span,
75 stage: self,
76 }
77 }
78
79 fn cancel_enabled(&self) -> bool {
80 true
81 }
82}
83
84impl Coordinator {
85 #[instrument]
86 pub async fn sequence_explain_timestamp(
87 &mut self,
88 ctx: ExecuteContext,
89 plan: plan::ExplainTimestampPlan,
90 target_cluster: TargetCluster,
91 ) {
92 let stage = return_if_err!(
93 self.explain_timestamp_validity(ctx.session(), plan, target_cluster),
94 ctx
95 );
96 self.sequence_staged(ctx, Span::current(), stage).await;
97 }
98
99 #[instrument]
100 fn explain_timestamp_validity(
101 &self,
102 session: &Session,
103 plan: plan::ExplainTimestampPlan,
104 target_cluster: TargetCluster,
105 ) -> Result<ExplainTimestampStage, AdapterError> {
106 let cluster = self
107 .catalog()
108 .resolve_target_cluster(target_cluster, session)?;
109 let cluster_id = cluster.id;
110 let dependencies = plan
111 .raw_plan
112 .depends_on()
113 .into_iter()
114 .map(|id| self.catalog().resolve_item_id(&id))
115 .collect();
116 let validity = PlanValidity::new(
117 self.catalog(),
118 dependencies,
119 Some(cluster_id),
120 None,
121 session.role_metadata().clone(),
122 );
123 Ok(ExplainTimestampStage::Optimize(ExplainTimestampOptimize {
124 validity,
125 plan,
126 cluster_id,
127 }))
128 }
129
130 #[instrument]
131 fn explain_timestamp_optimize(
132 &self,
133 ExplainTimestampOptimize {
134 validity,
135 plan,
136 cluster_id,
137 }: ExplainTimestampOptimize,
138 ) -> Result<StageResult<Box<ExplainTimestampStage>>, AdapterError> {
139 let optimizer_config = optimize::OptimizerConfig::from(self.catalog().system_config());
141
142 let mut optimizer = optimize::view::Optimizer::new(optimizer_config, None);
143
144 let span = Span::current();
145 Ok(StageResult::Handle(mz_ore::task::spawn_blocking(
146 || "optimize explain timestamp",
147 move || {
148 span.in_scope(|| {
149 let plan::ExplainTimestampPlan {
150 format,
151 raw_plan,
152 when,
153 } = plan;
154
155 let optimized_plan = optimizer.optimize(raw_plan)?;
157
158 let stage =
159 ExplainTimestampStage::RealTimeRecency(ExplainTimestampRealTimeRecency {
160 validity,
161 format,
162 optimized_plan,
163 cluster_id,
164 when,
165 });
166 Ok(Box::new(stage))
167 })
168 },
169 )))
170 }
171
172 #[instrument]
173 async fn explain_timestamp_real_time_recency(
174 &self,
175 session: &Session,
176 ExplainTimestampRealTimeRecency {
177 validity,
178 format,
179 optimized_plan,
180 cluster_id,
181 when,
182 }: ExplainTimestampRealTimeRecency,
183 ) -> Result<StageResult<Box<ExplainTimestampStage>>, AdapterError> {
184 let source_ids = optimized_plan.depends_on();
185 let fut = self
186 .determine_real_time_recent_timestamp_if_needed(session, source_ids.iter().copied())
187 .await?;
188
189 match fut {
190 Some(fut) => {
191 let catalog = Arc::clone(&self.catalog);
192 let span = Span::current();
193 Ok(StageResult::Handle(mz_ore::task::spawn(
194 || "explain timestamp real time recency",
195 async move {
196 let real_time_recency_ts =
197 Coordinator::await_real_time_recent_timestamp(catalog, fut).await?;
198 let stage = ExplainTimestampStage::LinearizeTimestamp(
199 ExplainTimestampLinearizeTimestamp {
200 validity,
201 format,
202 optimized_plan,
203 cluster_id,
204 source_ids,
205 when,
206 real_time_recency_ts: Some(real_time_recency_ts),
207 },
208 );
209 Ok(Box::new(stage))
210 }
211 .instrument(span),
212 )))
213 }
214 None => Ok(StageResult::Immediate(Box::new(
215 ExplainTimestampStage::LinearizeTimestamp(ExplainTimestampLinearizeTimestamp {
216 validity,
217 format,
218 optimized_plan,
219 cluster_id,
220 source_ids,
221 when,
222 real_time_recency_ts: None,
223 }),
224 ))),
225 }
226 }
227
228 #[instrument]
231 async fn explain_timestamp_linearize_timestamp(
232 &self,
233 session: &Session,
234 ExplainTimestampLinearizeTimestamp {
235 validity,
236 format,
237 optimized_plan,
238 cluster_id,
239 source_ids,
240 when,
241 real_time_recency_ts,
242 }: ExplainTimestampLinearizeTimestamp,
243 ) -> Result<StageResult<Box<ExplainTimestampStage>>, AdapterError> {
244 let mut timeline_context = self
245 .catalog()
246 .validate_timeline_context(source_ids.iter().copied())?;
247 if matches!(timeline_context, TimelineContext::TimestampIndependent)
248 && optimized_plan.contains_temporal()
249 {
250 timeline_context = TimelineContext::TimestampDependent;
254 }
255
256 let oracle = self.linearized_read_ts_oracle(session, &timeline_context, &when);
257
258 let build_stage = move |oracle_read_ts: Option<Timestamp>| {
259 ExplainTimestampStage::Finish(ExplainTimestampFinish {
260 validity,
261 format,
262 cluster_id,
263 source_ids,
264 when,
265 real_time_recency_ts,
266 timeline_context,
267 oracle_read_ts,
268 })
269 };
270
271 Ok(spawn_linearized_read_ts(
272 oracle,
273 "explain timestamp linearize timestamp",
274 build_stage,
275 ))
276 }
277
278 pub(crate) fn explain_timestamp(
279 &self,
280 conn_id: &ConnectionId,
281 session_wall_time: DateTime<Utc>,
282 cluster_id: ClusterId,
283 id_bundle: &CollectionIdBundle,
284 determination: TimestampDetermination,
285 ) -> TimestampExplanation {
286 let mut sources = Vec::new();
287 {
288 let storage_ids = id_bundle.storage_ids.iter().cloned().collect_vec();
289 let frontiers = self
290 .controller
291 .storage
292 .collections_frontiers(storage_ids)
293 .expect("missing collection");
294
295 for (id, since, upper) in frontiers {
296 let name = self
297 .catalog()
298 .try_get_entry_by_global_id(&id)
299 .map(|item| item.name())
300 .map(|name| {
301 self.catalog()
302 .resolve_full_name(name, Some(conn_id))
303 .to_string()
304 })
305 .unwrap_or_else(|| id.to_string());
306 sources.push(TimestampSource {
307 name: format!("{name} ({id}, storage)"),
308 read_frontier: since.elements().to_vec(),
309 write_frontier: upper.elements().to_vec(),
310 });
311 }
312 }
313 {
314 if let Some(compute_ids) = id_bundle.compute_ids.get(&cluster_id) {
315 let catalog = self.catalog();
316 for id in compute_ids {
317 let frontiers = self
318 .controller
319 .compute
320 .collection_frontiers(*id, Some(cluster_id))
321 .expect("id does not exist");
322 let name = catalog
323 .try_get_entry_by_global_id(id)
324 .map(|item| item.name())
325 .map(|name| catalog.resolve_full_name(name, Some(conn_id)).to_string())
326 .unwrap_or_else(|| id.to_string());
327 sources.push(TimestampSource {
328 name: format!("{name} ({id}, compute)"),
329 read_frontier: frontiers.read_frontier.to_vec(),
330 write_frontier: frontiers.write_frontier.to_vec(),
331 });
332 }
333 }
334 }
335 let respond_immediately = determination.respond_immediately();
336 TimestampExplanation {
337 determination,
338 sources,
339 session_wall_time,
340 respond_immediately,
341 }
342 }
343
344 #[instrument]
345 fn explain_timestamp_finish(
346 &mut self,
347 session: &mut Session,
348 ExplainTimestampFinish {
349 validity: _,
350 format,
351 cluster_id,
352 source_ids,
353 when,
354 real_time_recency_ts,
355 timeline_context,
356 oracle_read_ts,
357 }: ExplainTimestampFinish,
358 ) -> Result<StageResult<Box<ExplainTimestampStage>>, AdapterError> {
359 let id_bundle = self
360 .index_oracle(cluster_id)
361 .sufficient_collections(source_ids.iter().copied());
362
363 let is_json = match format {
364 ExplainFormat::Text => false,
365 ExplainFormat::Json => true,
366 ExplainFormat::Dot => {
367 return Err(AdapterError::Unsupported("EXPLAIN TIMESTAMP AS DOT"));
368 }
369 };
370
371 let determination = self.sequence_peek_timestamp(
372 session,
373 &when,
374 cluster_id,
375 timeline_context,
376 oracle_read_ts,
377 &id_bundle,
378 &source_ids,
379 real_time_recency_ts,
380 RequireLinearization::NotRequired,
381 )?;
382 let explanation = self.explain_timestamp(
383 session.conn_id(),
384 session.pcx().wall_time,
385 cluster_id,
386 &id_bundle,
387 determination,
388 );
389
390 let s = if is_json {
391 serde_json::to_string_pretty(&explanation).expect("failed to serialize explanation")
392 } else {
393 explanation.to_string()
394 };
395 let rows = vec![Row::pack_slice(&[Datum::from(s.as_str())])];
396 Ok(StageResult::Response(Self::send_immediate_rows(rows)))
397 }
398}