1use std::collections::BTreeMap;
13use std::fmt::Formatter;
14use std::time::Duration;
15
16use mz_ore::str::{Indent, IndentLike, separated};
17use mz_repr::GlobalId;
18use mz_repr::explain::ExplainError::LinearChainsPlusRecursive;
19use mz_repr::explain::text::DisplayText;
20use mz_repr::explain::{
21 AnnotatedPlan, Explain, ExplainConfig, ExplainError, ExprHumanizer, ScalarOps,
22 UnsupportedFormat, UsedIndexes,
23};
24use mz_repr::optimize::OptimizerFeatures;
25
26use crate::interpret::{Interpreter, MfpEval, Trace};
27use crate::visit::Visit;
28use crate::{
29 AccessStrategy, Id, LocalId, MapFilterProject, MirRelationExpr, MirScalarExpr, RowSetFinishing,
30};
31
32pub use crate::explain::text::{
33 HumanizedExplain, HumanizedExpr, HumanizedNotice, HumanizerMode, fmt_text_constant_rows,
34};
35
36mod json;
37mod text;
38
39#[derive(Debug)]
42pub struct ExplainContext<'a> {
43 pub config: &'a ExplainConfig,
44 pub features: &'a OptimizerFeatures,
45 pub humanizer: &'a dyn ExprHumanizer,
46 pub cardinality_stats: BTreeMap<GlobalId, usize>,
47 pub used_indexes: UsedIndexes,
48 pub finishing: Option<RowSetFinishing>,
49 pub duration: Duration,
50 pub target_cluster: Option<&'a str>,
52 pub optimizer_notices: Vec<String>,
56}
57
58#[allow(missing_debug_implementations)]
63pub struct ExplainSinglePlan<'a, T> {
64 pub context: &'a ExplainContext<'a>,
65 pub plan: AnnotatedPlan<'a, T>,
66}
67
68#[allow(missing_debug_implementations)]
71pub struct PushdownInfo<'a> {
72 pub pushdown: Vec<&'a MirScalarExpr>,
74}
75
76impl<'a, C, M> DisplayText<C> for HumanizedExpr<'a, PushdownInfo<'a>, M>
77where
78 C: AsMut<Indent>,
79 M: HumanizerMode,
80{
81 fn fmt_text(&self, f: &mut Formatter<'_>, ctx: &mut C) -> std::fmt::Result {
82 let PushdownInfo { pushdown } = self.expr;
83
84 if !pushdown.is_empty() {
85 let pushdown = pushdown.iter().map(|e| self.mode.expr(*e, self.cols));
86 let pushdown = separated(" AND ", pushdown);
87 writeln!(f, "{}pushdown=({})", ctx.as_mut(), pushdown)?;
88 }
89
90 Ok(())
91 }
92}
93
94#[allow(missing_debug_implementations)]
95pub struct ExplainSource<'a> {
96 pub id: GlobalId,
97 pub op: Option<&'a MapFilterProject>,
98 pub pushdown_info: Option<PushdownInfo<'a>>,
99}
100
101impl<'a> ExplainSource<'a> {
102 pub fn new(
103 id: GlobalId,
104 op: Option<&'a MapFilterProject>,
105 filter_pushdown: bool,
106 ) -> ExplainSource<'a> {
107 let pushdown_info = if filter_pushdown {
108 op.map(|op| {
109 let mfp_mapped = MfpEval::new(&Trace, op.input_arity, &op.expressions);
110 let pushdown = op
111 .predicates
112 .iter()
113 .filter(|(_, e)| mfp_mapped.expr(e).pushdownable())
114 .map(|(_, e)| e)
115 .collect();
116 PushdownInfo { pushdown }
117 })
118 } else {
119 None
120 };
121
122 ExplainSource {
123 id,
124 op,
125 pushdown_info,
126 }
127 }
128
129 #[inline]
130 pub fn is_identity(&self) -> bool {
131 match self.op {
132 Some(op) => op.is_identity(),
133 None => false,
134 }
135 }
136}
137
138impl<'a, 'h, C, M> DisplayText<C> for HumanizedExpr<'a, ExplainSource<'a>, M>
139where
140 C: AsMut<Indent> + AsRef<&'h dyn ExprHumanizer>,
141 M: HumanizerMode,
142{
143 fn fmt_text(&self, f: &mut std::fmt::Formatter<'_>, ctx: &mut C) -> std::fmt::Result {
144 let id = ctx
145 .as_ref()
146 .humanize_id(self.expr.id)
147 .unwrap_or_else(|| self.expr.id.to_string());
148 writeln!(f, "{}Source {}", ctx.as_mut(), id)?;
149 ctx.indented(|ctx| {
150 if let Some(op) = self.expr.op {
151 self.child(op).fmt_text(f, ctx)?;
152 }
153 if let Some(pushdown_info) = &self.expr.pushdown_info {
154 self.child(pushdown_info).fmt_text(f, ctx)?;
155 }
156 Ok(())
157 })
158 }
159}
160
161#[allow(missing_debug_implementations)]
166pub struct ExplainMultiPlan<'a, T> {
167 pub context: &'a ExplainContext<'a>,
168 pub sources: Vec<ExplainSource<'a>>,
171 pub plans: Vec<(String, AnnotatedPlan<'a, T>)>,
173}
174
175impl<'a> Explain<'a> for MirRelationExpr {
176 type Context = ExplainContext<'a>;
177
178 type Text = ExplainSinglePlan<'a, MirRelationExpr>;
179
180 type VerboseText = ExplainSinglePlan<'a, MirRelationExpr>;
181
182 type Json = ExplainSinglePlan<'a, MirRelationExpr>;
183
184 type Dot = UnsupportedFormat;
185
186 fn explain_text(&'a mut self, context: &'a Self::Context) -> Result<Self::Text, ExplainError> {
187 self.as_explain_single_plan(context)
188 }
189
190 fn explain_verbose_text(
191 &'a mut self,
192 context: &'a Self::Context,
193 ) -> Result<Self::VerboseText, ExplainError> {
194 self.as_explain_single_plan(context)
195 }
196
197 fn explain_json(&'a mut self, context: &'a Self::Context) -> Result<Self::Json, ExplainError> {
198 self.as_explain_single_plan(context)
199 }
200}
201
202impl<'a> MirRelationExpr {
203 fn as_explain_single_plan(
204 &'a mut self,
205 context: &'a ExplainContext<'a>,
206 ) -> Result<ExplainSinglePlan<'a, MirRelationExpr>, ExplainError> {
207 if context.config.linear_chains {
210 enforce_linear_chains(self)?;
211 };
212
213 let plan = AnnotatedPlan {
214 plan: self,
215 annotations: BTreeMap::default(),
216 };
217
218 Ok(ExplainSinglePlan { context, plan })
219 }
220}
221
222pub fn enforce_linear_chains(expr: &mut MirRelationExpr) -> Result<(), ExplainError> {
230 use MirRelationExpr::{Constant, Get, Join, Union};
231
232 if expr.is_recursive() {
233 return Err(LinearChainsPlusRecursive);
236 }
237
238 let mut id_gen = id_gen(expr).peekable();
240
241 let mut wrap_in_let = |input: &mut MirRelationExpr| {
242 match input {
243 Constant { .. } | Get { .. } => (),
244 input => {
245 let id = id_gen.next().unwrap();
251 let value = input.take_safely(None);
252 let mut binding = MirRelationExpr::Let {
254 id,
255 value: Box::new(value),
256 body: Box::new(Get {
257 id: Id::Local(id.clone()),
258 typ: input.typ(),
259 access_strategy: AccessStrategy::UnknownOrLocal,
260 }),
261 };
262 std::mem::swap(input, &mut binding);
264 }
265 }
266 };
267
268 expr.try_visit_mut_post(&mut |expr: &mut MirRelationExpr| {
269 match expr {
270 Join { inputs, .. } => {
271 for input in inputs {
272 wrap_in_let(input);
273 }
274 }
275 Union { base, inputs } => {
276 wrap_in_let(base);
277 for input in inputs {
278 wrap_in_let(input);
279 }
280 }
281 _ => (),
282 }
283 Ok(())
284 })
285}
286
287fn id_gen(expr: &MirRelationExpr) -> impl Iterator<Item = LocalId> + use<> {
290 let mut max_id = 0_u64;
291
292 expr.visit_pre(|expr| {
293 match expr {
294 MirRelationExpr::Let { id, .. } => max_id = std::cmp::max(max_id, id.into()),
295 _ => (),
296 };
297 });
298
299 (max_id + 1..).map(LocalId::new)
300}
301
302impl ScalarOps for MirScalarExpr {
303 fn match_col_ref(&self) -> Option<usize> {
304 match self {
305 MirScalarExpr::Column(c) => Some(*c),
306 _ => None,
307 }
308 }
309
310 fn references(&self, column: usize) -> bool {
311 match self {
312 MirScalarExpr::Column(c) => *c == column,
313 _ => false,
314 }
315 }
316}