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