Skip to content

Commit 825351d

Browse files
committed
Add plan parent-reduction rule API
Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
1 parent 55e7a8c commit 825351d

3 files changed

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

0 commit comments

Comments
 (0)