1use itertools::Itertools;
22use mz_ore::treat_as_equal::TreatAsEqual;
23use std::fmt;
24
25use mz_expr::explain::{HumanizedExplain, HumanizerMode, fmt_text_constant_rows};
26use mz_expr::virtual_syntax::{AlgExcept, Except};
27use mz_expr::{Id, WindowFrame};
28use mz_ore::error::ErrorExt;
29use mz_ore::str::{IndentLike, separated};
30use mz_repr::Diff;
31use mz_repr::explain::text::DisplayText;
32use mz_repr::explain::{CompactScalarSeq, Indices, PlanRenderingContext};
33
34use crate::plan::{AggregateExpr, Hir, HirRelationExpr, HirScalarExpr, JoinKind, WindowExprType};
35
36impl DisplayText<PlanRenderingContext<'_, HirRelationExpr>> for HirRelationExpr {
37 fn fmt_text(
38 &self,
39 f: &mut fmt::Formatter<'_>,
40 ctx: &mut PlanRenderingContext<'_, HirRelationExpr>,
41 ) -> fmt::Result {
42 if ctx.config.raw_syntax {
43 self.fmt_raw_syntax(f, ctx)
44 } else {
45 self.fmt_virtual_syntax(f, ctx)
46 }
47 }
48}
49
50impl HirRelationExpr {
51 fn fmt_virtual_syntax(
52 &self,
53 f: &mut fmt::Formatter<'_>,
54 ctx: &mut PlanRenderingContext<'_, HirRelationExpr>,
55 ) -> fmt::Result {
56 if let Some(Except { all, lhs, rhs }) = Hir::un_except(self) {
57 if all {
58 writeln!(f, "{}ExceptAll", ctx.indent)?;
59 } else {
60 writeln!(f, "{}Except", ctx.indent)?;
61 }
62 ctx.indented(|ctx| {
63 lhs.fmt_text(f, ctx)?;
64 rhs.fmt_text(f, ctx)?;
65 Ok(())
66 })?;
67 } else {
68 self.fmt_raw_syntax(f, ctx)?;
70 }
71
72 Ok(())
73 }
74
75 fn fmt_raw_syntax(
76 &self,
77 f: &mut fmt::Formatter<'_>,
78 ctx: &mut PlanRenderingContext<'_, HirRelationExpr>,
79 ) -> fmt::Result {
80 use HirRelationExpr::*;
81
82 let mode = HumanizedExplain::new(ctx.config.redacted);
83
84 match &self {
85 Constant { rows, .. } => {
86 if !rows.is_empty() {
87 writeln!(f, "{}Constant", ctx.indent)?;
88 ctx.indented(|ctx| {
89 fmt_text_constant_rows(
90 f,
91 rows.iter().map(|row| (row, &Diff::ONE)),
92 &mut ctx.indent,
93 ctx.config.redacted,
94 )
95 })?;
96 } else {
97 writeln!(f, "{}Constant <empty>", ctx.indent)?;
98 }
99 }
100 Let {
101 name,
102 id,
103 value,
104 body,
105 } => {
106 let mut bindings = vec![(id, name, value.as_ref())];
107 let mut head = body.as_ref();
108
109 while let Let {
112 name,
113 id,
114 value,
115 body,
116 } = head
117 {
118 bindings.push((id, name, value.as_ref()));
119 head = body.as_ref();
120 }
121
122 writeln!(f, "{}With", ctx.indent)?;
123 ctx.indented(|ctx| {
124 for (id, name, value) in bindings.iter() {
125 writeln!(f, "{}cte [{} as {}] =", ctx.indent, *id, *name)?;
127 ctx.indented(|ctx| value.fmt_text(f, ctx))?;
128 }
129 Ok(())
130 })?;
131 writeln!(f, "{}Return", ctx.indent)?;
132 ctx.indented(|ctx| head.fmt_text(f, ctx))?;
133 }
134 LetRec {
135 limit,
136 bindings,
137 body,
138 } => {
139 write!(f, "{}With Mutually Recursive", ctx.indent)?;
140 if let Some(limit) = limit {
141 write!(f, " {}", limit)?;
142 }
143 writeln!(f)?;
144 ctx.indented(|ctx| {
145 for (name, id, value, _type) in bindings.iter() {
146 writeln!(f, "{}cte [{} as {}] =", ctx.indent, *id, *name)?;
148 ctx.indented(|ctx| value.fmt_text(f, ctx))?;
149 }
150 Ok(())
151 })?;
152 writeln!(f, "{}Return", ctx.indent)?;
153 ctx.indented(|ctx| body.fmt_text(f, ctx))?;
154 }
155 Get { id, .. } => match id {
156 Id::Local(id) => {
157 writeln!(f, "{}Get {}", ctx.indent, id)?;
159 }
160 Id::Global(id) => {
161 let humanized_id = ctx
162 .humanizer
163 .humanize_id(*id)
164 .unwrap_or_else(|| id.to_string());
165 writeln!(f, "{}Get {}", ctx.indent, humanized_id)?;
166 }
167 },
168 Project { outputs, input } => {
169 let outputs = Indices(outputs);
170 writeln!(f, "{}Project ({})", ctx.indent, outputs)?;
171 ctx.indented(|ctx| input.fmt_text(f, ctx))?;
172 }
173 Map { scalars, input } => {
174 let scalars = CompactScalarSeq(scalars);
175 writeln!(f, "{}Map ({})", ctx.indent, scalars)?;
176 ctx.indented(|ctx| input.fmt_text(f, ctx))?;
177 }
178 CallTable { func, exprs } => {
179 let exprs = CompactScalarSeq(exprs);
180 writeln!(f, "{}CallTable {}({})", ctx.indent, func, exprs)?;
181 }
182 Filter { predicates, input } => {
183 let predicates = separated(" AND ", predicates);
184 writeln!(f, "{}Filter {}", ctx.indent, predicates)?;
185 ctx.indented(|ctx| input.fmt_text(f, ctx))?;
186 }
187 Join {
188 left,
189 right,
190 on,
191 kind,
192 } => {
193 if on.is_literal_true() && kind == &JoinKind::Inner {
194 write!(f, "{}CrossJoin", ctx.indent)?;
195 } else {
196 write!(f, "{}{}Join {}", ctx.indent, kind, on)?;
197 }
198 writeln!(f)?;
199 ctx.indented(|ctx| {
200 left.fmt_text(f, ctx)?;
201 right.fmt_text(f, ctx)?;
202 Ok(())
203 })?;
204 }
205 Reduce {
206 group_key,
207 aggregates,
208 expected_group_size,
209 input,
210 } => {
211 write!(f, "{}Reduce", ctx.indent)?;
212 if group_key.len() > 0 {
213 let group_key = Indices(group_key);
214 write!(f, " group_by=[{}]", group_key)?;
215 }
216 if aggregates.len() > 0 {
217 let aggregates = separated(", ", aggregates);
218 write!(f, " aggregates=[{}]", aggregates)?;
219 }
220 if let Some(expected_group_size) = expected_group_size {
221 write!(f, " exp_group_size={}", expected_group_size)?;
222 }
223 writeln!(f)?;
224 ctx.indented(|ctx| input.fmt_text(f, ctx))?;
225 }
226 Distinct { input } => {
227 writeln!(f, "{}Distinct", ctx.indent)?;
228 ctx.indented(|ctx| input.fmt_text(f, ctx))?;
229 }
230 TopK {
231 group_key,
232 order_key,
233 limit,
234 offset,
235 input,
236 expected_group_size,
237 } => {
238 write!(f, "{}TopK", ctx.indent)?;
239 if group_key.len() > 0 {
240 let group_by = Indices(group_key);
241 write!(f, " group_by=[{}]", group_by)?;
242 }
243 if order_key.len() > 0 {
244 let order_by = mode.seq(order_key, None);
245 let order_by = separated(", ", order_by);
246 write!(f, " order_by=[{}]", order_by)?;
247 }
248 if let Some(limit) = limit {
249 write!(f, " limit={}", limit)?;
250 }
251 let offset_literal = offset.clone().try_into_literal_int64();
253 if !offset_literal.clone().is_ok_and(|offset| offset == 0) {
254 let offset = if offset.contains_parameters() {
255 offset.to_string()
260 } else {
261 match offset_literal {
262 Ok(offset) => offset.to_string(),
263 Err(err) => err.to_string_with_causes(),
266 }
267 };
268 write!(f, " offset={}", offset)?;
269 };
270 if let Some(expected_group_size) = expected_group_size {
271 write!(f, " exp_group_size={}", expected_group_size)?;
272 }
273 writeln!(f)?;
274 ctx.indented(|ctx| input.fmt_text(f, ctx))?;
275 }
276 Negate { input } => {
277 writeln!(f, "{}Negate", ctx.indent)?;
278 ctx.indented(|ctx| input.fmt_text(f, ctx))?;
279 }
280 Threshold { input } => {
281 writeln!(f, "{}Threshold", ctx.indent)?;
282 ctx.indented(|ctx| input.fmt_text(f, ctx))?;
283 }
284 Union { base, inputs } => {
285 writeln!(f, "{}Union", ctx.indent)?;
286 ctx.indented(|ctx| {
287 base.fmt_text(f, ctx)?;
288 for input in inputs.iter() {
289 input.fmt_text(f, ctx)?;
290 }
291 Ok(())
292 })?;
293 }
294 }
295
296 Ok(())
297 }
298}
299
300impl fmt::Display for HirScalarExpr {
301 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
302 use HirRelationExpr::Get;
303 use HirScalarExpr::*;
304 match self {
305 Column(i, TreatAsEqual(None)) => write!(
306 f,
307 "#{}{}",
308 (0..i.level).map(|_| '^').collect::<String>(),
309 i.column
310 ),
311 Column(i, TreatAsEqual(Some(name))) => write!(
312 f,
313 "#{}{}{{{name}}}",
314 (0..i.level).map(|_| '^').collect::<String>(),
315 i.column,
316 ),
317 Parameter(i, _name) => write!(f, "${}", i),
318 Literal(row, _, _name) => write!(f, "{}", row.unpack_first()),
319 CallUnmaterializable(func, _name) => write!(f, "{}()", func),
320 CallUnary { func, expr, .. } => {
321 if let mz_expr::UnaryFunc::Not(_) = *func {
322 if let CallUnary { func, expr, .. } = expr.as_ref() {
323 if let Some(is) = func.is() {
324 return write!(f, "({}) IS NOT {}", expr, is);
325 }
326 }
327 }
328 if let Some(is) = func.is() {
329 write!(f, "({}) IS {}", expr, is)
330 } else {
331 write!(f, "{}({})", func, expr)
332 }
333 }
334 CallBinary {
335 func, expr1, expr2, ..
336 } => {
337 if func.is_infix_op() {
338 write!(f, "({} {} {})", expr1, func, expr2)
339 } else {
340 write!(f, "{}({}, {})", func, expr1, expr2)
341 }
342 }
343 CallVariadic { func, exprs, .. } => {
344 use mz_expr::VariadicFunc::*;
345 match func {
346 ArrayCreate { .. } => {
347 let exprs = separated(", ", exprs);
348 write!(f, "array[{}]", exprs)
349 }
350 ListCreate { .. } => {
351 let exprs = separated(", ", exprs);
352 write!(f, "list[{}]", exprs)
353 }
354 RecordCreate { .. } => {
355 let exprs = separated(", ", exprs);
356 write!(f, "row({})", exprs)
357 }
358 func if func.is_infix_op() && exprs.len() > 1 => {
359 let func = format!(" {} ", func);
360 let exprs = separated(&func, exprs);
361 write!(f, "({})", exprs)
362 }
363 func => {
364 let exprs = separated(", ", exprs);
365 write!(f, "{}({})", func, exprs)
366 }
367 }
368 }
369 If {
370 cond, then, els, ..
371 } => {
372 write!(f, "case when {} then {} else {} end", cond, then, els)
373 }
374 Windowing(expr, _name) => {
375 let (column_orders, ignore_nulls, window_frame) = match &expr.func {
380 WindowExprType::Scalar(scalar_window_expr) => {
381 write!(f, "{}()", scalar_window_expr.func)?;
382 (&scalar_window_expr.order_by, false, None)
383 }
384 WindowExprType::Value(value_window_expr) => {
385 write!(f, "{}({})", value_window_expr.func, value_window_expr.args)?;
386 (
387 &value_window_expr.order_by,
388 value_window_expr.ignore_nulls,
389 Some(&value_window_expr.window_frame),
390 )
391 }
392 WindowExprType::Aggregate(aggregate_window_expr) => {
393 write!(f, "{}", aggregate_window_expr.aggregate_expr)?;
394 (
395 &aggregate_window_expr.order_by,
396 false,
397 Some(&aggregate_window_expr.window_frame),
398 )
399 }
400 };
401
402 assert!(
407 column_orders
408 .iter()
409 .enumerate()
410 .all(|(i, column_order)| i == column_order.column)
411 );
412 let order_by = column_orders
413 .iter()
414 .zip_eq(expr.order_by.iter())
415 .map(|(column_order, expr)| {
416 ColumnOrderWithExpr {
417 expr: expr.clone(),
418 desc: column_order.desc,
419 nulls_last: column_order.nulls_last,
420 }
421 })
423 .collect_vec();
424
425 if ignore_nulls {
427 write!(f, " ignore nulls")?;
428 }
429
430 write!(f, " over (")?;
434 if !expr.partition_by.is_empty() {
435 write!(
436 f,
437 "partition by [{}] ",
438 separated(", ", expr.partition_by.iter())
439 )?;
440 }
441 write!(f, "order by [{}]", separated(", ", order_by.iter()))?;
442 if let Some(window_frame) = window_frame {
443 if *window_frame != WindowFrame::default() {
444 write!(f, " {}", window_frame)?;
445 }
446 }
447 write!(f, ")")?;
448
449 Ok(())
450 }
451 Exists(expr, _name) => match expr.as_ref() {
452 Get { id, .. } => write!(f, "exists(Get {})", id), _ => write!(f, "exists(???)"),
454 },
455 Select(expr, _name) => match expr.as_ref() {
456 Get { id, .. } => write!(f, "select(Get {})", id), _ => write!(f, "select(???)"),
458 },
459 }
460 }
461}
462
463struct ColumnOrderWithExpr {
466 pub expr: HirScalarExpr,
468 pub desc: bool,
470 pub nulls_last: bool,
472}
473
474impl fmt::Display for ColumnOrderWithExpr {
475 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
476 write!(
478 f,
479 "{} {} {}",
480 self.expr,
481 if self.desc { "desc" } else { "asc" },
482 if self.nulls_last {
483 "nulls_last"
484 } else {
485 "nulls_first"
486 },
487 )
488 }
489}
490
491impl fmt::Display for AggregateExpr {
492 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
493 if self.is_count_asterisk() {
494 return write!(f, "count(*)");
495 }
496
497 let mode = HumanizedExplain::new(false);
499 let func = self.func.clone().into_expr();
500 let func = mode.expr(&func, None);
501 let distinct = if self.distinct { "distinct " } else { "" };
502
503 write!(f, "{}({}", func, distinct)?;
504 self.expr.fmt(f)?;
505 write!(f, ")")
506 }
507}