Skip to main content

mz_compute_types/
explain.rs

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.
9
10//! `EXPLAIN` support for structures defined in this crate.
11
12pub(crate) mod text;
13
14use std::borrow::Cow;
15use std::collections::BTreeMap;
16
17use mz_expr::explain::{ExplainContext, ExplainMultiPlan, ExplainSource, enforce_linear_chains};
18use mz_expr::{MirRelationExpr, OptimizedMirRelationExpr};
19use mz_repr::GlobalId;
20use mz_repr::explain::{AnnotatedPlan, Explain, ExplainError, UnsupportedFormat};
21
22use crate::dataflows::{DataflowDescription, LirDataflowDescription};
23use crate::plan::LirRelationExpr;
24
25impl<'a> Explain<'a> for LirDataflowDescription {
26    type Context = ExplainContext<'a>;
27
28    type Text = ExplainMultiPlan<'a, LirRelationExpr>;
29
30    type Json = ExplainMultiPlan<'a, LirRelationExpr>;
31
32    type Dot = UnsupportedFormat;
33
34    fn explain_text(&'a mut self, context: &'a Self::Context) -> Result<Self::Text, ExplainError> {
35        self.as_explain_multi_plan(context)
36    }
37
38    fn explain_json(&'a mut self, context: &'a Self::Context) -> Result<Self::Text, ExplainError> {
39        self.as_explain_multi_plan(context)
40    }
41}
42
43impl<'a> LirDataflowDescription {
44    fn as_explain_multi_plan(
45        &'a mut self,
46        context: &'a ExplainContext<'a>,
47    ) -> Result<ExplainMultiPlan<'a, LirRelationExpr>, ExplainError> {
48        let export_ids = export_ids_for(self);
49        let plans = self
50            .objects_to_build
51            .iter_mut()
52            .rev()
53            .map(|build_desc| {
54                let public_id = export_ids
55                    .get(&build_desc.id)
56                    .unwrap_or(&build_desc.id)
57                    .clone();
58                let id = context
59                    .humanizer
60                    .humanize_id(public_id)
61                    .unwrap_or_else(|| public_id.to_string());
62                let plan = AnnotatedPlan {
63                    plan: &build_desc.plan,
64                    annotations: BTreeMap::default(),
65                };
66                (id, plan)
67            })
68            .collect::<Vec<_>>();
69
70        let sources = self
71            .source_imports
72            .iter_mut()
73            .map(|(id, import)| {
74                // Folding the plan back is display-only, and need not
75                // reproduce the planned operators exactly.
76                let op = import.desc.arguments.operators.as_ref();
77                let op = op.map(|plan| Cow::Owned(plan.clone().into_map_filter_project()));
78                ExplainSource::new(*id, op, context.config.filter_pushdown)
79            })
80            .collect::<Vec<_>>();
81
82        Ok(ExplainMultiPlan {
83            context,
84            sources,
85            plans,
86        })
87    }
88}
89
90impl<'a> Explain<'a> for DataflowDescription<OptimizedMirRelationExpr> {
91    type Context = ExplainContext<'a>;
92
93    type Text = ExplainMultiPlan<'a, MirRelationExpr>;
94
95    type Json = ExplainMultiPlan<'a, MirRelationExpr>;
96
97    type Dot = UnsupportedFormat;
98
99    fn explain_text(&'a mut self, context: &'a Self::Context) -> Result<Self::Text, ExplainError> {
100        self.as_explain_multi_plan(context)
101    }
102
103    fn explain_json(&'a mut self, context: &'a Self::Context) -> Result<Self::Text, ExplainError> {
104        self.as_explain_multi_plan(context)
105    }
106}
107
108impl<'a> DataflowDescription<OptimizedMirRelationExpr> {
109    fn as_explain_multi_plan(
110        &'a mut self,
111        context: &'a ExplainContext<'a>,
112    ) -> Result<ExplainMultiPlan<'a, MirRelationExpr>, ExplainError> {
113        let export_ids = export_ids_for(self);
114        let plans = self
115            .objects_to_build
116            .iter_mut()
117            .rev()
118            .map(|build_desc| {
119                // normalize the representation as linear chains
120                // (this implies !context.config.raw_plans by construction)
121                if context.config.linear_chains {
122                    enforce_linear_chains(build_desc.plan.as_inner_mut())?;
123                };
124
125                let id = export_ids
126                    .get(&build_desc.id)
127                    .unwrap_or(&build_desc.id)
128                    .clone();
129                let id = context
130                    .humanizer
131                    .humanize_id(id)
132                    .unwrap_or_else(|| id.to_string());
133                let plan = AnnotatedPlan {
134                    plan: build_desc.plan.as_inner(),
135                    annotations: BTreeMap::default(),
136                };
137                Ok((id, plan))
138            })
139            .collect::<Result<Vec<_>, ExplainError>>()?;
140
141        let sources = self
142            .source_imports
143            .iter_mut()
144            .map(|(id, import)| {
145                let op = import.desc.arguments.operators.as_ref().map(Cow::Borrowed);
146                ExplainSource::new(*id, op, context.config.filter_pushdown)
147            })
148            .collect::<Vec<_>>();
149
150        Ok(ExplainMultiPlan {
151            context,
152            sources,
153            plans,
154        })
155    }
156}
157
158/// TODO(database-issues#7533): Add documentation.
159pub fn export_ids_for<P, S, O>(dd: &DataflowDescription<P, S, O>) -> BTreeMap<GlobalId, GlobalId> {
160    let mut map = BTreeMap::<GlobalId, GlobalId>::default();
161
162    // Dataflows created from a `CREATE MATERIALIZED VIEW` have:
163    //
164    // 1. Exactly one entry in `objects_to_build` representing the dataflow to
165    //    be installed. This entry has a transient ID that changes whenever the
166    //    dataflow is re-installed.
167    // 2. Exactly one entry in `sink_exports` referencing the `objects_to_build`
168    //    entry.
169    // 3. No enties in index_exports.
170    //
171    // Because the user-facing ID is for the `sink_exports` entry in (2), we
172    // create a mapping.
173    if dd.sink_exports.len() == 1 && dd.objects_to_build.len() == 1 && dd.index_exports.is_empty() {
174        for (public_id, export) in dd.sink_exports.iter() {
175            map.insert(export.from, *public_id);
176        }
177    }
178
179    // Dataflows created from a `CREATE INDEX` adhere to the following
180    // constraints.
181    //
182    // 1. One or more entries in `objects_to_build`. The last entry arranges a
183    //    `Get $id` where $id might be:
184    //    1. The previous `objects_to_build` entry that corresponds to the
185    //       dataflow of the indexed view (if we index a VIEW).
186    //    2. A `source_imports` entry (if we index a SOURCE, TABLE, or
187    //       MATERIALIZED VIEW).
188    //    3. An `index_imports` entry (if we index a SOURCE, TABLE, or
189    //       MATERIALIZED VIEW that is already indexed).
190    // 2. Exactly one entry in `index_exports` identified by the same ID as the
191    //    last `objects_to_build` entry.
192    //
193    // Because there are no transient IDs involved in the above configurations,
194    // we don't need to add further entries to the `map` to account for these
195    // cases.
196
197    map
198}