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