Skip to content

Commit 6f8023d

Browse files
committed
Avoid expanding unreferenced struct plan fields
Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
1 parent 1bdf135 commit 6f8023d

2 files changed

Lines changed: 75 additions & 15 deletions

File tree

vortex-layout/src/plan/plans/struct_.rs

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -245,8 +245,7 @@ impl PlanParentReduceRule<StructPlan> for ExpressionStructRule {
245245
.get(&ExactBoundExpr(expression.clone()))
246246
.vortex_expect("Bound expression missing free-field annotations")
247247
.clone();
248-
let expanded_root = expanded_struct_root(&child.dtype, fields)?;
249-
let expanded = expand_struct_root(expression.clone(), &expanded_root, fields)?;
248+
let expanded = expand_struct_root(expression.clone(), fields)?;
250249
let partitioned =
251250
partition_bound(expanded.clone(), make_bound_free_field_annotator(fields))?;
252251
if partitioned.partition_names.is_empty() {
@@ -368,14 +367,13 @@ fn expanded_struct_root(
368367

369368
fn expand_struct_root(
370369
expression: BoundExpression,
371-
expanded_root: &BoundExpression,
372370
fields: &StructFields,
373371
) -> VortexResult<BoundExpression> {
374372
Ok(expression
375373
.transform_down(|node| {
376374
if node.is_root() {
377375
return Ok(Transformed {
378-
value: expanded_root.clone(),
376+
value: expanded_struct_root(node.dtype(), fields)?,
379377
changed: true,
380378
order: TraversalOrder::Skip,
381379
});
@@ -392,28 +390,23 @@ fn expand_struct_root(
392390
return Ok(Transformed::no(node));
393391
}
394392

395-
if let Some(field_name) = scalar_fn.as_opt::<GetItem>() {
396-
let index = fields.find(field_name).ok_or_else(|| {
397-
vortex_err!("Field {field_name} not found while expanding struct root")
398-
})?;
393+
if scalar_fn.is::<GetItem>() {
399394
return Ok(Transformed {
400-
value: expanded_root.children()[index].clone(),
401-
changed: true,
395+
value: node,
396+
changed: false,
402397
order: TraversalOrder::Skip,
403398
});
404399
}
405400

406401
if let Some(selection) = scalar_fn.as_opt::<Select>() {
407402
let names = selection.normalize_to_included_fields(fields.names())?;
403+
let root = node.children()[0].clone();
408404
let children = names
409405
.iter()
410406
.map(|name| {
411-
let index = fields.find(name).vortex_expect(
412-
"normalized selection fields must exist in the struct root",
413-
);
414-
expanded_root.children()[index].clone()
407+
BoundExpression::try_new(GetItem.bind(name.clone()), [root.clone()])
415408
})
416-
.collect();
409+
.collect::<VortexResult<Vec<_>>>()?;
417410
return Ok(Transformed {
418411
value: bound_pack(names, children)?,
419412
changed: true,

vortex-layout/src/plan/tests.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ use vortex_array::expr::gt;
2727
use vortex_array::expr::is_null;
2828
use vortex_array::expr::lit;
2929
use vortex_array::expr::root;
30+
use vortex_array::expr::select;
31+
use vortex_array::expr::select_exclude;
3032
use vortex_error::VortexResult;
3133
use vortex_error::vortex_err;
3234
use vortex_io::runtime::single::block_on;
@@ -282,6 +284,71 @@ fn struct_plan_optimization_visits_all_fields() -> VortexResult<()> {
282284
Ok(())
283285
}
284286

287+
#[test]
288+
fn struct_select_preserves_struct_output() -> VortexResult<()> {
289+
let field_dtype = primitive(PType::I32, Nullability::NonNullable);
290+
let layout = StructLayout::new(
291+
3,
292+
DType::Struct(
293+
StructFields::from_iter([("a", field_dtype.clone()), ("b", field_dtype.clone())]),
294+
Nullability::NonNullable,
295+
),
296+
vec![
297+
flat(3, field_dtype.clone(), 0),
298+
flat(3, field_dtype.clone(), 1),
299+
],
300+
)
301+
.into_layout();
302+
303+
let optimized = make_expression_plan(select(["a"], root()), make_plan(layout)?)?.optimize()?;
304+
305+
assert_eq!(
306+
optimized.dtype(),
307+
&DType::Struct(
308+
StructFields::from_iter([("a", field_dtype)]),
309+
Nullability::NonNullable,
310+
)
311+
);
312+
insta::assert_snapshot!(optimized.tree_display(), @r"
313+
root: ExpressionPlan({a=i32}, rows=3) expr=pack(a: $)
314+
child: FlatPlan(i32, rows=3)
315+
");
316+
Ok(())
317+
}
318+
319+
#[test]
320+
fn struct_select_exclude_preserves_struct_output() -> VortexResult<()> {
321+
let field_dtype = primitive(PType::I32, Nullability::NonNullable);
322+
let layout = StructLayout::new(
323+
3,
324+
DType::Struct(
325+
StructFields::from_iter([("a", field_dtype.clone()), ("b", field_dtype.clone())]),
326+
Nullability::NonNullable,
327+
),
328+
vec![
329+
flat(3, field_dtype.clone(), 0),
330+
flat(3, field_dtype.clone(), 1),
331+
],
332+
)
333+
.into_layout();
334+
335+
let optimized =
336+
make_expression_plan(select_exclude(["b"], root()), make_plan(layout)?)?.optimize()?;
337+
338+
assert_eq!(
339+
optimized.dtype(),
340+
&DType::Struct(
341+
StructFields::from_iter([("a", field_dtype)]),
342+
Nullability::NonNullable,
343+
)
344+
);
345+
insta::assert_snapshot!(optimized.tree_display(), @r"
346+
root: ExpressionPlan({a=i32}, rows=3) expr=pack(a: $)
347+
child: FlatPlan(i32, rows=3)
348+
");
349+
Ok(())
350+
}
351+
285352
#[test]
286353
fn chunked_plan_optimization_visits_all_chunks() -> VortexResult<()> {
287354
let dtype = primitive(PType::I32, Nullability::NonNullable);

0 commit comments

Comments
 (0)