Skip to content
This repository was archived by the owner on Jan 7, 2025. It is now read-only.

Commit aae6046

Browse files
authored
feat: display cost in explain (#104)
Closes #89. When using `explain verbose`, the cardinality of each plan node will be displayed. Example: ``` ❯ create table t1(v1 int); 0 rows in set. Query took 0.005 seconds. Execution took 0.000 secs, Planning took 0.000 secs ❯ explain verbose select * from t1; PhysicalProjection { exprs: [ #0 ], cost: weighted=1.06,row_cnt=1.00,compute=0.06,io=1.00 } └── PhysicalScan { table: t1, cost: weighted=1.00,row_cnt=1.00,compute=0.00,io=1.00 } ❯ explain verbose select count(*) from t1; PhysicalProjection { exprs: [ #0 ], cost: weighted=21.18,row_cnt=1.00,compute=20.18,io=1.00 } └── PhysicalAgg ├── aggrs:Agg(Count) │ └── [ 1 ] ├── groups: [] ├── cost: weighted=21.12,row_cnt=1.00,compute=20.12,io=1.00 └── PhysicalScan { table: t1, cost: weighted=1.00,row_cnt=1.00,compute=0.00,io=1.00 } ```
1 parent b52f5ed commit aae6046

File tree

13 files changed

+219
-103
lines changed

13 files changed

+219
-103
lines changed

optd-datafusion-bridge/src/lib.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -206,9 +206,11 @@ impl OptdQueryPlanner {
206206
.create_physical_plan(logical_plan, session_state)
207207
.await?);
208208
}
209-
let (mut explains, logical_plan) = match logical_plan {
210-
LogicalPlan::Explain(Explain { plan, .. }) => (Some(Vec::new()), plan.as_ref()),
211-
_ => (None, logical_plan),
209+
let (mut explains, verbose, logical_plan) = match logical_plan {
210+
LogicalPlan::Explain(Explain { plan, verbose, .. }) => {
211+
(Some(Vec::new()), *verbose, plan.as_ref())
212+
}
213+
_ => (None, false, logical_plan),
212214
};
213215
let mut ctx = OptdPlanContext::new(session_state);
214216
if let Some(explains) = &mut explains {
@@ -224,7 +226,7 @@ impl OptdQueryPlanner {
224226
},
225227
PlanNode::from_rel_node(optd_rel.clone())
226228
.unwrap()
227-
.explain_to_string(),
229+
.explain_to_string(None),
228230
));
229231
}
230232
let mut optimizer = self.optimizer.lock().unwrap().take().unwrap();
@@ -237,7 +239,7 @@ impl OptdQueryPlanner {
237239
},
238240
PlanNode::from_rel_node(optimized_rel.clone())
239241
.unwrap()
240-
.explain_to_string(),
242+
.explain_to_string(if verbose { Some(&meta) } else { None }),
241243
));
242244
let join_order = get_join_order(optimized_rel.clone());
243245
explains.push(StringifiedPlan::new(

optd-datafusion-repr/src/bin/test_optimize.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ pub fn main() {
7575
);
7676
println!(
7777
"{}",
78-
PlanNode::from_rel_node(node).unwrap().explain_to_string()
78+
PlanNode::from_rel_node(node)
79+
.unwrap()
80+
.explain_to_string(None)
7981
);
8082

8183
let mut optimizer = HeuristicsOptimizer::new_with_rules(
@@ -94,6 +96,8 @@ pub fn main() {
9496
let node = optimizer.optimize(fnal.0.into_rel_node()).unwrap();
9597
println!(
9698
"{}",
97-
PlanNode::from_rel_node(node).unwrap().explain_to_string()
99+
PlanNode::from_rel_node(node)
100+
.unwrap()
101+
.explain_to_string(None)
98102
);
99103
}

optd-datafusion-repr/src/explain.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
use optd_core::rel_node::RelNodeMeta;
2+
use pretty_xmlish::Pretty;
3+
4+
use crate::cost::{COMPUTE_COST, IO_COST, ROW_COUNT};
5+
6+
pub trait Insertable<'a> {
7+
fn with_meta(self, meta: &RelNodeMeta) -> Self;
8+
}
9+
10+
impl<'a> Insertable<'a> for Vec<(&'a str, Pretty<'a>)> {
11+
// FIXME: this assumes we are using OptCostModel
12+
fn with_meta(mut self, meta: &RelNodeMeta) -> Self {
13+
self.push((
14+
"cost",
15+
Pretty::display(&format!(
16+
"weighted={:.2},row_cnt={:.2},compute={:.2},io={:.2}",
17+
meta.cost.0[0],
18+
meta.cost.0[ROW_COUNT],
19+
meta.cost.0[COMPUTE_COST],
20+
meta.cost.0[IO_COST],
21+
)),
22+
));
23+
self
24+
}
25+
}

optd-datafusion-repr/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use rules::{
2323
pub use optd_core::rel_node::Value;
2424

2525
pub mod cost;
26+
mod explain;
2627
pub mod plan_nodes;
2728
pub mod properties;
2829
pub mod rules;

optd-datafusion-repr/src/plan_nodes.rs

Lines changed: 48 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ mod projection;
1212
mod scan;
1313
mod sort;
1414

15+
use std::fmt::Debug;
1516
use std::sync::Arc;
1617

1718
use arrow_schema::DataType;
1819
use optd_core::{
1920
cascades::{CascadesOptimizer, GroupId},
20-
rel_node::{RelNode, RelNodeRef, RelNodeTyp},
21+
rel_node::{RelNode, RelNodeMeta, RelNodeMetaMap, RelNodeRef, RelNodeTyp},
2122
};
2223

2324
pub use agg::{LogicalAgg, PhysicalAgg};
@@ -168,21 +169,21 @@ pub trait OptRelNode: 'static + Clone {
168169
where
169170
Self: Sized;
170171

171-
fn dispatch_explain(&self) -> Pretty<'static>;
172+
fn dispatch_explain(&self, meta_map: Option<&RelNodeMetaMap>) -> Pretty<'static>;
172173

173-
fn explain(&self) -> Pretty<'static> {
174-
explain(self.clone().into_rel_node())
174+
fn explain(&self, meta_map: Option<&RelNodeMetaMap>) -> Pretty<'static> {
175+
explain(self.clone().into_rel_node(), meta_map)
175176
}
176177

177-
fn explain_to_string(&self) -> String {
178+
fn explain_to_string(&self, meta_map: Option<&RelNodeMetaMap>) -> String {
178179
let mut config = PrettyConfig {
179180
need_boundaries: false,
180181
reduced_spaces: false,
181182
width: 300,
182183
..Default::default()
183184
};
184185
let mut out = String::new();
185-
config.unicode(&mut out, &self.explain());
186+
config.unicode(&mut out, &self.explain(meta_map));
186187
out
187188
}
188189

@@ -221,6 +222,12 @@ impl PlanNode {
221222
pub fn from_group(rel_node: OptRelNodeRef) -> Self {
222223
Self(rel_node)
223224
}
225+
226+
pub fn get_meta<'a>(&self, meta_map: &'a RelNodeMetaMap) -> &'a RelNodeMeta {
227+
meta_map
228+
.get(&(self.0.as_ref() as *const _ as usize))
229+
.unwrap()
230+
}
224231
}
225232

226233
impl OptRelNode for PlanNode {
@@ -235,7 +242,7 @@ impl OptRelNode for PlanNode {
235242
Some(Self(rel_node))
236243
}
237244

238-
fn dispatch_explain(&self) -> Pretty<'static> {
245+
fn dispatch_explain(&self, meta_map: Option<&RelNodeMetaMap>) -> Pretty<'static> {
239246
Pretty::simple_record(
240247
"<PlanNode>",
241248
vec![(
@@ -245,7 +252,7 @@ impl OptRelNode for PlanNode {
245252
self.0
246253
.children
247254
.iter()
248-
.map(|child| explain(child.clone()))
255+
.map(|child| explain(child.clone(), meta_map))
249256
.collect(),
250257
)
251258
}
@@ -274,7 +281,7 @@ impl OptRelNode for Expr {
274281
}
275282
Some(Self(rel_node))
276283
}
277-
fn dispatch_explain(&self) -> Pretty<'static> {
284+
fn dispatch_explain(&self, meta_map: Option<&RelNodeMetaMap>) -> Pretty<'static> {
278285
Pretty::simple_record(
279286
"<Expr>",
280287
vec![(
@@ -284,107 +291,107 @@ impl OptRelNode for Expr {
284291
self.0
285292
.children
286293
.iter()
287-
.map(|child| explain(child.clone()))
294+
.map(|child| explain(child.clone(), meta_map))
288295
.collect(),
289296
)
290297
}
291298
}
292299

293-
pub fn explain(rel_node: OptRelNodeRef) -> Pretty<'static> {
300+
pub fn explain(rel_node: OptRelNodeRef, meta_map: Option<&RelNodeMetaMap>) -> Pretty<'static> {
294301
match rel_node.typ {
295302
OptRelNodeTyp::ColumnRef => ColumnRefExpr::from_rel_node(rel_node)
296303
.unwrap()
297-
.dispatch_explain(),
304+
.dispatch_explain(meta_map),
298305
OptRelNodeTyp::Constant(_) => ConstantExpr::from_rel_node(rel_node)
299306
.unwrap()
300-
.dispatch_explain(),
307+
.dispatch_explain(meta_map),
301308
OptRelNodeTyp::UnOp(_) => UnOpExpr::from_rel_node(rel_node)
302309
.unwrap()
303-
.dispatch_explain(),
310+
.dispatch_explain(meta_map),
304311
OptRelNodeTyp::BinOp(_) => BinOpExpr::from_rel_node(rel_node)
305312
.unwrap()
306-
.dispatch_explain(),
313+
.dispatch_explain(meta_map),
307314
OptRelNodeTyp::Func(_) => FuncExpr::from_rel_node(rel_node)
308315
.unwrap()
309-
.dispatch_explain(),
316+
.dispatch_explain(meta_map),
310317
OptRelNodeTyp::Join(_) => LogicalJoin::from_rel_node(rel_node)
311318
.unwrap()
312-
.dispatch_explain(),
319+
.dispatch_explain(meta_map),
313320
OptRelNodeTyp::Scan => LogicalScan::from_rel_node(rel_node)
314321
.unwrap()
315-
.dispatch_explain(),
322+
.dispatch_explain(meta_map),
316323
OptRelNodeTyp::Filter => LogicalFilter::from_rel_node(rel_node)
317324
.unwrap()
318-
.dispatch_explain(),
325+
.dispatch_explain(meta_map),
319326
OptRelNodeTyp::Apply(_) => LogicalApply::from_rel_node(rel_node)
320327
.unwrap()
321-
.dispatch_explain(),
328+
.dispatch_explain(meta_map),
322329
OptRelNodeTyp::EmptyRelation => LogicalEmptyRelation::from_rel_node(rel_node)
323330
.unwrap()
324-
.dispatch_explain(),
331+
.dispatch_explain(meta_map),
325332
OptRelNodeTyp::Limit => LogicalLimit::from_rel_node(rel_node)
326333
.unwrap()
327-
.dispatch_explain(),
334+
.dispatch_explain(meta_map),
328335
OptRelNodeTyp::PhysicalFilter => PhysicalFilter::from_rel_node(rel_node)
329336
.unwrap()
330-
.dispatch_explain(),
337+
.dispatch_explain(meta_map),
331338
OptRelNodeTyp::PhysicalScan => PhysicalScan::from_rel_node(rel_node)
332339
.unwrap()
333-
.dispatch_explain(),
340+
.dispatch_explain(meta_map),
334341
OptRelNodeTyp::PhysicalNestedLoopJoin(_) => PhysicalNestedLoopJoin::from_rel_node(rel_node)
335342
.unwrap()
336-
.dispatch_explain(),
343+
.dispatch_explain(meta_map),
337344
OptRelNodeTyp::Placeholder(_) => unreachable!("should not explain a placeholder"),
338345
OptRelNodeTyp::List => {
339346
ExprList::from_rel_node(rel_node) // ExprList is the only place that we will have list in the datafusion repr
340347
.unwrap()
341-
.dispatch_explain()
348+
.dispatch_explain(meta_map)
342349
}
343350
OptRelNodeTyp::Agg => LogicalAgg::from_rel_node(rel_node)
344351
.unwrap()
345-
.dispatch_explain(),
352+
.dispatch_explain(meta_map),
346353
OptRelNodeTyp::Sort => LogicalSort::from_rel_node(rel_node)
347354
.unwrap()
348-
.dispatch_explain(),
355+
.dispatch_explain(meta_map),
349356
OptRelNodeTyp::Projection => LogicalProjection::from_rel_node(rel_node)
350357
.unwrap()
351-
.dispatch_explain(),
358+
.dispatch_explain(meta_map),
352359
OptRelNodeTyp::PhysicalProjection => PhysicalProjection::from_rel_node(rel_node)
353360
.unwrap()
354-
.dispatch_explain(),
361+
.dispatch_explain(meta_map),
355362
OptRelNodeTyp::PhysicalAgg => PhysicalAgg::from_rel_node(rel_node)
356363
.unwrap()
357-
.dispatch_explain(),
364+
.dispatch_explain(meta_map),
358365
OptRelNodeTyp::PhysicalSort => PhysicalSort::from_rel_node(rel_node)
359366
.unwrap()
360-
.dispatch_explain(),
367+
.dispatch_explain(meta_map),
361368
OptRelNodeTyp::PhysicalHashJoin(_) => PhysicalHashJoin::from_rel_node(rel_node)
362369
.unwrap()
363-
.dispatch_explain(),
370+
.dispatch_explain(meta_map),
364371
OptRelNodeTyp::SortOrder(_) => SortOrderExpr::from_rel_node(rel_node)
365372
.unwrap()
366-
.dispatch_explain(),
373+
.dispatch_explain(meta_map),
367374
OptRelNodeTyp::PhysicalEmptyRelation => PhysicalEmptyRelation::from_rel_node(rel_node)
368375
.unwrap()
369-
.dispatch_explain(),
376+
.dispatch_explain(meta_map),
370377
OptRelNodeTyp::PhysicalLimit => PhysicalLimit::from_rel_node(rel_node)
371378
.unwrap()
372-
.dispatch_explain(),
379+
.dispatch_explain(meta_map),
373380
OptRelNodeTyp::Between => BetweenExpr::from_rel_node(rel_node)
374381
.unwrap()
375-
.dispatch_explain(),
382+
.dispatch_explain(meta_map),
376383
OptRelNodeTyp::Cast => CastExpr::from_rel_node(rel_node)
377384
.unwrap()
378-
.dispatch_explain(),
385+
.dispatch_explain(meta_map),
379386
OptRelNodeTyp::Like => LikeExpr::from_rel_node(rel_node)
380387
.unwrap()
381-
.dispatch_explain(),
388+
.dispatch_explain(meta_map),
382389
OptRelNodeTyp::DataType(_) => DataTypeExpr::from_rel_node(rel_node)
383390
.unwrap()
384-
.dispatch_explain(),
391+
.dispatch_explain(meta_map),
385392
OptRelNodeTyp::InList => InListExpr::from_rel_node(rel_node)
386393
.unwrap()
387-
.dispatch_explain(),
394+
.dispatch_explain(meta_map),
388395
}
389396
}
390397

optd-datafusion-repr/src/plan_nodes/apply.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::fmt::Display;
33

44
use pretty_xmlish::Pretty;
55

6-
use optd_core::rel_node::RelNode;
6+
use optd_core::rel_node::{RelNode, RelNodeMetaMap};
77

88
use super::{Expr, JoinType, OptRelNode, OptRelNodeRef, OptRelNodeTyp, PlanNode};
99

@@ -48,14 +48,17 @@ impl OptRelNode for LogicalApply {
4848
}
4949
}
5050

51-
fn dispatch_explain(&self) -> Pretty<'static> {
51+
fn dispatch_explain(&self, meta_map: Option<&RelNodeMetaMap>) -> Pretty<'static> {
5252
Pretty::simple_record(
5353
"LogicalApply",
5454
vec![
5555
("typ", self.apply_type().to_string().into()),
56-
("cond", self.cond().explain()),
56+
("cond", self.cond().explain(meta_map)),
57+
],
58+
vec![
59+
self.left_child().explain(meta_map),
60+
self.right_child().explain(meta_map),
5761
],
58-
vec![self.left_child().explain(), self.right_child().explain()],
5962
)
6063
}
6164
}

