1mod builder;
15mod pruning;
16mod reader;
17mod schema;
18pub mod writer;
19pub mod zone_map;
20
21use std::num::NonZeroUsize;
22use std::sync::Arc;
23
24pub(crate) use builder::AggregateStatsAccumulator;
25pub(crate) use builder::aggregate_partials;
26use prost::Message;
27pub use schema::MAX_IS_TRUNCATED;
28pub use schema::MIN_IS_TRUNCATED;
29use vortex_array::DeserializeMetadata;
30use vortex_array::SerializeMetadata;
31use vortex_array::aggregate_fn::AggregateFnRef;
32use vortex_array::dtype::DType;
33use vortex_array::dtype::TryFromBytes;
34use vortex_array::expr::stats::Stat;
35use vortex_array::stats::as_stat_bitset_bytes;
36use vortex_array::stats::stats_from_bitset_bytes;
37use vortex_error::VortexExpect;
38use vortex_error::VortexResult;
39use vortex_error::vortex_bail;
40use vortex_error::vortex_ensure;
41use vortex_error::vortex_ensure_eq;
42use vortex_error::vortex_panic;
43use vortex_session::VortexSession;
44use vortex_session::registry::CachedId;
45
46use crate::LayoutBuildContext;
47use crate::LayoutChildType;
48use crate::LayoutEncodingRef;
49use crate::LayoutId;
50use crate::LayoutReaderRef;
51use crate::LayoutRef;
52use crate::VTable;
53use crate::children::LayoutChildren;
54use crate::children::OwnedLayoutChildren;
55use crate::layouts::zoned::reader::ZonedReader;
56use crate::layouts::zoned::schema::AggregateSpecProto;
57use crate::layouts::zoned::schema::aggregate_specs_from_fns;
58use crate::layouts::zoned::schema::aggregate_stats_table_dtype;
59use crate::layouts::zoned::schema::legacy_stats_table_dtype;
60use crate::layouts::zoned::schema::try_aggregate_fns_from_specs;
61use crate::segments::SegmentId;
62use crate::segments::SegmentSource;
63use crate::vtable;
64
65vtable!(Zoned);
66vtable!(LegacyStats);
67
68fn zoned_reader(
73 layout: &ZonedLayout,
74 name: Arc<str>,
75 segment_source: Arc<dyn SegmentSource>,
76 session: &VortexSession,
77 ctx: &crate::LayoutReaderContext,
78) -> VortexResult<LayoutReaderRef> {
79 if layout.zone_len == 0 {
80 return layout.children.child(0, &layout.dtype)?.new_reader(
81 name,
82 segment_source,
83 session,
84 ctx,
85 );
86 }
87
88 Ok(Arc::new(ZonedReader::try_new(
89 layout.clone(),
90 name,
91 segment_source,
92 session.clone(),
93 ctx.clone(),
94 )?))
95}
96
97impl VTable for Zoned {
98 type Layout = ZonedLayout;
99 type Encoding = ZonedLayoutEncoding;
100 type Metadata = ZonedMetadata;
101
102 fn id(_encoding: &Self::Encoding) -> LayoutId {
103 static ID: CachedId = CachedId::new("vortex.zoned");
104 *ID
105 }
106
107 fn encoding(_layout: &Self::Layout) -> LayoutEncodingRef {
108 LayoutEncodingRef::new_ref(ZonedLayoutEncoding.as_ref())
109 }
110
111 fn row_count(layout: &Self::Layout) -> u64 {
112 layout.children.child_row_count(0)
113 }
114
115 fn dtype(layout: &Self::Layout) -> &DType {
116 &layout.dtype
117 }
118
119 fn metadata(layout: &Self::Layout) -> Self::Metadata {
120 ZonedMetadata {
121 zone_len: u32::try_from(layout.zone_len).vortex_expect("Invalid zone length"),
122 aggregate_specs: match &layout.zone_map_schema {
123 ZoneMapSchema::AggregateFns(aggregate_fns) => {
124 aggregate_specs_from_fns(aggregate_fns).vortex_expect(
125 "aggregate functions should be validated as serializable during build",
126 )
127 }
128 ZoneMapSchema::LegacyStats(_) => {
129 vortex_panic!("Cannot serialize legacy stats schema as vortex.zoned")
130 }
131 },
132 }
133 }
134
135 fn segment_ids(_layout: &Self::Layout) -> Vec<SegmentId> {
136 vec![]
137 }
138
139 fn nchildren(_layout: &Self::Layout) -> usize {
140 2
141 }
142
143 fn child(layout: &Self::Layout, idx: usize) -> VortexResult<LayoutRef> {
144 match idx {
145 0 => layout.children.child(0, layout.dtype()),
146 1 => layout.children.child(1, &layout.stats_table_dtype),
147 _ => vortex_bail!("Invalid child index: {}", idx),
148 }
149 }
150
151 fn child_type(_layout: &Self::Layout, idx: usize) -> LayoutChildType {
152 match idx {
153 0 => LayoutChildType::Transparent("data".into()),
154 1 => LayoutChildType::Auxiliary("zones".into()),
155 _ => vortex_panic!("Invalid child index: {}", idx),
156 }
157 }
158
159 fn new_reader(
160 layout: &Self::Layout,
161 name: Arc<str>,
162 segment_source: Arc<dyn SegmentSource>,
163 session: &VortexSession,
164 ctx: &crate::LayoutReaderContext,
165 ) -> VortexResult<LayoutReaderRef> {
166 zoned_reader(layout, name, segment_source, session, ctx)
167 }
168
169 fn build(
170 _encoding: &Self::Encoding,
171 dtype: &DType,
172 _row_count: u64,
173 metadata: &ZonedMetadata,
174 _segment_ids: Vec<SegmentId>,
175 children: &dyn LayoutChildren,
176 build_ctx: &LayoutBuildContext<'_>,
177 ) -> VortexResult<Self::Layout> {
178 vortex_ensure_eq!(
179 children.nchildren(),
180 2,
181 "ZonedLayout expects exactly 2 children (data, zones)"
182 );
183
184 let Some(aggregate_fns) =
187 try_aggregate_fns_from_specs(&metadata.aggregate_specs, build_ctx.session)?
188 else {
189 return Ok(ZonedLayout {
190 dtype: dtype.clone(),
191 children: children.to_arc(),
192 zone_len: 0,
193 zone_map_schema: ZoneMapSchema::AggregateFns(Arc::new([])),
194 stats_table_dtype: aggregate_stats_table_dtype(dtype, &[]),
195 });
196 };
197
198 aggregate_specs_from_fns(&aggregate_fns)?;
200 let stats_table_dtype = aggregate_stats_table_dtype(dtype, &aggregate_fns);
201
202 Ok(ZonedLayout {
203 dtype: dtype.clone(),
204 children: children.to_arc(),
205 zone_len: metadata.zone_len as usize,
206 zone_map_schema: ZoneMapSchema::AggregateFns(aggregate_fns),
207 stats_table_dtype,
208 })
209 }
210
211 fn with_children(layout: &mut Self::Layout, children: Vec<LayoutRef>) -> VortexResult<()> {
212 if children.len() != 2 {
213 vortex_bail!(
214 "ZonedLayout expects exactly 2 children (data, zones), got {}",
215 children.len()
216 );
217 }
218 layout.children = OwnedLayoutChildren::layout_children(children);
219 Ok(())
220 }
221}
222
223impl VTable for LegacyStats {
226 type Layout = LegacyStatsLayout;
227 type Encoding = LegacyStatsLayoutEncoding;
228 type Metadata = LegacyStatsMetadata;
229
230 fn id(_encoding: &Self::Encoding) -> LayoutId {
231 static ID: CachedId = CachedId::new("vortex.stats");
232 *ID
233 }
234
235 fn encoding(_layout: &Self::Layout) -> LayoutEncodingRef {
236 LayoutEncodingRef::new_ref(LegacyStatsLayoutEncoding.as_ref())
237 }
238
239 fn row_count(layout: &Self::Layout) -> u64 {
240 <Zoned as VTable>::row_count(&layout.0)
241 }
242
243 fn dtype(layout: &Self::Layout) -> &DType {
244 <Zoned as VTable>::dtype(&layout.0)
245 }
246
247 fn metadata(layout: &Self::Layout) -> Self::Metadata {
248 LegacyStatsMetadata {
249 zone_len: u32::try_from(layout.0.zone_len).vortex_expect("Invalid zone length"),
250 zone_map_schema: layout.0.zone_map_schema.clone(),
251 }
252 }
253
254 fn segment_ids(layout: &Self::Layout) -> Vec<SegmentId> {
255 <Zoned as VTable>::segment_ids(&layout.0)
256 }
257
258 fn nchildren(layout: &Self::Layout) -> usize {
259 <Zoned as VTable>::nchildren(&layout.0)
260 }
261
262 fn child(layout: &Self::Layout, idx: usize) -> VortexResult<LayoutRef> {
263 <Zoned as VTable>::child(&layout.0, idx)
264 }
265
266 fn child_type(layout: &Self::Layout, idx: usize) -> LayoutChildType {
267 <Zoned as VTable>::child_type(&layout.0, idx)
268 }
269
270 fn new_reader(
271 layout: &Self::Layout,
272 name: Arc<str>,
273 segment_source: Arc<dyn SegmentSource>,
274 session: &VortexSession,
275 ctx: &crate::LayoutReaderContext,
276 ) -> VortexResult<LayoutReaderRef> {
277 zoned_reader(&layout.0, name, segment_source, session, ctx)
278 }
279
280 fn build(
281 _encoding: &Self::Encoding,
282 dtype: &DType,
283 _row_count: u64,
284 metadata: &LegacyStatsMetadata,
285 _segment_ids: Vec<SegmentId>,
286 children: &dyn LayoutChildren,
287 _build_ctx: &LayoutBuildContext<'_>,
288 ) -> VortexResult<Self::Layout> {
289 vortex_ensure_eq!(
290 children.nchildren(),
291 2,
292 "LegacyStatsLayout expects exactly 2 children (data, zones)"
293 );
294 let stats_table_dtype = match &metadata.zone_map_schema {
295 ZoneMapSchema::LegacyStats(stats) => legacy_stats_table_dtype(dtype, stats),
296 ZoneMapSchema::AggregateFns(aggregate_fns) => {
297 aggregate_stats_table_dtype(dtype, aggregate_fns)
298 }
299 };
300 Ok(LegacyStatsLayout(ZonedLayout {
301 dtype: dtype.clone(),
302 children: children.to_arc(),
303 zone_len: metadata.zone_len as usize,
304 zone_map_schema: metadata.zone_map_schema.clone(),
305 stats_table_dtype,
306 }))
307 }
308
309 fn with_children(layout: &mut Self::Layout, children: Vec<LayoutRef>) -> VortexResult<()> {
310 <Zoned as VTable>::with_children(&mut layout.0, children)
311 }
312}
313
314#[derive(Debug)]
316pub struct ZonedLayoutEncoding;
317
318#[derive(Debug)]
320pub struct LegacyStatsLayoutEncoding;
321
322#[derive(Clone, Debug)]
328pub struct ZonedLayout {
329 dtype: DType,
330 children: Arc<dyn LayoutChildren>,
331 zone_len: usize,
332 zone_map_schema: ZoneMapSchema,
333 stats_table_dtype: DType,
334}
335
336#[derive(Clone, Debug)]
338pub struct LegacyStatsLayout(ZonedLayout);
339
340impl LegacyStatsLayout {
341 pub fn present_aggregates(&self) -> Arc<[String]> {
343 self.0.present_aggregates()
344 }
345}
346
347#[derive(Clone, Debug, PartialEq, Eq)]
348pub(crate) enum ZoneMapSchema {
349 LegacyStats(Arc<[Stat]>),
350 AggregateFns(Arc<[AggregateFnRef]>),
351}
352
353impl ZonedLayout {
354 pub fn try_new(
357 data: LayoutRef,
358 zones: LayoutRef,
359 zone_len: NonZeroUsize,
360 aggregate_fns: Arc<[AggregateFnRef]>,
361 ) -> VortexResult<Self> {
362 let expected_dtype = aggregate_stats_table_dtype(data.dtype(), &aggregate_fns);
363 if zones.dtype() != &expected_dtype {
364 vortex_bail!("Invalid zone map layout: zones dtype does not match expected dtype");
365 }
366
367 aggregate_specs_from_fns(&aggregate_fns)?;
369
370 Ok(Self {
371 dtype: data.dtype().clone(),
372 children: OwnedLayoutChildren::layout_children(vec![data, zones]),
373 zone_len: zone_len.get(),
374 zone_map_schema: ZoneMapSchema::AggregateFns(aggregate_fns),
375 stats_table_dtype: expected_dtype,
376 })
377 }
378
379 pub fn nzones(&self) -> usize {
380 usize::try_from(self.children.child_row_count(1)).vortex_expect("Invalid number of zones")
381 }
382
383 pub fn zone_len(&self) -> usize {
384 self.zone_len
385 }
386
387 pub fn present_aggregates(&self) -> Arc<[String]> {
389 match &self.zone_map_schema {
390 ZoneMapSchema::LegacyStats(stats) => stats
391 .iter()
392 .filter_map(Stat::aggregate_fn)
393 .map(|aggregate_fn| aggregate_fn.to_string())
394 .collect::<Vec<_>>()
395 .into(),
396 ZoneMapSchema::AggregateFns(aggregate_fns) => aggregate_fns
397 .iter()
398 .map(ToString::to_string)
399 .collect::<Vec<_>>()
400 .into(),
401 }
402 }
403
404 pub(super) fn aggregate_fns(
405 &self,
406 _session: &VortexSession,
407 ) -> VortexResult<Arc<[AggregateFnRef]>> {
408 match &self.zone_map_schema {
409 ZoneMapSchema::LegacyStats(stats) => Ok(stats
410 .iter()
411 .filter_map(Stat::aggregate_fn)
412 .collect::<Vec<_>>()
413 .into()),
414 ZoneMapSchema::AggregateFns(aggregate_fns) => Ok(Arc::clone(aggregate_fns)),
415 }
416 }
417
418 pub(super) fn stats_table_dtype_for(&self, aggregate_fns: &[AggregateFnRef]) -> DType {
419 if let ZoneMapSchema::LegacyStats(stats) = &self.zone_map_schema {
420 return legacy_stats_table_dtype(&self.dtype, stats);
421 }
422
423 aggregate_stats_table_dtype(&self.dtype, aggregate_fns)
424 }
425}
426
427#[derive(Debug, PartialEq, Eq, Clone)]
432pub struct ZonedMetadata {
433 pub(super) zone_len: u32,
434 pub(super) aggregate_specs: Arc<[AggregateSpecProto]>,
435}
436
437#[derive(Debug, PartialEq, Eq, Clone)]
439pub struct LegacyStatsMetadata {
440 pub(super) zone_len: u32,
441 pub(crate) zone_map_schema: ZoneMapSchema,
442}
443
444const ZONED_METADATA_PROTO_VERSION: u8 = 1;
445
446#[derive(Clone, PartialEq, Message)]
447struct ZonedMetadataProto {
448 #[prost(uint32, tag = "1")]
449 zone_len: u32,
450 #[prost(message, repeated, tag = "2")]
451 aggregate_specs: Vec<AggregateSpecProto>,
452}
453
454impl DeserializeMetadata for ZonedMetadata {
455 type Output = Self;
456
457 fn deserialize(metadata: &[u8]) -> VortexResult<Self::Output> {
458 let Some((&version, proto_bytes)) = metadata.split_first() else {
459 vortex_bail!("Zoned metadata missing protobuf version");
460 };
461
462 vortex_ensure!(
463 version == ZONED_METADATA_PROTO_VERSION,
464 "Unsupported zoned metadata version: {}",
465 version
466 );
467 vortex_ensure!(!proto_bytes.is_empty(), "Zoned metadata missing protobuf");
468
469 let proto = ZonedMetadataProto::decode(proto_bytes)?;
470 Ok(Self {
471 zone_len: proto.zone_len,
472 aggregate_specs: proto.aggregate_specs.into(),
473 })
474 }
475}
476
477impl SerializeMetadata for ZonedMetadata {
478 fn serialize(self) -> Vec<u8> {
479 let proto = ZonedMetadataProto {
480 zone_len: self.zone_len,
481 aggregate_specs: self.aggregate_specs.to_vec(),
482 };
483 let mut metadata = vec![ZONED_METADATA_PROTO_VERSION];
484 metadata.extend(proto.encode_to_vec());
485 metadata
486 }
487}
488
489impl DeserializeMetadata for LegacyStatsMetadata {
490 type Output = Self;
491
492 fn deserialize(metadata: &[u8]) -> VortexResult<Self::Output> {
493 vortex_ensure!(
494 metadata.len() >= 4,
495 "Legacy zoned metadata must contain at least 4 bytes for zone length, got {}",
496 metadata.len()
497 );
498
499 let zone_len = u32::try_from_le_bytes(&metadata[0..4])?;
503 let present_stats: Arc<[Stat]> = stats_from_bitset_bytes(&metadata[4..]).into();
504
505 Ok(Self {
506 zone_len,
507 zone_map_schema: ZoneMapSchema::LegacyStats(present_stats),
508 })
509 }
510}
511
512impl SerializeMetadata for LegacyStatsMetadata {
513 fn serialize(self) -> Vec<u8> {
514 match self.zone_map_schema {
515 ZoneMapSchema::LegacyStats(stats) => {
516 let mut metadata = self.zone_len.to_le_bytes().to_vec();
517 metadata.extend(as_stat_bitset_bytes(&stats));
518 metadata
519 }
520 ZoneMapSchema::AggregateFns(_) => {
521 vortex_panic!("Cannot serialize aggregate specs as legacy stats metadata")
522 }
523 }
524 }
525}
526
527#[cfg(test)]
528mod tests {
529 use std::panic;
530
531 use rstest::rstest;
532 use vortex_array::aggregate_fn::AggregateFnRef;
533 use vortex_array::aggregate_fn::AggregateFnVTableExt;
534 use vortex_array::aggregate_fn::NumericalAggregateOpts;
535 use vortex_array::aggregate_fn::fns::bounded_max::BoundedMax;
536 use vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxOptions;
537 use vortex_array::aggregate_fn::fns::max::Max;
538 use vortex_array::aggregate_fn::fns::min::Min;
539 use vortex_array::aggregate_fn::session::AggregateFnSession;
540 use vortex_array::dtype::DType;
541 use vortex_array::dtype::Nullability;
542 use vortex_array::dtype::PType;
543 use vortex_array::stats::as_stat_bitset_bytes;
544 use vortex_session::VortexSession;
545 use vortex_session::registry::ReadContext;
546
547 use super::*;
548 use crate::IntoLayout;
549 use crate::children::OwnedLayoutChildren;
550 use crate::layouts::flat::FlatLayout;
551 use crate::segments::SegmentId;
552
553 fn aggregate_spec(aggregate_fn: AggregateFnRef) -> AggregateSpecProto {
554 AggregateSpecProto::try_from_aggregate_fn(&aggregate_fn).unwrap()
555 }
556
557 #[rstest]
558 #[case(ZonedMetadata {
559 zone_len: u32::MAX,
560 aggregate_specs: Arc::new([]),
561 })]
562 #[case::min_max(ZonedMetadata {
563 zone_len: 314,
564 aggregate_specs: Arc::new([
565 aggregate_spec(Max.bind(NumericalAggregateOpts::skip_nans())),
566 aggregate_spec(Min.bind(NumericalAggregateOpts::skip_nans())),
567 ]),
568 })]
569 fn test_metadata_serialization(#[case] metadata: ZonedMetadata) {
570 let serialized = metadata.clone().serialize();
571 assert_eq!(serialized[0], ZONED_METADATA_PROTO_VERSION);
572 let deserialized = ZonedMetadata::deserialize(&serialized).unwrap();
573 assert_eq!(deserialized, metadata);
574 }
575
576 #[test]
577 fn test_metadata_serialization_preserves_aggregate_options() -> VortexResult<()> {
578 let aggregate_fn = BoundedMax.bind(BoundedMaxOptions {
579 max_bytes: unsafe { NonZeroUsize::new_unchecked(128) },
581 });
582 let metadata = ZonedMetadata {
583 zone_len: 314,
584 aggregate_specs: Arc::new([AggregateSpecProto::try_from_aggregate_fn(&aggregate_fn)?]),
585 };
586
587 let deserialized = ZonedMetadata::deserialize(&metadata.serialize())?;
588 let session = VortexSession::empty().with::<AggregateFnSession>();
589 let aggregate_fns = try_aggregate_fns_from_specs(&deserialized.aggregate_specs, &session)?
590 .expect("known aggregates resolve");
591
592 assert_eq!(aggregate_fns.as_ref(), std::slice::from_ref(&aggregate_fn));
593 Ok(())
594 }
595
596 #[test]
597 fn test_deserialize_legacy_stat_bitset_as_legacy_stats() {
598 let mut serialized = u32::MAX.to_le_bytes().to_vec();
599 serialized.extend(as_stat_bitset_bytes(&[
600 Stat::IsStrictSorted,
601 Stat::IsSorted,
602 Stat::Max,
603 ]));
604 let deserialized = LegacyStatsMetadata::deserialize(&serialized).unwrap();
605 let ZoneMapSchema::LegacyStats(legacy_stats) = deserialized.zone_map_schema else {
606 panic!("legacy bitset metadata should deserialize as legacy stats");
607 };
608
609 assert!(legacy_stats.is_sorted());
610 assert_eq!(
611 legacy_stats.as_ref(),
612 &[Stat::IsSorted, Stat::IsStrictSorted, Stat::Max]
613 );
614 }
615
616 #[rstest]
617 #[case::empty(vec![])]
618 #[case::unsupported_version(vec![0])]
619 #[case::missing_proto(vec![ZONED_METADATA_PROTO_VERSION])]
620 #[case::malformed_proto(vec![ZONED_METADATA_PROTO_VERSION, 0])]
621 fn test_deserialize_short_metadata_errors(#[case] metadata: Vec<u8>) {
622 assert!(ZonedMetadata::deserialize(&metadata).is_err());
623 }
624
625 #[test]
626 fn test_deserialize_short_metadata_returns_error_not_panic() {
627 let result = panic::catch_unwind(|| ZonedMetadata::deserialize(&[]));
628 assert!(
629 result.is_ok(),
630 "deserialize should return an error, not panic"
631 );
632 assert!(result.unwrap().is_err());
633 }
634
635 #[test]
636 fn test_deserialize_zero_zone_len_is_allowed_for_backcompat() {
637 let metadata = 0u32.to_le_bytes();
638 let deserialized = LegacyStatsMetadata::deserialize(&metadata).unwrap();
639 assert_eq!(deserialized.zone_len, 0);
640 let ZoneMapSchema::LegacyStats(legacy_stats) = deserialized.zone_map_schema else {
641 panic!("legacy bitset metadata should deserialize as legacy stats");
642 };
643 assert!(legacy_stats.is_empty());
644 }
645
646 #[test]
647 fn test_build_allows_zero_zone_len_for_backcompat() -> VortexResult<()> {
648 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
649 let read_ctx = ReadContext::new([]);
650 let children = OwnedLayoutChildren::layout_children(vec![
651 FlatLayout::new(0, dtype.clone(), SegmentId::from(0), read_ctx.clone()).into_layout(),
652 FlatLayout::new(
653 0,
654 legacy_stats_table_dtype(&dtype, &[]),
655 SegmentId::from(1),
656 read_ctx,
657 )
658 .into_layout(),
659 ]);
660 let session = vortex_array::array_session();
661 let build_read_ctx = ReadContext::new([]);
662 let build_ctx = LayoutBuildContext {
663 session: &session,
664 array_read_ctx: &build_read_ctx,
665 };
666
667 let layout = <LegacyStats as VTable>::build(
668 &LegacyStatsLayoutEncoding,
669 &dtype,
670 0,
671 &LegacyStatsMetadata {
672 zone_len: 0,
673 zone_map_schema: ZoneMapSchema::LegacyStats(Arc::new([])),
674 },
675 vec![],
676 children.as_ref(),
677 &build_ctx,
678 )?;
679
680 assert_eq!(layout.0.zone_len, 0);
681 Ok(())
682 }
683
684 #[test]
685 fn test_build_rejects_invalid_child_count() {
686 let metadata = ZonedMetadata {
687 zone_len: 3,
688 aggregate_specs: Arc::new([]),
689 };
690 let children = OwnedLayoutChildren::layout_children(vec![]);
691 let session = vortex_array::array_session();
692 let build_read_ctx = ReadContext::new([]);
693 let build_ctx = LayoutBuildContext {
694 session: &session,
695 array_read_ctx: &build_read_ctx,
696 };
697
698 let result = <Zoned as VTable>::build(
699 &ZonedLayoutEncoding,
700 &DType::Primitive(PType::I32, Nullability::NonNullable),
701 0,
702 &metadata,
703 vec![],
704 children.as_ref(),
705 &build_ctx,
706 );
707
708 assert!(result.is_err());
709 }
710
711 #[test]
712 fn test_build_unknown_aggregate_disables_pruning_when_allowed() -> VortexResult<()> {
713 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
714 let read_ctx = ReadContext::new([]);
715 let children = OwnedLayoutChildren::layout_children(vec![
716 FlatLayout::new(0, dtype.clone(), SegmentId::from(0), read_ctx.clone()).into_layout(),
717 FlatLayout::new(0, dtype.clone(), SegmentId::from(1), read_ctx).into_layout(),
718 ]);
719 let session = vortex_array::array_session();
720 session.allow_unknown();
721 let build_read_ctx = ReadContext::new([]);
722 let build_ctx = LayoutBuildContext {
723 session: &session,
724 array_read_ctx: &build_read_ctx,
725 };
726
727 let metadata = ZonedMetadata {
728 zone_len: 8,
729 aggregate_specs: Arc::new([AggregateSpecProto::new_unknown("vortex.test.unknown")]),
730 };
731
732 let layout = <Zoned as VTable>::build(
733 &ZonedLayoutEncoding,
734 &dtype,
735 0,
736 &metadata,
737 vec![],
738 children.as_ref(),
739 &build_ctx,
740 )?;
741
742 assert_eq!(layout.zone_len, 0);
745 Ok(())
746 }
747
748 #[test]
749 fn test_build_unknown_aggregate_errors_without_allow_unknown() {
750 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
751 let read_ctx = ReadContext::new([]);
752 let children = OwnedLayoutChildren::layout_children(vec![
753 FlatLayout::new(0, dtype.clone(), SegmentId::from(0), read_ctx.clone()).into_layout(),
754 FlatLayout::new(0, dtype.clone(), SegmentId::from(1), read_ctx).into_layout(),
755 ]);
756 let session = vortex_array::array_session();
757 let build_read_ctx = ReadContext::new([]);
758 let build_ctx = LayoutBuildContext {
759 session: &session,
760 array_read_ctx: &build_read_ctx,
761 };
762
763 let metadata = ZonedMetadata {
764 zone_len: 8,
765 aggregate_specs: Arc::new([AggregateSpecProto::new_unknown("vortex.test.unknown")]),
766 };
767
768 let result = <Zoned as VTable>::build(
769 &ZonedLayoutEncoding,
770 &dtype,
771 0,
772 &metadata,
773 vec![],
774 children.as_ref(),
775 &build_ctx,
776 );
777
778 assert!(result.is_err());
779 }
780}