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.
910//! `EXPLAIN` support for structures defined in this crate.
1112use std::panic::AssertUnwindSafe;
1314use mz_expr::explain::{ExplainContext, ExplainSinglePlan};
15use mz_expr::visit::{Visit, VisitChildren};
16use mz_expr::{Id, LocalId};
17use mz_ore::stack::RecursionLimitError;
18use mz_repr::RelationType;
19use mz_repr::explain::{AnnotatedPlan, Explain, ExplainError, ScalarOps, UnsupportedFormat};
2021use crate::plan::{HirRelationExpr, HirScalarExpr};
2223mod text;
2425impl<'a> Explain<'a> for HirRelationExpr {
26type Context = ExplainContext<'a>;
2728type Text = ExplainSinglePlan<'a, HirRelationExpr>;
2930type VerboseText = ExplainSinglePlan<'a, HirRelationExpr>;
3132type Json = ExplainSinglePlan<'a, HirRelationExpr>;
3334type Dot = UnsupportedFormat;
3536fn explain_text(&'a mut self, context: &'a Self::Context) -> Result<Self::Text, ExplainError> {
37self.as_explain_single_plan(context)
38 }
3940fn explain_verbose_text(
41&'a mut self,
42 context: &'a Self::Context,
43 ) -> Result<Self::VerboseText, ExplainError> {
44self.as_explain_single_plan(context)
45 }
4647fn explain_json(&'a mut self, context: &'a Self::Context) -> Result<Self::Json, ExplainError> {
48self.as_explain_single_plan(context)
49 }
50}
5152impl<'a> HirRelationExpr {
53fn as_explain_single_plan(
54&'a mut self,
55 context: &'a ExplainContext<'a>,
56 ) -> Result<ExplainSinglePlan<'a, HirRelationExpr>, ExplainError> {
57// unless raw plans are explicitly requested
58 // ensure that all nested subqueries are wrapped in Let blocks by calling
59 // `normalize_subqueries`
60if !context.config.raw_plans {
61 mz_ore::panic::catch_unwind_str(AssertUnwindSafe(|| {
62 normalize_subqueries(self).map_err(|e| e.into())
63 }))
64 .unwrap_or_else(|panic| {
65// A panic during optimization is always a bug; log an error so we learn about it.
66 // TODO(teskje): collect and log a backtrace from the panic site
67tracing::error!("caught a panic during `normalize_subqueries`: {panic}");
6869let msg = format!("unexpected panic during `normalize_subqueries`: {panic}");
70Err(ExplainError::UnknownError(msg))
71 })?
72}
7374// TODO: use config values to infer requested
75 // plan annotations
76let plan = AnnotatedPlan {
77 plan: self,
78 annotations: Default::default(),
79 };
80Ok(ExplainSinglePlan { context, plan })
81 }
82}
8384/// Normalize the way subqueries appear in [`HirScalarExpr::Exists`]
85/// or [`HirScalarExpr::Select`] variants.
86///
87/// After the transform is applied, subqueries are pulled as a value in
88/// a let binding enclosing the [`HirRelationExpr`] parent of the
89/// [`HirScalarExpr::Exists`] or [`HirScalarExpr::Select`] where the
90/// subquery appears, and the corresponding variant references the
91/// new binding with a [`HirRelationExpr::Get`].
92pub fn normalize_subqueries<'a>(expr: &'a mut HirRelationExpr) -> Result<(), RecursionLimitError> {
93// A helper struct to represent accumulated `$local_id = $subquery`
94 // bindings that need to be installed in `let ... in $expr` nodes
95 // that wrap their parent $expr.
96struct Binding {
97 local_id: LocalId,
98 subquery: HirRelationExpr,
99 }
100101// Context for the transformation
102 // - a stack of bindings
103let mut bindings = Vec::<Binding>::new();
104// - a generator of fresh local ids
105let mut id_gen = id_gen(expr)?.peekable();
106107// Grow the `bindings` stack by collecting subqueries appearing in
108 // one of the HirScalarExpr children at the given HirRelationExpr.
109 // As part of this, the subquery is replaced by a `Get(id)` for a
110 // fresh local id.
111let mut collect_subqueries = |expr: &mut HirRelationExpr, bindings: &mut Vec<Binding>| {
112 expr.try_visit_mut_children(|expr: &mut HirScalarExpr| {
113use HirRelationExpr::Get;
114use HirScalarExpr::{Exists, Select};
115 expr.visit_mut_post(&mut |expr: &mut HirScalarExpr| match expr {
116 Exists(expr) | Select(expr) => match expr.as_mut() {
117 Get { .. } => (),
118 expr => {
119// generate fresh local id
120let local_id = id_gen.next().unwrap();
121// generate a `Get(local_id)` to be used as a subquery replacement
122let mut subquery = Get {
123 id: Id::Local(local_id.clone()),
124 typ: RelationType::empty(), // TODO (aalexandrov)
125};
126// swap the current subquery with the replacement
127std::mem::swap(expr, &mut subquery);
128// push a new $local_id = $subquery binding for a wrapping Let { ... }
129bindings.push(Binding { local_id, subquery });
130 }
131 },
132_ => (),
133 })
134 })
135 };
136137// Drain the `bindings` stack by wrapping the given `HirRelationExpr` with
138 // a sequence of `Let { ... }` nodes, one for each binding.
139let insert_let_bindings = |expr: &mut HirRelationExpr, bindings: &mut Vec<Binding>| {
140for binding in bindings.drain(..) {
141let name = format!("subquery-{}", Into::<u64>::into(&binding.local_id));
142let id = binding.local_id;
143let value = Box::new(binding.subquery);
144let body = Box::new(expr.take());
145*expr = HirRelationExpr::Let {
146 name,
147 id,
148 value,
149 body,
150 }
151 }
152 };
153154 expr.try_visit_mut_post(&mut |expr: &mut HirRelationExpr| {
155// first grow bindings stack
156collect_subqueries(expr, &mut bindings)?;
157// then drain bindings stack
158insert_let_bindings(expr, &mut bindings);
159// done!
160Ok(())
161 })
162}
163164// Create an [`Iterator`] for [`LocalId`] values that are guaranteed to be
165// fresh within the scope of the given [`HirRelationExpr`].
166fn id_gen(
167 expr: &HirRelationExpr,
168) -> Result<impl Iterator<Item = LocalId> + use<>, RecursionLimitError> {
169let mut max_id = 0_u64;
170171 expr.visit_pre(&mut |expr| {
172match expr {
173 HirRelationExpr::Let { id, .. } => max_id = std::cmp::max(max_id, id.into()),
174_ => (),
175 };
176 })?;
177178Ok((max_id + 1..).map(LocalId::new))
179}
180181impl ScalarOps for HirScalarExpr {
182fn match_col_ref(&self) -> Option<usize> {
183match self {
184 HirScalarExpr::Column(c) if c.level == 0 => Some(c.column),
185_ => None,
186 }
187 }
188189fn references(&self, column: usize) -> bool {
190match self {
191 HirScalarExpr::Column(c) => c.column == column && c.level == 0,
192_ => false,
193 }
194 }
195}