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