Skip to content

Commit 82b78f4

Browse files
committed
explain: add WITH (projection pushdown) option to show source column demand
1 parent 1e45586 commit 82b78f4

8 files changed

Lines changed: 167 additions & 6 deletions

File tree

src/adapter/src/explain/mir.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,12 @@ impl<'a> Explainable<'a, DataflowDescription<OptimizedMirRelationExpr>> {
135135
.iter_mut()
136136
.map(|(id, import)| {
137137
let op = import.desc.arguments.operators.as_ref();
138-
ExplainSource::new(*id, op, context.config.filter_pushdown)
138+
ExplainSource::new(
139+
*id,
140+
op,
141+
context.config.filter_pushdown,
142+
context.config.projection_pushdown,
143+
)
139144
})
140145
.collect::<Vec<_>>();
141146

src/compute-types/src/explain.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,12 @@ impl<'a> DataflowDescription<Plan> {
7171
.iter_mut()
7272
.map(|(id, import)| {
7373
let op = import.desc.arguments.operators.as_ref();
74-
ExplainSource::new(*id, op, context.config.filter_pushdown)
74+
ExplainSource::new(
75+
*id,
76+
op,
77+
context.config.filter_pushdown,
78+
context.config.projection_pushdown,
79+
)
7580
})
7681
.collect::<Vec<_>>();
7782

@@ -139,7 +144,12 @@ impl<'a> DataflowDescription<OptimizedMirRelationExpr> {
139144
.iter_mut()
140145
.map(|(id, import)| {
141146
let op = import.desc.arguments.operators.as_ref();
142-
ExplainSource::new(*id, op, context.config.filter_pushdown)
147+
ExplainSource::new(
148+
*id,
149+
op,
150+
context.config.filter_pushdown,
151+
context.config.projection_pushdown,
152+
)
143153
})
144154
.collect::<Vec<_>>();
145155

src/expr/src/explain.rs

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
//! `EXPLAIN` support for structures defined in this crate.
1111
12-
use std::collections::BTreeMap;
12+
use std::collections::{BTreeMap, BTreeSet};
1313
use std::fmt::Formatter;
1414
use std::time::Duration;
1515

@@ -18,7 +18,7 @@ use mz_repr::GlobalId;
1818
use mz_repr::explain::ExplainError::LinearChainsPlusRecursive;
1919
use mz_repr::explain::text::DisplayText;
2020
use mz_repr::explain::{
21-
AnnotatedPlan, Explain, ExplainConfig, ExplainError, ExprHumanizer, ScalarOps,
21+
AnnotatedPlan, Explain, ExplainConfig, ExplainError, ExprHumanizer, Indices, ScalarOps,
2222
UnsupportedFormat, UsedIndexes,
2323
};
2424
use mz_repr::optimize::OptimizerFeatures;
@@ -93,18 +93,51 @@ where
9393
}
9494
}
9595

96+
/// Carries metadata about the columns demanded from a source decoder.
97+
/// (Only emitted when a context flag is enabled.)
98+
#[derive(Debug)]
99+
pub struct ProjectionInfo {
100+
/// Input columns demanded by the source MFP (filter + map + project).
101+
pub demand: Vec<usize>,
102+
/// Total input arity, for deciding whether the demand is the identity.
103+
pub input_arity: usize,
104+
}
105+
106+
impl<'a, C, M> DisplayText<C> for HumanizedExpr<'a, ProjectionInfo, M>
107+
where
108+
C: AsMut<Indent>,
109+
M: HumanizerMode,
110+
{
111+
fn fmt_text(&self, f: &mut Formatter<'_>, ctx: &mut C) -> std::fmt::Result {
112+
let ProjectionInfo {
113+
demand,
114+
input_arity,
115+
} = self.expr;
116+
117+
// Suppress the annotation when every column is demanded — that's the
118+
// case where projection pushdown is a no-op.
119+
if demand.len() == *input_arity {
120+
return Ok(());
121+
}
122+
123+
writeln!(f, "{}demand=({})", ctx.as_mut(), Indices(demand))
124+
}
125+
}
126+
96127
#[allow(missing_debug_implementations)]
97128
pub struct ExplainSource<'a> {
98129
pub id: GlobalId,
99130
pub op: Option<&'a MapFilterProject>,
100131
pub pushdown_info: Option<PushdownInfo<'a>>,
132+
pub projection_info: Option<ProjectionInfo>,
101133
}
102134

