Skip to content

Commit 1588a7c

Browse files
joseph-isaacsclaude
andcommitted
Add zoned layout scan plan
Add a `Zoned` operator pairing data with the zone statistics summarising it, and lower both `vortex.zoned` and legacy `vortex.stats` layouts into it, since the two share a child shape. Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012obBhJ8oPZoBbKyeS79yMv Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
1 parent 265f6c2 commit 1588a7c

5 files changed

Lines changed: 174 additions & 0 deletions

File tree

vortex-layout/src/plan/lower.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,15 @@ use crate::layouts::list::OFFSETS_CHILD_INDEX;
2424
use crate::layouts::list::VALIDITY_CHILD_INDEX;
2525
use crate::layouts::struct_::Struct;
2626
use crate::layouts::struct_::StructLayout;
27+
use crate::layouts::zoned::LegacyStats;
28+
use crate::layouts::zoned::Zoned;
2729
use crate::plan::ConcatPlan;
2830
use crate::plan::ListPackPlan;
2931
use crate::plan::PackPlan;
3032
use crate::plan::PlanRef;
3133
use crate::plan::SegmentScanPlan;
3234
use crate::plan::TakePlan;
35+
use crate::plan::ZonedPlan;
3336

3437
/// Lowers `layout` into a physical plan.
3538
///
@@ -51,6 +54,9 @@ pub fn lower(layout: &LayoutRef) -> VortexResult<PlanRef> {
5154
if let Some(layout) = layout.as_opt::<List>() {
5255
return Ok(lower_list(layout)?.into_plan());
5356
}
57+
if layout.is::<Zoned>() || layout.is::<LegacyStats>() {
58+
return Ok(lower_zoned(layout)?.into_plan());
59+
}
5460
vortex_bail!(
5561
"No physical plan implementation for layout '{}'",
5662
layout.encoding_id()
@@ -143,3 +149,18 @@ fn lower_list(layout: &ListLayout) -> VortexResult<ListPackPlan> {
143149
validity,
144150
)
145151
}
152+
153+
fn lower_zoned(layout: &LayoutRef) -> VortexResult<ZonedPlan> {
154+
// Zoned and legacy stats layouts share a child shape: transparent data, auxiliary zones.
155+
let data = lower(
156+
&layout
157+
.slot(0)?
158+
.ok_or_else(|| vortex_err!("Zoned data child is absent"))?,
159+
)?;
160+
let zones = lower(
161+
&layout
162+
.slot(1)?
163+
.ok_or_else(|| vortex_err!("Zoned zones child is absent"))?,
164+
)?;
165+
Ok(ZonedPlan::new(data, zones))
166+
}

vortex-layout/src/plan/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ pub use plans::SegmentScanData;
5151
pub use plans::SegmentScanPlan;
5252
pub use plans::Take;
5353
pub use plans::TakePlan;
54+
pub use plans::Zoned;
55+
pub use plans::ZonedPlan;
5456
pub use plans::row_idx_dtype;
5557
pub use typed::DynPlan;
5658
pub use typed::Plan;

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod row_idx_partition;
1010
mod row_idx_values;
1111
mod segment_scan;
1212
mod take;
13+
mod zoned;
1314

1415
pub use concat::Concat;
1516
pub use concat::ConcatData;
@@ -43,3 +44,5 @@ pub use segment_scan::SegmentScanPlan;
4344
pub(crate) use take::ExpressionTakeRule;
4445
pub use take::Take;
4546
pub use take::TakePlan;
47+
pub use zoned::Zoned;
48+
pub use zoned::ZonedPlan;
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3+
4+
use std::borrow::Cow;
5+
6+
use vortex_array::EmptyMetadata;
7+
use vortex_array::dtype::DType;
8+
use vortex_error::VortexResult;
9+
use vortex_session::registry::CachedId;
10+
11+
use crate::plan::Plan;
12+
use crate::plan::PlanId;
13+
use crate::plan::PlanParts;
14+
use crate::plan::PlanRef;
15+
use crate::plan::PlanVTable;
16+
use crate::plan::check_child_count;
17+
18+
pub(crate) const DATA: usize = 0;
19+
pub(crate) const ZONES: usize = 1;
20+
21+
/// Reads data alongside the zone statistics summarising it.
22+
///
23+
/// This operator covers both `vortex.zoned` layouts and legacy `vortex.stats` layouts, which have
24+
/// the same physical child shape.
25+
#[derive(Clone, Debug)]
26+
pub struct Zoned;
27+
28+
/// A plan that pairs data with its zone statistics.
29+
pub type ZonedPlan = Plan<Zoned>;
30+
31+
impl ZonedPlan {
32+
/// Creates a zoned plan over `data` summarised by `zones`.
33+
pub fn new(data: PlanRef, zones: PlanRef) -> Self {
34+
let dtype: DType = data.dtype().clone();
35+
let row_count = data.row_count();
36+
PlanParts {
37+
vtable: Zoned,
38+
dtype,
39+
row_count,
40+
children: vec![data, zones],
41+
data: (),
42+
}
43+
.into_typed()
44+
}
45+
46+
/// Returns the plan producing the summarised data.
47+
pub fn data_plan(&self) -> &PlanRef {
48+
&self.children()[DATA]
49+
}
50+
51+
/// Returns the plan producing the zone statistics.
52+
pub fn zones_plan(&self) -> &PlanRef {
53+
&self.children()[ZONES]
54+
}
55+
}
56+
57+
impl PlanVTable for Zoned {
58+
type PlanData = ();
59+
type Metadata = EmptyMetadata;
60+
61+
fn id(&self) -> PlanId {
62+
static ID: CachedId = CachedId::new("vortex.plan.zoned");
63+
*ID
64+
}
65+
66+
fn metadata(_plan: &Plan<Self>) -> Option<Self::Metadata> {
67+
Some(EmptyMetadata)
68+
}
69+
70+
fn with_children(_plan: &Plan<Self>, mut children: Vec<PlanRef>) -> VortexResult<Plan<Self>> {
71+
check_child_count("Zoned", &children, 2)?;
72+
let zones = children.remove(ZONES);
73+
let data = children.remove(DATA);
74+
Ok(ZonedPlan::new(data, zones))
75+
}
76+
77+
fn child_name(_plan: &Plan<Self>, index: usize) -> Cow<'_, str> {
78+
match index {
79+
DATA => Cow::Borrowed("data"),
80+
ZONES => Cow::Borrowed("zones"),
81+
_ => Cow::Owned(format!("child[{index}]")),
82+
}
83+
}
84+
}

