1use std::borrow::Cow;
13use std::collections::BTreeMap;
14use std::fmt::Formatter;
15use std::time::Duration;
16
17use mz_ore::str::{Indent, IndentLike, separated};
18use mz_repr::GlobalId;
19use mz_repr::explain::ExplainError::LinearChainsPlusRecursive;
20use mz_repr::explain::text::DisplayText;
21use mz_repr::explain::{
22 AnnotatedPlan, Explain, ExplainConfig, ExplainError, ExprHumanizer, ScalarOps,
23 UnsupportedFormat, UsedIndexes,
24};
25use mz_repr::optimize::OptimizerFeatures;
26
27use crate::interpret::{Interpretable, Interpreter, MfpEval, Trace};
28use crate::scalar::optimizable::OptimizableExpr;
29use crate::visit::Visit;
30use crate::{
31 AccessStrategy, BinaryFunc, Id, LocalId, MapFilterProject, MfpPlan, MirRelationExpr,
32 MirScalarExpr, RowSetFinishing, UnmaterializableFunc, func,
33};
34
35pub use crate::explain::text::{
36 HumanizeDisplay, HumanizedExplain, HumanizedExpr, HumanizedNotice, HumanizerMode,
37 fmt_text_constant_rows,
38};
39
40mod json;
41mod text;
42
43#[derive(Debug)]
46pub struct ExplainContext<'a> {
47 pub config: &'a ExplainConfig,
48 pub features: &'a OptimizerFeatures,
49 pub humanizer: &'a dyn ExprHumanizer,
50 pub cardinality_stats: BTreeMap<GlobalId, usize>,
51 pub used_indexes: UsedIndexes,
52 pub finishing: Option<RowSetFinishing>,
53 pub duration: Duration,
54 pub target_cluster: Option<&'a str>,
56 pub optimizer_notices: Vec<String>,
60}
61
62#[allow(missing_debug_implementations)]
67pub struct ExplainSinglePlan<'a, T> {
68 pub context: &'a ExplainContext<'a>,
70 pub plan: AnnotatedPlan<'a, T>,
72}
73
74#[allow(missing_debug_implementations)]
79pub struct PushdownInfo<E = MirScalarExpr> {
80 pub pushdown: Vec<E>,
82 pub lower_bounds: Vec<E>,
84 pub upper_bounds: Vec<E>,
86}
87
88impl<'a, C, M, E> DisplayText<C> for HumanizedExpr<'a, PushdownInfo<E>, M>
89where
90 C: AsMut<Indent>,
91 M: HumanizerMode,
92 E: HumanizeDisplay,
93{
94 fn fmt_text(&self, f: &mut Formatter<'_>, ctx: &mut C) -> std::fmt::Result {
95 let PushdownInfo {
96 pushdown,
97 lower_bounds,
98 upper_bounds,
99 } = self.expr;
100
101 if !pushdown.is_empty() {
102 let pushdown = pushdown.iter().map(|e| self.mode.expr(e, self.cols));
103 let pushdown = separated(" AND ", pushdown);
104 writeln!(f, "{}pushdown=({})", ctx.as_mut(), pushdown)?;
105 }
106 if !lower_bounds.is_empty() || !upper_bounds.is_empty() {
107 write!(f, "{}pushdown ", ctx.as_mut())?;
108 fmt_temporal_filter(lower_bounds, upper_bounds, self, f)?;
109 }
110
111 Ok(())
112 }
113}
114
115pub fn fmt_temporal_filter<'a, T, M, E>(
119 lower_bounds: &[E],
120 upper_bounds: &[E],
121 humanizer: &HumanizedExpr<'a, T, M>,
122 f: &mut Formatter<'_>,
123) -> std::fmt::Result
124where
125 M: HumanizerMode,
126 E: HumanizeDisplay,
127{
128 write!(f, "temporal filter=")?;
129 if !lower_bounds.is_empty() {
130 let lowers = lower_bounds.iter().map(|b| humanizer.child(b));
131 write!(f, "{} <= ", separated(", ", lowers))?;
132 }
133 write!(f, "mz_now()")?;
134 if !upper_bounds.is_empty() {
135 let uppers = upper_bounds.iter().map(|b| humanizer.child(b));
136 write!(f, " < {}", separated(", ", uppers))?;
137 }
138 writeln!(f)
139}
140
141pub trait SourceOperators: Clone + serde::Serialize {
145 type Expr: HumanizeDisplay + serde::Serialize;
147
148 fn is_identity(&self) -> bool;
150
151 fn appended_columns(&self) -> usize;
153
154 fn pushdown_info(&self) -> PushdownInfo<Self::Expr>;
156
157 fn fmt_text<M: HumanizerMode, C: AsMut<Indent>>(
159 humanized: &HumanizedExpr<'_, Self, M>,
160 f: &mut Formatter<'_>,
161 ctx: &mut C,
162 ) -> std::fmt::Result;
163}
164
165impl<E> SourceOperators for MapFilterProject<E>
166where
167 E: OptimizableExpr + HumanizeDisplay + Interpretable + serde::Serialize,
168{
169 type Expr = E;
170
171 fn is_identity(&self) -> bool {
172 MapFilterProject::is_identity(self)
173 }
174
175 fn appended_columns(&self) -> usize {
176 self.expressions.len()
177 }
178
179 fn pushdown_info(&self) -> PushdownInfo<E> {
180 let mfp_mapped = MfpEval::new(&Trace, self.input_arity, &self.expressions);
181 let pushdown = self
182 .predicates
183 .iter()
184 .filter(|(_, e)| mfp_mapped.expr(e).pushdownable())
185 .map(|(_, e)| e.clone())
186 .collect();
187 PushdownInfo {
188 pushdown,
189 lower_bounds: Vec::new(),
190 upper_bounds: Vec::new(),
191 }
192 }
193
194 fn fmt_text<M: HumanizerMode, C: AsMut<Indent>>(
195 humanized: &HumanizedExpr<'_, Self, M>,
196 f: &mut Formatter<'_>,
197 ctx: &mut C,
198 ) -> std::fmt::Result {
199 DisplayText::fmt_text(humanized, f, ctx)
200 }
201}
202
203impl<E> SourceOperators for MfpPlan<E>
204where
205 E: OptimizableExpr + HumanizeDisplay + Interpretable + serde::Serialize,
206{
207 type Expr = E;
208
209 fn is_identity(&self) -> bool {
210 MfpPlan::is_identity(self)
211 }
212
213 fn appended_columns(&self) -> usize {
214 self.safe_mfp().expressions.len()
215 }
216
217 fn pushdown_info(&self) -> PushdownInfo<E> {
218 let (safe, lower_bounds, upper_bounds) = self.as_parts();
219 let mfp_mapped = MfpEval::new(&Trace, safe.input_arity, &safe.expressions);
220 let pushdown = safe
221 .predicates
222 .iter()
223 .filter(|(_, e)| mfp_mapped.expr(e).pushdownable())
224 .map(|(_, e)| e.clone())
225 .collect();
226 let mz_now = mfp_mapped.unmaterializable(&UnmaterializableFunc::MzNow);
229 let pushdownable = |bound: &E, func: BinaryFunc| {
230 mfp_mapped
231 .binary(&func, mfp_mapped.expr(bound), mz_now.clone())
232 .pushdownable()
233 };
234 let lower_bounds = lower_bounds
235 .iter()
236 .filter(|b| pushdownable(b, BinaryFunc::Lte(func::Lte)))
237 .cloned()
238 .collect();
239 let upper_bounds = upper_bounds
240 .iter()
241 .filter(|b| pushdownable(b, BinaryFunc::Gte(func::Gte)))
242 .cloned()
243 .collect();
244 PushdownInfo {
245 pushdown,
246 lower_bounds,
247 upper_bounds,
248 }
249 }
250
251 fn fmt_text<M: HumanizerMode, C: AsMut<Indent>>(
252 humanized: &HumanizedExpr<'_, Self, M>,
253 f: &mut Formatter<'_>,
254 ctx: &mut C,
255 ) -> std::fmt::Result {
256 let (safe, lower_bounds, upper_bounds) = humanized.expr.as_parts();
257 DisplayText::fmt_text(&humanized.child(&**safe), f, ctx)?;
258 if !lower_bounds.is_empty() || !upper_bounds.is_empty() {
259 write!(f, "{}", ctx.as_mut())?;
260 fmt_temporal_filter(lower_bounds, upper_bounds, humanized, f)?;
261 }
262 Ok(())
263 }
264}
265
266#[allow(missing_debug_implementations)]
267pub struct ExplainSource<'a, O: SourceOperators = MapFilterProject> {
268 pub id: GlobalId,
269 pub op: Option<Cow<'a, O>>,
270 pub pushdown_info: Option<PushdownInfo<O::Expr>>,
271}
272
273impl<'a, O: SourceOperators> ExplainSource<'a, O> {
274 pub fn new(
275 id: GlobalId,
276 op: Option<Cow<'a, O>>,
277 filter_pushdown: bool,
278 ) -> ExplainSource<'a, O> {
279 let pushdown_info = if filter_pushdown {
280 op.as_deref().map(SourceOperators::pushdown_info)
281 } else {
282 None
283 };
284
285 ExplainSource {
286 id,
287 op,
288 pushdown_info,
289 }
290 }
291
292 #[inline]
293 pub fn is_identity(&self) -> bool {
294 match &self.op {
295 Some(op) => op.is_identity(),
296 None => false,
297 }
298 }
299}
300
301impl<'a, 'h, C, M, O> DisplayText<C> for HumanizedExpr<'a, ExplainSource<'a, O>, M>
302where
303 C: AsMut<Indent> + AsRef<&'h dyn ExprHumanizer>,
304 M: HumanizerMode,
305 O: SourceOperators,
306{
307 fn fmt_text(&self, f: &mut std::fmt::Formatter<'_>, ctx: &mut C) -> std::fmt::Result {
308 let id = ctx
309 .as_ref()
310 .humanize_id(self.expr.id)
311 .unwrap_or_else(|| self.expr.id.to_string());
312 writeln!(f, "{}Source {}", ctx.as_mut(), id)?;
313 ctx.indented(|ctx| {
314 if let Some(op) = self.expr.op.as_deref() {
315 O::fmt_text(&self.child(op), f, ctx)?;
316 }
317 if let Some(pushdown_info) = &self.expr.pushdown_info {
318 self.child(pushdown_info).fmt_text(f, ctx)?;
319 }
320 Ok(())
321 })
322 }
323}
324
325#[allow(missing_debug_implementations)]
330pub struct ExplainMultiPlan<'a, T, O: SourceOperators = MapFilterProject> {
331 pub context: &'a ExplainContext<'a>,
332 pub sources: Vec<ExplainSource<'a, O>>,
335 pub plans: Vec<(String, AnnotatedPlan<'a, T>)>,
337}
338
339impl<'a> Explain<'a> for MirRelationExpr {
340 type Context = ExplainContext<'a>;
341
342 type Text = ExplainSinglePlan<'a, MirRelationExpr>;
343
344 type Json = ExplainSinglePlan<'a, MirRelationExpr>;
345
346 type Dot = UnsupportedFormat;
347
348 fn explain_text(&'a mut self, context: &'a Self::Context) -> Result<Self::Text, ExplainError> {
349 self.as_explain_single_plan(context)
350 }
351
352 fn explain_json(&'a mut self, context: &'a Self::Context) -> Result<Self::Json, ExplainError> {
353 self.as_explain_single_plan(context)
354 }
355}
356
357impl<'a> MirRelationExpr {
358 fn as_explain_single_plan(
359 &'a mut self,
360 context: &'a ExplainContext<'a>,
361 ) -> Result<ExplainSinglePlan<'a, MirRelationExpr>, ExplainError> {
362 if context.config.linear_chains {
365 enforce_linear_chains(self)?;
366 };
367
368 let plan = AnnotatedPlan {
369 plan: self,
370 annotations: BTreeMap::default(),
371 };
372
373 Ok(ExplainSinglePlan { context, plan })
374 }
375}
376
377pub fn enforce_linear_chains(expr: &mut MirRelationExpr) -> Result<(), ExplainError> {
385 use MirRelationExpr::{Constant, Get, Join, Union};
386
387 if expr.is_recursive() {
388 return Err(LinearChainsPlusRecursive);
391 }
392
393 let mut id_gen = id_gen(expr).peekable();
395
396 let mut wrap_in_let = |input: &mut MirRelationExpr| {
397 match input {
398 Constant { .. } | Get { .. } => (),
399 input => {
400 let id = id_gen.next().unwrap();
406 let value = input.take_safely(None);
407 let mut binding = MirRelationExpr::Let {
409 id,
410 value: Box::new(value),
411 body: Box::new(Get {
412 id: Id::Local(id.clone()),
413 typ: input.typ(),
414 access_strategy: AccessStrategy::UnknownOrLocal,
415 }),
416 };
417 std::mem::swap(input, &mut binding);
419 }
420 }
421 };
422
423 expr.try_visit_mut_post(&mut |expr: &mut MirRelationExpr| {
424 match expr {
425 Join { inputs, .. } => {
426 for input in inputs {
427 wrap_in_let(input);
428 }
429 }
430 Union { base, inputs } => {
431 wrap_in_let(base);
432 for input in inputs {
433 wrap_in_let(input);
434 }
435 }
436 _ => (),
437 }
438 Ok(())
439 })
440}
441
442fn id_gen(expr: &MirRelationExpr) -> impl Iterator<Item = LocalId> + use<> {
445 let mut max_id = 0_u64;
446
447 expr.visit_pre(|expr| {
448 match expr {
449 MirRelationExpr::Let { id, .. } => max_id = std::cmp::max(max_id, id.into()),
450 _ => (),
451 };
452 });
453
454 (max_id + 1..).map(LocalId::new)
455}
456
457impl ScalarOps for MirScalarExpr {
458 fn match_col_ref(&self) -> Option<usize> {
459 match self {
460 MirScalarExpr::Column(c, _name) => Some(*c),
461 _ => None,
462 }
463 }
464
465 fn references(&self, column: usize) -> bool {
466 match self {
467 MirScalarExpr::Column(c, _name) => *c == column,
468 _ => false,
469 }
470 }
471}