103135
impl<'a> ExplainSource<'a> {
104136
pub fn new(
105137
id: GlobalId,
106138
op: Option<&'a MapFilterProject>,
107139
filter_pushdown: bool,
140+
projection_pushdown: bool,
108141
) -> ExplainSource<'a> {
109142
let pushdown_info = if filter_pushdown {
110143
op.map(|op| {
@@ -121,10 +154,23 @@ impl<'a> ExplainSource<'a> {
121154
None
122155
};
123156

157+
let projection_info = if projection_pushdown {
158+
op.filter(|op| !op.is_identity()).map(|op| {
159+
let demand: BTreeSet<usize> = op.demand();
160+
ProjectionInfo {
161+
demand: demand.into_iter().collect(),
162+
input_arity: op.input_arity,
163+
}
164+
})
165+
} else {
166+
None
167+
};
168+
124169
ExplainSource {
125170
id,
126171
op,
127172
pushdown_info,
173+
projection_info,
128174
}
129175
}
130176

@@ -155,6 +201,9 @@ where
155201
if let Some(pushdown_info) = &self.expr.pushdown_info {
156202
self.child(pushdown_info).fmt_text(f, ctx)?;
157203
}
204+
if let Some(projection_info) = &self.expr.projection_info {
205+
self.child(projection_info).fmt_text(f, ctx)?;
206+
}
158207
Ok(())
159208
})
160209
}

src/expr/src/explain/json.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
1212
use mz_repr::explain::json::DisplayJson;
1313

14-
use crate::explain::{ExplainMultiPlan, ExplainSinglePlan, ExplainSource, PushdownInfo};
14+
use crate::explain::{
15+
ExplainMultiPlan, ExplainSinglePlan, ExplainSource, ProjectionInfo, PushdownInfo,
16+
};
1517

1618
impl<'a, T: 'a> DisplayJson for ExplainSinglePlan<'a, T>
1719
where
@@ -47,6 +49,7 @@ where
4749
id,
4850
op,
4951
pushdown_info,
52+
projection_info,
5053
}| {
5154
let mut json = serde_json::json!({
5255
"id": id,
@@ -58,6 +61,15 @@ where
5861
object.insert("pushdown".to_owned(), serde_json::json!(pushdown));
5962
}
6063

64+
if let Some(ProjectionInfo {
65+
demand,
66+
input_arity: _,
67+
}) = projection_info
68+
{
69+
let object = json.as_object_mut().unwrap();
70+
object.insert("demand".to_owned(), serde_json::json!(demand));
71+
}
72+
6173
json
6274
},
6375
)