vortex-layout/src/plan/tests.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
// SPDX-FileCopyrightText: Copyright the Vortex contributors
33

44
use std::fmt;
5+
use std::num::NonZeroUsize;
56
use std::sync::Arc;
67

8+
use vortex_array::aggregate_fn::AggregateFnRef;
79
use vortex_array::dtype::DType;
810
use vortex_array::dtype::Nullability;
911
use vortex_array::dtype::PType;
@@ -22,6 +24,8 @@ use vortex_session::registry::CachedId;
2224
use vortex_session::registry::ReadContext;
2325

2426
use super::*;
27+
use crate::LayoutBuildContext;
28+
use crate::LayoutEncoding;
2529
use crate::LayoutRef;
2630
use crate::OwnedLayoutChildren;
2731
use crate::layouts::chunked::ChunkedLayout;
@@ -31,6 +35,8 @@ use crate::layouts::foreign::new_foreign_layout;
3135
use crate::layouts::list::ListLayout;
3236
use crate::layouts::row_idx::row_idx;
3337
use crate::layouts::struct_::StructLayout;
38+
use crate::layouts::zoned::LegacyStatsLayoutEncoding;
39+
use crate::layouts::zoned::ZonedLayout;
3440
use crate::segments::SegmentId;
3541

3642
fn primitive(ptype: PType, nullability: Nullability) -> DType {
@@ -852,3 +858,61 @@ fn nullable_struct_keeps_expression_above_parent_validity() -> VortexResult<()>
852858
assert!(eval.child_plan().is::<Pack>());
853859
Ok(())
854860
}
861+
862+
#[test]
863+
fn zoned_plan_exposes_data_and_zones() -> VortexResult<()> {
864+
let dtype = primitive(PType::I32, Nullability::NonNullable);
865+
let zones_dtype = DType::Struct(StructFields::empty(), Nullability::NonNullable);
866+
let zone_len = NonZeroUsize::new(3).ok_or_else(|| vortex_err!("zone length is zero"))?;
867+
let aggregate_fns: Arc<[AggregateFnRef]> = Vec::new().into();
868+
let layout = ZonedLayout::try_new(
869+
flat(5, dtype, 0),
870+
flat(2, zones_dtype, 1),
871+
zone_len,
872+
aggregate_fns,
873+
)?
874+
.into_layout();
875+
876+
let plan = make_plan(layout)?;
877+
assert!(plan.is::<Zoned>());
878+
insta::assert_snapshot!(plan.display_tree(), @"
879+
root: vortex.plan.zoned(i32, rows=5)
880+
data: vortex.plan.segment_scan(i32, rows=5)
881+
zones: vortex.plan.segment_scan({}, rows=2)
882+
");
883+
Ok(())
884+
}
885+
886+
#[test]
887+
fn legacy_stats_layout_uses_zoned_plan() -> VortexResult<()> {
888+
let dtype = primitive(PType::I32, Nullability::NonNullable);
889+
let zones_dtype = DType::Struct(StructFields::empty(), Nullability::NonNullable);
890+
let children = OwnedLayoutChildren::layout_children(vec![
891+
flat(5, dtype.clone(), 0),
892+
flat(2, zones_dtype, 1),
893+
]);
894+
let session = vortex_array::array_session();
895+
let read_ctx = ReadContext::new([]);
896+
let build_ctx = LayoutBuildContext {
897+
session: &session,
898+
array_read_ctx: &read_ctx,
899+
};
900+
let layout = LayoutEncoding::build(
901+
&LegacyStatsLayoutEncoding,
902+
&dtype,
903+
5,
904+
&3_u32.to_le_bytes(),
905+
Vec::new(),
906+
children.as_ref(),
907+
&build_ctx,
908+
)?;
909+
910+
let plan = make_plan(layout)?;
911+
assert!(plan.is::<Zoned>());
912+
insta::assert_snapshot!(plan.display_tree(), @"
913+
root: vortex.plan.zoned(i32, rows=5)
914+
data: vortex.plan.segment_scan(i32, rows=5)
915+
zones: vortex.plan.segment_scan({}, rows=2)
916+
");
917+
Ok(())
918+
}

0 commit comments

Comments
 (0)