Skip to content

Commit ce79a77

Browse files
committed
Add plan optimizer rules and push expressions
Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
1 parent a0a0b20 commit ce79a77

12 files changed

Lines changed: 1260 additions & 13 deletions

File tree

vortex-array/src/expr/transform/bound_partition.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use crate::expr::traversal::NodeExt;
2828
use crate::expr::traversal::NodeRewriter;
2929
use crate::expr::traversal::Transformed;
3030
use crate::expr::traversal::TraversalOrder;
31+
use crate::scalar_fn::fns::get_item::GetItem;
3132

3233
/// Partition an expression into sub-expressions that are uniquely associated with an annotation.
3334
/// A root expression is also returned that can be used to recombine the results of the partitions
@@ -300,6 +301,46 @@ fn replace_root_dtype(expr: BoundExpression, root_dtype: DType) -> VortexResult<
300301
.into_inner())
301302
}
302303

304+
/// Rewrites partition accessors in `expression` to read from a partitioned root.
305+
pub fn rewrite_partition_root(
306+
expression: BoundExpression,
307+
root_dtype: DType,
308+
collapsed: &[(FieldName, FieldName)],
309+
) -> VortexResult<BoundExpression> {
310+
Ok(expression
311+
.transform_down(|node| {
312+
if let Some(value_name) = node.as_opt::<GetItem>() {
313+
let partition_access = &node.children()[0];
314+
if let Some(partition_name) = partition_access.as_opt::<GetItem>()
315+
&& partition_access.children()[0].is_root()
316+
&& collapsed.iter().any(|(partition, value)| {
317+
partition == partition_name && value == value_name
318+
})
319+
{
320+
return Ok(Transformed {
321+
value: get_item(
322+
partition_name.clone(),
323+
BoundExpression::new_root(root_dtype.clone()),
324+
),
325+
changed: true,
326+
order: TraversalOrder::Skip,
327+
});
328+
}
329+
}
330+
331+
if node.is_root() {
332+
Ok(Transformed {
333+
value: BoundExpression::new_root(root_dtype.clone()),
334+
changed: true,
335+
order: TraversalOrder::Skip,
336+
})
337+
} else {
338+
Ok(Transformed::no(node))
339+
}
340+
})?
341+
.into_inner())
342+
}
343+
303344
#[cfg(test)]
304345
mod tests {
305346
use rstest::fixture;

vortex-layout/src/plan/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ mod children;
1111
mod display;
1212
mod lower;
1313
mod optimize;
14+
pub mod optimizer;
1415
mod plans;
1516
mod typed;
1617
mod vtable;

vortex-layout/src/plan/optimize.rs

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,35 @@
11
// SPDX-License-Identifier: Apache-2.0
22
// SPDX-FileCopyrightText: Copyright the Vortex contributors
33

4-
//! Generic bottom-up optimization over physical plans.
4+
//! Plan optimization.
5+
//!
6+
//! The optimizer applies static rewrites top-down, optimizes children, then retries rewrites
7+
//! exposed by the optimized children.
58
69
use vortex_error::VortexResult;
710

8-
use crate::plan::Eval;
911
use crate::plan::PlanRef;
12+
use crate::plan::optimizer::reduce_parent;
13+
use crate::plan::optimizer::reduce_plan;
14+
15+
fn reduce(plan: &PlanRef) -> VortexResult<Option<PlanRef>> {
16+
if let Some(rewritten) = reduce_plan(plan)? {
17+
return Ok(Some(rewritten));
18+
}
19+
for child_idx in 0..plan.child_count() {
20+
if let Some(rewritten) = reduce_parent(plan, child_idx)? {
21+
return Ok(Some(rewritten));
22+
}
23+
}
24+
Ok(None)
25+
}
1026

1127
/// Optimizes `plan`, preserving its dtype and row domain.
1228
pub fn optimize(plan: PlanRef) -> VortexResult<PlanRef> {
29+
if let Some(rewritten) = reduce(&plan)? {
30+
return optimize(rewritten);
31+
}
32+
1333
let mut children = Vec::with_capacity(plan.child_count());
1434
let mut changed = false;
1535
for child in plan.children().iter() {
@@ -19,17 +39,13 @@ pub fn optimize(plan: PlanRef) -> VortexResult<PlanRef> {
1939
children.push(optimized);
2040
}
2141

22-
let plan = if changed {
23-
plan.with_children(children)?
24-
} else {
25-
plan
26-
};
27-
28-
let Some(eval) = plan.as_opt::<Eval>() else {
42+
if !changed {
2943
return Ok(plan);
30-
};
31-
if eval.expression().is_root() {
32-
return eval.child_plan();
44+
}
45+
46+
let plan = plan.with_children(children)?;
47+
if let Some(rewritten) = reduce(&plan)? {
48+
return optimize(rewritten);
3349
}
3450
Ok(plan)
3551
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3+
4+
//! Static rewrite rules for physical plans.
5+
6+
mod rules;
7+
8+
pub use rules::DynPlanParentReduceRule;
9+
pub use rules::DynPlanReduceRule;
10+
pub use rules::PlanParentReduceRule;
11+
pub use rules::PlanParentReduceRuleAdapter;
12+
pub use rules::PlanParentRuleSet;
13+
pub use rules::PlanReduceRule;
14+
pub use rules::PlanReduceRuleAdapter;
15+
pub use rules::PlanRuleSet;
16+
use vortex_error::VortexResult;
17+
18+
use super::Concat;
19+
use super::Eval;
20+
use super::Pack;
21+
use super::PlanRef;
22+
use super::RowIdx;
23+
use super::Take;
24+
use super::plans::EvalIdentityRule;
25+
use super::plans::ExpressionConcatRule;
26+
use super::plans::ExpressionPackRule;
27+
use super::plans::ExpressionRowIdxRule;
28+
use super::plans::ExpressionTakeRule;
29+
30+
static EVAL_IDENTITY_RULE: PlanReduceRuleAdapter<Eval, EvalIdentityRule> =
31+
PlanReduceRuleAdapter::new(EvalIdentityRule);
32+
33+
static PLAN_RULES: PlanRuleSet = PlanRuleSet::new(&[&EVAL_IDENTITY_RULE]);
34+
35+
static EXPRESSION_CONCAT_RULE: PlanParentReduceRuleAdapter<Concat, ExpressionConcatRule> =
36+
PlanParentReduceRuleAdapter::new(ExpressionConcatRule);
37+
static EXPRESSION_TAKE_RULE: PlanParentReduceRuleAdapter<Take, ExpressionTakeRule> =
38+
PlanParentReduceRuleAdapter::new(ExpressionTakeRule);
39+
static EXPRESSION_ROW_IDX_RULE: PlanParentReduceRuleAdapter<RowIdx, ExpressionRowIdxRule> =
40+
PlanParentReduceRuleAdapter::new(ExpressionRowIdxRule);
41+
static EXPRESSION_PACK_RULE: PlanParentReduceRuleAdapter<Pack, ExpressionPackRule> =
42+
PlanParentReduceRuleAdapter::new(ExpressionPackRule);
43+
44+
static PARENT_RULES: PlanParentRuleSet = PlanParentRuleSet::new(&[
45+
&EXPRESSION_CONCAT_RULE,
46+
&EXPRESSION_TAKE_RULE,
47+
&EXPRESSION_ROW_IDX_RULE,
48+
&EXPRESSION_PACK_RULE,
49+
]);
50+
51+
/// Attempts a static rewrite for `plan`.
52+
pub(crate) fn reduce_plan(plan: &PlanRef) -> VortexResult<Option<PlanRef>> {
53+
PLAN_RULES.evaluate(plan)
54+
}
55+
56+
/// Attempts a static rewrite for `parent` and its child at `child_idx`.
57+
pub(crate) fn reduce_parent(parent: &PlanRef, child_idx: usize) -> VortexResult<Option<PlanRef>> {
58+
let Some(child) = parent.child(child_idx)? else {
59+
return Ok(None);
60+
};
61+
PARENT_RULES.evaluate(&child, parent, child_idx)
62+
}

0 commit comments

Comments
 (0)