Skip to content

Commit 7bb24b7

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

3 files changed

Lines changed: 163 additions & 0 deletions

File tree

vortex-layout/src/plan/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
66
mod children;
77
mod display;
8+
pub mod optimizer;
89
mod plans;
910

1011
use std::any::Any;
@@ -44,6 +45,8 @@ pub type PlanRef = Arc<dyn Plan>;
4445
/// Layout plans expose their optimizer-facing children in a stable logical order. Optional child
4546
/// slots count toward [`child_count`](Self::child_count) and are returned as `None` by
4647
/// [`child`](Self::child) when absent. Accessing a child may initialize and cache its plan.
48+
/// Parent-child rewrites are expressed as [`optimizer::PlanParentReduceRule`]s and collected in a
49+
/// static [`optimizer::PlanParentRuleSet`].
4750
pub trait Plan: Any + Send + Sync {
4851
/// Returns the display name of this plan kind.
4952
///
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+
15+
/// A metadata-only rewrite where a child plan rewrites its parent plan.
16+
///
17+
/// Rules return one rewrite without recursively optimizing the replacement. The plan optimizer
18+
/// owns traversal and drives further rewrites.
19+
pub trait PlanParentReduceRule<C: Plan>: Debug + Send + Sync + 'static {
20+
/// The concrete parent plan matched by this rule.
21+
type Parent: Plan;
22+
23+
/// Attempts to replace `parent` based on its child at `child_idx`.
24+
fn reduce_parent(
25+
&self,
26+
child: &C,
27+
parent: &Self::Parent,
28+
child_idx: usize,
29+
) -> VortexResult<Option<PlanRef>>;
30+
}
31+
32+
/// Type-erased interface used by [`PlanParentRuleSet`].
33+
pub trait DynPlanParentReduceRule: Debug + Send + Sync + 'static {
34+
/// Returns whether this rule supports the concrete child and parent plan types.
35+
fn matches(&self, child: &dyn Plan, parent: &dyn Plan) -> bool;
36+
37+
/// Attempts to replace `parent` based on `child` at `child_idx`.
38+
fn reduce_parent(
39+
&self,
40+
child: &dyn Plan,
41+
parent: &dyn Plan,
42+
child_idx: usize,
43+
) -> VortexResult<Option<PlanRef>>;
44+
}
45+
46+
/// Bridges a typed [`PlanParentReduceRule`] to a type-erased static registry.
47+
pub struct PlanParentReduceRuleAdapter<C, R> {
48+
rule: R,
49+
_child: PhantomData<fn() -> C>,
50+
}
51+
52+
impl<C, R> PlanParentReduceRuleAdapter<C, R> {
53+
/// Creates an adapter for a typed parent-child rule.
54+
pub const fn new(rule: R) -> Self {
55+
Self {
56+
rule,
57+
_child: PhantomData,
58+
}
59+
}
60+
}
61+
62+
impl<C, R> Debug for PlanParentReduceRuleAdapter<C, R>
63+
where
64+
C: Plan,
65+
R: PlanParentReduceRule<C>,
66+
{
67+
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68+
formatter
69+
.debug_struct("PlanParentReduceRuleAdapter")
70+
.field("parent", &type_name::<R::Parent>())
71+
.field("child", &type_name::<C>())
72+
.field("rule", &self.rule)
73+
.finish()
74+
}
75+
}
76+
77+
impl<C, R> DynPlanParentReduceRule for PlanParentReduceRuleAdapter<C, R>
78+
where
79+
C: Plan,
80+
R: PlanParentReduceRule<C>,
81+
{
82+
fn matches(&self, child: &dyn Plan, parent: &dyn Plan) -> bool {
83+
child.is::<C>() && parent.is::<R::Parent>()
84+
}
85+
86+
fn reduce_parent(
87+
&self,
88+
child: &dyn Plan,
89+
parent: &dyn Plan,
90+
child_idx: usize,
91+
) -> VortexResult<Option<PlanRef>> {
92+
let Some(child) = child.downcast_ref::<C>() else {
93+
return Ok(None);
94+
};
95+
let Some(parent) = parent.downcast_ref::<R::Parent>() else {
96+
return Ok(None);
97+
};
98+
self.rule.reduce_parent(child, parent, child_idx)
99+
}
100+
}
101+
102+
/// An ordered static collection of parent-child plan rewrite rules.
103+
pub struct PlanParentRuleSet {
104+
rules: &'static [&'static dyn DynPlanParentReduceRule],
105+
}
106+
107+
impl PlanParentRuleSet {
108+
/// Creates a rule set whose first successful rewrite wins.
109+
pub const fn new(rules: &'static [&'static dyn DynPlanParentReduceRule]) -> Self {
110+
Self { rules }
111+
}
112+
113+
/// Evaluates rules registered for the concrete `(parent, child)` pair.
114+
pub fn evaluate(
115+
&self,
116+
child: &PlanRef,
117+
parent: &PlanRef,
118+
child_idx: usize,
119+
) -> VortexResult<Option<PlanRef>> {
120+
for rule in self.rules {
121+
if !rule.matches(child.as_ref(), parent.as_ref()) {
122+
continue;
123+
}
124+
let Some(reduced) = rule.reduce_parent(child.as_ref(), parent.as_ref(), child_idx)?
125+
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)