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