Skip to content

Commit 15deec3

Browse files
committed
Add plan parent-reduction rule API
Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
1 parent b18397d commit 15deec3

3 files changed

Lines changed: 161 additions & 0 deletions

File tree

vortex-layout/src/plan/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ mod children;
1313
mod display;
1414
mod lower;
1515
mod optimize;
16+
pub mod optimizer;
1617
mod plans;
1718
mod typed;
1819
mod vtable;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3+
4+
//! Static parent-child rewrite rules for physical plans.
5+
6+
mod rules;
7+
8+
pub use rules::DynPlanParentReduceRule;
9+
pub use rules::PlanParentReduceRule;
10+
pub use rules::PlanParentReduceRuleAdapter;
11+
pub use rules::PlanParentRuleSet;
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3+
4+
//! Typed and type-erased interfaces for parent-child plan rewrites.
5+
6+
use std::any::type_name;
7+
use std::fmt::Debug;
8+
use std::marker::PhantomData;
9+
10+
use vortex_error::VortexResult;
11+
12+
use crate::plan::Plan;
13+
use crate::plan::PlanRef;
14+
use crate::plan::PlanVTable;
15+
16+
/// A metadata-only rewrite where a child plan rewrites its parent plan.
17+
///
18+
/// Rules return one rewrite without recursively optimizing the replacement. The plan optimizer
19+
/// owns traversal and drives further rewrites.
20+
pub trait PlanParentReduceRule<C: PlanVTable>: Debug + Send + Sync + 'static {
21+
/// The concrete parent operator matched by this rule.
22+
type Parent: PlanVTable;
23+
24+
/// Attempts to replace `parent` based on its child at `child_idx`.
25+
fn reduce_parent(
26+
&self,
27+
child: &Plan<C>,
28+
parent: &Plan<Self::Parent>,
29+
child_idx: usize,
30+
) -> VortexResult<Option<PlanRef>>;
31+
}
32+
33+
/// Type-erased interface used by [`PlanParentRuleSet`].
34+
pub trait DynPlanParentReduceRule: Debug + Send + Sync + 'static {
35+
/// Returns whether this rule supports the concrete child and parent operators.
36+
fn matches(&self, child: &PlanRef, parent: &PlanRef) -> bool;
37+
38+
/// Attempts to replace `parent` based on `child` at `child_idx`.
39+
fn reduce_parent(
40+
&self,
41+
child: &PlanRef,
42+
parent: &PlanRef,
43+
child_idx: usize,
44+
) -> VortexResult<Option<PlanRef>>;
45+
}
46+
47+
/// Bridges a typed [`PlanParentReduceRule`] to a type-erased static registry.
48+
pub struct PlanParentReduceRuleAdapter<C, R> {
49+
rule: R,
50+
_child: PhantomData<fn() -> C>,
51+
}
52+
53+
impl<C, R> PlanParentReduceRuleAdapter<C, R> {
54+
/// Creates an adapter for a typed parent-child rule.
55+
pub const fn new(rule: R) -> Self {
56+
Self {
57+
rule,
58+
_child: PhantomData,
59+
}
60+
}
61+
}
62+
63+
impl<C, R> Debug for PlanParentReduceRuleAdapter<C, R>
64+
where
65+
C: PlanVTable,
66+
R: PlanParentReduceRule<C>,
67+
{
68+
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69+
formatter
70+
.debug_struct("PlanParentReduceRuleAdapter")
71+
.field("parent", &type_name::<R::Parent>())
72+
.field("child", &type_name::<C>())
73+
.field("rule", &self.rule)
74+
.finish()
75+
}
76+
}
77+
78+
impl<C, R> DynPlanParentReduceRule for PlanParentReduceRuleAdapter<C, R>
79+
where
80+
C: PlanVTable,
81+
R: PlanParentReduceRule<C>,
82+
{
83+
fn matches(&self, child: &PlanRef, parent: &PlanRef) -> bool {
84+
child.is::<C>() && parent.is::<R::Parent>()
85+
}
86+
87+
fn reduce_parent(
88+
&self,
89+
child: &PlanRef,
90+
parent: &PlanRef,
91+
child_idx: usize,
92+
) -> VortexResult<Option<PlanRef>> {
93+
let Some(child) = child.as_opt::<C>() else {
94+
return Ok(None);
95+
};
96+
let Some(parent) = parent.as_opt::<R::Parent>() else {
97+
return Ok(None);
98+
};
99+
self.rule.reduce_parent(child, parent, child_idx)
100+
}
101+
}
102+
103+
/// An ordered static collection of parent-child plan rewrite rules.
104+
pub struct PlanParentRuleSet {
105+
rules: &'static [&'static dyn DynPlanParentReduceRule],
106+
}
107+
108+
impl PlanParentRuleSet {
109+
/// Creates a rule set whose first successful rewrite wins.
110+
pub const fn new(rules: &'static [&'static dyn DynPlanParentReduceRule]) -> Self {
111+
Self { rules }
112+
}
113+
114+
/// Evaluates rules registered for the concrete `(parent, child)` pair.
115+
pub fn evaluate(
116+
&self,
117+
child: &PlanRef,
118+
parent: &PlanRef,
119+
child_idx: usize,
120+
) -> VortexResult<Option<PlanRef>> {
121+
for rule in self.rules {
122+
if !rule.matches(child, parent) {
123+
continue;
124+
}
125+
let Some(reduced) = rule.reduce_parent(child, parent, child_idx)? else {
126+
continue;
127+
};
128+
129+
#[cfg(debug_assertions)]
130+
{
131+
vortex_error::vortex_ensure!(
132+
reduced.row_count() == parent.row_count(),
133+
"Plan rewrite from {rule:?} changed row count from {} to {}",
134+
parent.row_count(),
135+
reduced.row_count()
136+
);
137+
vortex_error::vortex_ensure!(
138+
reduced.dtype() == parent.dtype(),
139+
"Plan rewrite from {rule:?} changed dtype from {} to {}",
140+
parent.dtype(),
141+
reduced.dtype()
142+
);
143+
}
144+
145+
return Ok(Some(reduced));
146+
}
147+
Ok(None)
148+
}
149+
}

0 commit comments

Comments
 (0)