optd-datafusion-repr/src/plan_nodes/empty_relation.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use pretty_xmlish::Pretty;
22

3-
use optd_core::rel_node::{RelNode, Value};
3+
use optd_core::rel_node::{RelNode, RelNodeMetaMap, Value};
4+
5+
use crate::explain::Insertable;
46

57
use super::{replace_typ, OptRelNode, OptRelNodeRef, OptRelNodeTyp, PlanNode};
68

@@ -19,7 +21,7 @@ impl OptRelNode for LogicalEmptyRelation {
1921
PlanNode::from_rel_node(rel_node).map(Self)
2022
}
2123

22-
fn dispatch_explain(&self) -> Pretty<'static> {
24+
fn dispatch_explain(&self, _meta_map: Option<&RelNodeMetaMap>) -> Pretty<'static> {
2325
Pretty::childless_record(
2426
"LogicalEmptyRelation",
2527
vec![("produce_one_row", self.produce_one_row().to_string().into())],
@@ -64,11 +66,12 @@ impl OptRelNode for PhysicalEmptyRelation {
6466
PlanNode::from_rel_node(rel_node).map(Self)
6567
}
6668

67-
fn dispatch_explain(&self) -> Pretty<'static> {
68-
Pretty::childless_record(
69-
"PhysicalEmptyRelation",
70-
vec![("produce_one_row", self.produce_one_row().to_string().into())],
71-
)
69+
fn dispatch_explain(&self, meta_map: Option<&RelNodeMetaMap>) -> Pretty<'static> {
70+
let mut fields = vec![("produce_one_row", self.produce_one_row().to_string().into())];
71+
if let Some(meta_map) = meta_map {
72+
fields = fields.with_meta(self.0.get_meta(meta_map));
73+
}
74+
Pretty::childless_record("PhysicalEmptyRelation", fields)
7275
}
7376
}
7477

0 commit comments

Comments
 (0)