src/repr/src/explain.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@ pub struct ExplainConfig {
199199
pub timing: bool,
200200
/// Show MFP pushdown information.
201201
pub filter_pushdown: bool,
202+
/// Show source column demand pushed into persist source decoders.
203+
pub projection_pushdown: bool,
202204

203205
/// Optimizer feature flags.
204206
pub features: OptimizerFeatureOverrides,
@@ -213,6 +215,7 @@ impl Default for ExplainConfig {
213215
cardinality: false,
214216
column_names: false,
215217
filter_pushdown: false,
218+
projection_pushdown: false,
216219
humanized_exprs: false,
217220
join_impls: true,
218221
keys: false,
@@ -1017,6 +1020,7 @@ mod tests {
10171020
cardinality: false,
10181021
column_names: false,
10191022
filter_pushdown: false,
1023+
projection_pushdown: false,
10201024
humanized_exprs: false,
10211025
join_impls: false,
10221026
keys: false,

src/sql-parser/src/ast/defs/statement.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3982,6 +3982,7 @@ pub enum ExplainPlanOptionName {
39823982
NoFastPath,
39833983
NoNotices,
39843984
NodeIdentifiers,
3985+
ProjectionPushdown,
39853986
RawPlans,
39863987
RawSyntax,
39873988
Raw, // Listed after the `Raw~` variants to keep the parser happy!
@@ -4019,6 +4020,7 @@ impl WithOptionName for ExplainPlanOptionName {
40194020
| Self::NoFastPath
40204021
| Self::NoNotices
40214022
| Self::NodeIdentifiers
4023+
| Self::ProjectionPushdown
40224024
| Self::RawPlans
40234025
| Self::RawSyntax
40244026
| Self::Raw

src/sql/src/plan/statement/dml.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,7 @@ generate_extracted_config!(
572572
(NonNegative, bool, Default(false)),
573573
(NoNotices, bool, Default(false)),
574574
(NodeIdentifiers, bool, Default(false)),
575+
(ProjectionPushdown, Option<bool>, Default(None)),
575576
(Raw, bool, Default(false)),
576577
(RawPlans, bool, Default(false)),
577578
(RawSyntax, bool, Default(false)),
@@ -613,6 +614,7 @@ impl TryFrom<ExplainPlanOptionExtracted> for ExplainConfig {
613614
cardinality: v.cardinality,
614615
column_names: v.column_names,
615616
filter_pushdown: v.filter_pushdown.unwrap_or(enable_on_prod),
617+
projection_pushdown: v.projection_pushdown.unwrap_or(enable_on_prod),
616618
humanized_exprs: !v.raw_plans && (v.humanized_expressions.unwrap_or(enable_on_prod)),
617619
join_impls: v.join_implementations,
618620
keys: v.keys,
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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 at the root of this repository.
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+
mode cockroach
11+
12+
# Verify that `EXPLAIN ... WITH (projection pushdown)` surfaces the column
13+
# demand the persist source decoder will receive. The `demand=(...)` set is the
14+
# union of columns read by the source MFP's filter, map, and projection. It is
15+
# suppressed when every input column is demanded (the no-op case).
16+
17+
statement ok
18+
CREATE TABLE t (a int, b int, c int, d int);
19+
20+
# Pure projection: only `a` is demanded.
21+
22+
statement ok
23+
CREATE MATERIALIZED VIEW mv_project AS SELECT a FROM t;
24+
25+
query T multiline
26+
EXPLAIN PHYSICAL PLAN WITH(humanized expressions, projection pushdown) AS VERBOSE TEXT FOR MATERIALIZED VIEW mv_project
27+
----
28+
materialize.public.mv_project:
29+
Get::Collection materialize.public.t
30+
raw=true
31+
32+
Source materialize.public.t
33+
project=(#0)
34+
demand=(#0)
35+
36+
Target cluster: quickstart
37+
38+
EOF
39+
40+
# Projection plus a filter on a different column: demand is the union.
41+
42+
statement ok
43+
CREATE MATERIALIZED VIEW mv_project_filter AS SELECT a FROM t WHERE c > 5;
44+
45+
query T multiline
46+
EXPLAIN PHYSICAL PLAN WITH(humanized expressions, projection pushdown) AS VERBOSE TEXT FOR MATERIALIZED VIEW mv_project_filter
47+
----
48+
materialize.public.mv_project_filter:
49+
Get::Collection materialize.public.t
50+
raw=true
51+
52+
Source materialize.public.t
53+
project=(#0)
54+
filter=((#2{c} > 5))
55+
demand=(#0, #2)
56+
57+
Target cluster: quickstart
58+
59+
EOF
60+
61+
# Identity projection: no demand annotation is emitted.
62+
63+
statement ok
64+
CREATE MATERIALIZED VIEW mv_identity AS SELECT * FROM t;
65+
66+
query T multiline
67+
EXPLAIN PHYSICAL PLAN WITH(humanized expressions, projection pushdown) AS VERBOSE TEXT FOR MATERIALIZED VIEW mv_identity
68+
----
69+
materialize.public.mv_identity:
70+
Get::PassArrangements materialize.public.t
71+
raw=true
72+
73+
Source materialize.public.t
74+
75+
Target cluster: quickstart
76+
77+
EOF

0 commit comments

Comments
 (0)