1use crate::{
24 data_type::{ByteArray, FixedLenByteArray},
25 errors::{ParquetError, Result},
26 parquet_thrift::{
27 ElementType, FieldType, ThriftCompactOutputProtocol, WriteThrift, WriteThriftField,
28 },
29};
30use std::ops::Deref;
31
32use crate::{
33 basic::BoundaryOrder,
34 data_type::{Int96, private::ParquetValueType},
35 file::page_index::index_reader::ThriftColumnIndex,
36};
37
38#[derive(Debug, Clone, PartialEq)]
40pub struct ColumnIndex {
41 pub(crate) null_pages: Vec<bool>,
42 pub(crate) boundary_order: BoundaryOrder,
43 pub(crate) null_counts: Option<Vec<i64>>,
44 pub(crate) repetition_level_histograms: Option<Vec<i64>>,
45 pub(crate) definition_level_histograms: Option<Vec<i64>>,
46 pub(crate) nan_counts: Option<Vec<i64>>,
47}
48
49impl ColumnIndex {
50 pub fn num_pages(&self) -> u64 {
52 self.null_pages.len() as u64
53 }
54
55 pub fn null_count(&self, idx: usize) -> Option<i64> {
59 self.null_counts.as_ref().map(|nc| nc[idx])
60 }
61
62 pub fn nan_count(&self, idx: usize) -> Option<i64> {
66 self.nan_counts.as_ref().map(|nc| nc[idx])
67 }
68
69 pub fn repetition_level_histogram(&self, idx: usize) -> Option<&[i64]> {
71 if let Some(rep_hists) = self.repetition_level_histograms.as_ref() {
72 let num_lvls = rep_hists.len() / self.num_pages() as usize;
73 let start = num_lvls * idx;
74 Some(&rep_hists[start..start + num_lvls])
75 } else {
76 None
77 }
78 }
79
80 pub fn definition_level_histogram(&self, idx: usize) -> Option<&[i64]> {
82 if let Some(def_hists) = self.definition_level_histograms.as_ref() {
83 let num_lvls = def_hists.len() / self.num_pages() as usize;
84 let start = num_lvls * idx;
85 Some(&def_hists[start..start + num_lvls])
86 } else {
87 None
88 }
89 }
90
91 pub fn is_null_page(&self, idx: usize) -> bool {
93 self.null_pages[idx]
94 }
95}
96
97#[derive(Debug, Clone, PartialEq)]
99pub struct PrimitiveColumnIndex<T> {
100 pub(crate) column_index: ColumnIndex,
101 pub(crate) min_values: Vec<T>,
102 pub(crate) max_values: Vec<T>,
103}
104
105impl<T: ParquetValueType> PrimitiveColumnIndex<T> {
106 #[expect(clippy::too_many_arguments)]
107 pub(crate) fn try_new(
108 null_pages: Vec<bool>,
109 boundary_order: BoundaryOrder,
110 null_counts: Option<Vec<i64>>,
111 nan_counts: Option<Vec<i64>>,
112 repetition_level_histograms: Option<Vec<i64>>,
113 definition_level_histograms: Option<Vec<i64>>,
114 min_bytes: Vec<&[u8]>,
115 max_bytes: Vec<&[u8]>,
116 ) -> Result<Self> {
117 let len = null_pages.len();
118
119 if min_bytes.len() != len || max_bytes.len() != len {
120 return Err(ParquetError::General(format!(
121 "ColumnIndex min/max length mismatch: expected {len}, got min={} max={}",
122 min_bytes.len(),
123 max_bytes.len()
124 )));
125 }
126 if let Some(ref nc) = null_counts
127 && nc.len() != len
128 {
129 return Err(ParquetError::General(format!(
130 "ColumnIndex null_counts length mismatch: expected {len}, got {}",
131 nc.len()
132 )));
133 }
134 if let Some(ref rep) = repetition_level_histograms
135 && len != 0
136 && rep.len() % len != 0
137 {
138 return Err(ParquetError::General(
139 "Invalid repetition_level_histograms length".to_string(),
140 ));
141 }
142 if let Some(ref def) = definition_level_histograms
143 && len != 0
144 && def.len() % len != 0
145 {
146 return Err(ParquetError::General(
147 "Invalid definition_level_histograms length".to_string(),
148 ));
149 }
150
151 let mut min_values = Vec::with_capacity(len);
152 let mut max_values = Vec::with_capacity(len);
153
154 for (i, is_null) in null_pages.iter().enumerate().take(len) {
155 if !is_null {
156 let min = min_bytes[i];
157 min_values.push(T::try_from_le_slice(min)?);
158
159 let max = max_bytes[i];
160 max_values.push(T::try_from_le_slice(max)?);
161 } else {
162 min_values.push(Default::default());
164 max_values.push(Default::default());
165 }
166 }
167
168 Ok(Self {
169 column_index: ColumnIndex {
170 null_pages,
171 boundary_order,
172 null_counts,
173 repetition_level_histograms,
174 definition_level_histograms,
175 nan_counts,
176 },
177 min_values,
178 max_values,
179 })
180 }
181
182 pub(super) fn try_from_thrift(index: ThriftColumnIndex) -> Result<Self> {
183 Self::try_new(
184 index.null_pages,
185 index.boundary_order,
186 index.null_counts,
187 index.nan_counts,
188 index.repetition_level_histograms,
189 index.definition_level_histograms,
190 index.min_values,
191 index.max_values,
192 )
193 }
194}
195
196impl<T> PrimitiveColumnIndex<T> {
197 pub fn min_values(&self) -> &[T] {
202 &self.min_values
203 }
204
205 pub fn max_values(&self) -> &[T] {
210 &self.max_values
211 }
212
213 pub fn min_values_iter(&self) -> impl Iterator<Item = Option<&T>> {
217 self.min_values.iter().enumerate().map(|(i, min)| {
218 if self.is_null_page(i) {
219 None
220 } else {
221 Some(min)
222 }
223 })
224 }
225
226 pub fn max_values_iter(&self) -> impl Iterator<Item = Option<&T>> {
230 self.max_values.iter().enumerate().map(|(i, min)| {
231 if self.is_null_page(i) {
232 None
233 } else {
234 Some(min)
235 }
236 })
237 }
238
239 #[inline]
243 pub fn min_value(&self, idx: usize) -> Option<&T> {
244 if self.null_pages[idx] {
245 None
246 } else {
247 Some(&self.min_values[idx])
248 }
249 }
250
251 #[inline]
255 pub fn max_value(&self, idx: usize) -> Option<&T> {
256 if self.null_pages[idx] {
257 None
258 } else {
259 Some(&self.max_values[idx])
260 }
261 }
262}
263
264impl<T> Deref for PrimitiveColumnIndex<T> {
265 type Target = ColumnIndex;
266
267 fn deref(&self) -> &Self::Target {
268 &self.column_index
269 }
270}
271
272impl<T: ParquetValueType> WriteThrift for PrimitiveColumnIndex<T> {
273 const ELEMENT_TYPE: ElementType = ElementType::Struct;
274 fn write_thrift<W: std::io::Write>(
275 &self,
276 writer: &mut ThriftCompactOutputProtocol<W>,
277 ) -> Result<()> {
278 self.null_pages.write_thrift_field(writer, 1, 0)?;
279
280 let len = self.null_pages.len();
282 writer.write_field_begin(FieldType::List, 2, 1)?;
283 writer.write_list_begin(ElementType::Binary, len)?;
284 for i in 0..len {
285 let min = self.min_value(i).map(|m| m.as_bytes()).unwrap_or(&[]);
286 min.write_thrift(writer)?;
287 }
288 writer.write_field_begin(FieldType::List, 3, 2)?;
289 writer.write_list_begin(ElementType::Binary, len)?;
290 for i in 0..len {
291 let max = self.max_value(i).map(|m| m.as_bytes()).unwrap_or(&[]);
292 max.write_thrift(writer)?;
293 }
294 let mut last_field_id = self.boundary_order.write_thrift_field(writer, 4, 3)?;
295 if let Some(null_counts) = &self.null_counts {
296 last_field_id = null_counts.write_thrift_field(writer, 5, last_field_id)?;
297 }
298 if let Some(repetition_level_histograms) = &self.repetition_level_histograms {
299 last_field_id =
300 repetition_level_histograms.write_thrift_field(writer, 6, last_field_id)?;
301 }
302 if let Some(definition_level_histograms) = &self.definition_level_histograms {
303 last_field_id =
304 definition_level_histograms.write_thrift_field(writer, 7, last_field_id)?;
305 }
306 if let Some(nan_counts) = &self.nan_counts {
307 nan_counts.write_thrift_field(writer, 8, last_field_id)?;
308 }
309 writer.write_struct_end()
310 }
311}
312
313#[derive(Debug, Clone, PartialEq)]
315pub struct ByteArrayColumnIndex {
316 pub(crate) column_index: ColumnIndex,
317 pub(crate) min_bytes: Vec<u8>,
319 pub(crate) min_offsets: Vec<usize>,
320 pub(crate) max_bytes: Vec<u8>,
321 pub(crate) max_offsets: Vec<usize>,
322}
323
324impl ByteArrayColumnIndex {
325 #[expect(clippy::too_many_arguments)]
326 pub(crate) fn try_new(
327 null_pages: Vec<bool>,
328 boundary_order: BoundaryOrder,
329 null_counts: Option<Vec<i64>>,
330 nan_counts: Option<Vec<i64>>,
331 repetition_level_histograms: Option<Vec<i64>>,
332 definition_level_histograms: Option<Vec<i64>>,
333 min_values: Vec<&[u8]>,
334 max_values: Vec<&[u8]>,
335 ) -> Result<Self> {
336 let len = null_pages.len();
337
338 if min_values.len() != len || max_values.len() != len {
339 return Err(ParquetError::General(format!(
340 "ColumnIndex min/max length mismatch: expected {len}, got min={} max={}",
341 min_values.len(),
342 max_values.len()
343 )));
344 }
345 if let Some(ref nc) = null_counts
346 && nc.len() != len
347 {
348 return Err(ParquetError::General(format!(
349 "ColumnIndex null_counts length mismatch: expected {len}, got {}",
350 nc.len()
351 )));
352 }
353 if let Some(ref rep) = repetition_level_histograms
354 && len != 0
355 && rep.len() % len != 0
356 {
357 return Err(ParquetError::General(
358 "Invalid repetition_level_histograms length".to_string(),
359 ));
360 }
361 if let Some(ref def) = definition_level_histograms
362 && len != 0
363 && def.len() % len != 0
364 {
365 return Err(ParquetError::General(
366 "Invalid definition_level_histograms length".to_string(),
367 ));
368 }
369
370 let min_len = min_values.iter().map(|&v| v.len()).sum();
371 let max_len = max_values.iter().map(|&v| v.len()).sum();
372 let mut min_bytes = vec![0u8; min_len];
373 let mut max_bytes = vec![0u8; max_len];
374
375 let mut min_offsets = vec![0usize; len + 1];
376 let mut max_offsets = vec![0usize; len + 1];
377
378 let mut min_pos = 0;
379 let mut max_pos = 0;
380
381 for (i, is_null) in null_pages.iter().enumerate().take(len) {
382 if !is_null {
383 let min = min_values[i];
384 let dst = &mut min_bytes[min_pos..min_pos + min.len()];
385 dst.copy_from_slice(min);
386 min_offsets[i] = min_pos;
387 min_pos += min.len();
388
389 let max = max_values[i];
390 let dst = &mut max_bytes[max_pos..max_pos + max.len()];
391 dst.copy_from_slice(max);
392 max_offsets[i] = max_pos;
393 max_pos += max.len();
394 } else {
395 min_offsets[i] = min_pos;
396 max_offsets[i] = max_pos;
397 }
398 }
399
400 min_offsets[len] = min_pos;
401 max_offsets[len] = max_pos;
402
403 Ok(Self {
404 column_index: ColumnIndex {
405 null_pages,
406 boundary_order,
407 null_counts,
408 repetition_level_histograms,
409 definition_level_histograms,
410 nan_counts,
411 },
412 min_bytes,
413 min_offsets,
414 max_bytes,
415 max_offsets,
416 })
417 }
418
419 pub(super) fn try_from_thrift(index: ThriftColumnIndex) -> Result<Self> {
420 Self::try_new(
421 index.null_pages,
422 index.boundary_order,
423 index.null_counts,
424 index.nan_counts,
425 index.repetition_level_histograms,
426 index.definition_level_histograms,
427 index.min_values,
428 index.max_values,
429 )
430 }
431
432 pub fn min_value(&self, idx: usize) -> Option<&[u8]> {
436 if self.null_pages[idx] {
437 None
438 } else {
439 let start = self.min_offsets[idx];
440 let end = self.min_offsets[idx + 1];
441 Some(&self.min_bytes[start..end])
442 }
443 }
444
445 pub fn max_value(&self, idx: usize) -> Option<&[u8]> {
449 if self.null_pages[idx] {
450 None
451 } else {
452 let start = self.max_offsets[idx];
453 let end = self.max_offsets[idx + 1];
454 Some(&self.max_bytes[start..end])
455 }
456 }
457
458 pub fn min_values_iter(&self) -> impl Iterator<Item = Option<&[u8]>> {
462 (0..self.num_pages() as usize).map(|i| self.min_value(i))
463 }
464
465 pub fn max_values_iter(&self) -> impl Iterator<Item = Option<&[u8]>> {
469 (0..self.num_pages() as usize).map(|i| self.max_value(i))
470 }
471}
472
473impl Deref for ByteArrayColumnIndex {
474 type Target = ColumnIndex;
475
476 fn deref(&self) -> &Self::Target {
477 &self.column_index
478 }
479}
480
481impl WriteThrift for ByteArrayColumnIndex {
482 const ELEMENT_TYPE: ElementType = ElementType::Struct;
483 fn write_thrift<W: std::io::Write>(
484 &self,
485 writer: &mut ThriftCompactOutputProtocol<W>,
486 ) -> Result<()> {
487 self.null_pages.write_thrift_field(writer, 1, 0)?;
488
489 let len = self.null_pages.len();
491 writer.write_field_begin(FieldType::List, 2, 1)?;
492 writer.write_list_begin(ElementType::Binary, len)?;
493 for i in 0..len {
494 let min = self.min_value(i).unwrap_or(&[]);
495 min.write_thrift(writer)?;
496 }
497 writer.write_field_begin(FieldType::List, 3, 2)?;
498 writer.write_list_begin(ElementType::Binary, len)?;
499 for i in 0..len {
500 let max = self.max_value(i).unwrap_or(&[]);
501 max.write_thrift(writer)?;
502 }
503 let mut last_field_id = self.boundary_order.write_thrift_field(writer, 4, 3)?;
504 if let Some(null_counts) = &self.null_counts {
505 last_field_id = null_counts.write_thrift_field(writer, 5, last_field_id)?;
506 }
507 if let Some(repetition_level_histograms) = &self.repetition_level_histograms {
508 last_field_id =
509 repetition_level_histograms.write_thrift_field(writer, 6, last_field_id)?;
510 }
511 if let Some(definition_level_histograms) = &self.definition_level_histograms {
512 last_field_id =
513 definition_level_histograms.write_thrift_field(writer, 7, last_field_id)?;
514 }
515 if let Some(nan_counts) = &self.nan_counts {
516 nan_counts.write_thrift_field(writer, 8, last_field_id)?;
517 }
518 writer.write_struct_end()
519 }
520}
521
522macro_rules! colidx_enum_func {
524 ($self:ident, $func:ident, $arg:ident) => {{
525 match *$self {
526 Self::BOOLEAN(ref typed) => typed.$func($arg),
527 Self::INT32(ref typed) => typed.$func($arg),
528 Self::INT64(ref typed) => typed.$func($arg),
529 Self::INT96(ref typed) => typed.$func($arg),
530 Self::FLOAT(ref typed) => typed.$func($arg),
531 Self::DOUBLE(ref typed) => typed.$func($arg),
532 Self::BYTE_ARRAY(ref typed) => typed.$func($arg),
533 Self::FIXED_LEN_BYTE_ARRAY(ref typed) => typed.$func($arg),
534 }
535 }};
536 ($self:ident, $func:ident) => {{
537 match *$self {
538 Self::BOOLEAN(ref typed) => typed.$func(),
539 Self::INT32(ref typed) => typed.$func(),
540 Self::INT64(ref typed) => typed.$func(),
541 Self::INT96(ref typed) => typed.$func(),
542 Self::FLOAT(ref typed) => typed.$func(),
543 Self::DOUBLE(ref typed) => typed.$func(),
544 Self::BYTE_ARRAY(ref typed) => typed.$func(),
545 Self::FIXED_LEN_BYTE_ARRAY(ref typed) => typed.$func(),
546 }
547 }};
548}
549
550#[derive(Debug, Clone, PartialEq)]
557#[expect(non_camel_case_types)]
558pub enum ColumnIndexMetaData {
559 BOOLEAN(PrimitiveColumnIndex<bool>),
561 INT32(PrimitiveColumnIndex<i32>),
563 INT64(PrimitiveColumnIndex<i64>),
565 INT96(PrimitiveColumnIndex<Int96>),
567 FLOAT(PrimitiveColumnIndex<f32>),
569 DOUBLE(PrimitiveColumnIndex<f64>),
571 BYTE_ARRAY(ByteArrayColumnIndex),
573 FIXED_LEN_BYTE_ARRAY(ByteArrayColumnIndex),
575}
576
577impl ColumnIndexMetaData {
578 pub fn is_sorted(&self) -> bool {
580 if let Some(order) = self.get_boundary_order() {
582 order != BoundaryOrder::UNORDERED
583 } else {
584 false
585 }
586 }
587
588 pub fn get_boundary_order(&self) -> Option<BoundaryOrder> {
590 match self {
591 Self::BOOLEAN(index) => Some(index.boundary_order),
592 Self::INT32(index) => Some(index.boundary_order),
593 Self::INT64(index) => Some(index.boundary_order),
594 Self::INT96(index) => Some(index.boundary_order),
595 Self::FLOAT(index) => Some(index.boundary_order),
596 Self::DOUBLE(index) => Some(index.boundary_order),
597 Self::BYTE_ARRAY(index) => Some(index.boundary_order),
598 Self::FIXED_LEN_BYTE_ARRAY(index) => Some(index.boundary_order),
599 }
600 }
601
602 pub fn null_counts(&self) -> Option<&Vec<i64>> {
606 match self {
607 Self::BOOLEAN(index) => index.null_counts.as_ref(),
608 Self::INT32(index) => index.null_counts.as_ref(),
609 Self::INT64(index) => index.null_counts.as_ref(),
610 Self::INT96(index) => index.null_counts.as_ref(),
611 Self::FLOAT(index) => index.null_counts.as_ref(),
612 Self::DOUBLE(index) => index.null_counts.as_ref(),
613 Self::BYTE_ARRAY(index) => index.null_counts.as_ref(),
614 Self::FIXED_LEN_BYTE_ARRAY(index) => index.null_counts.as_ref(),
615 }
616 }
617
618 pub fn nan_counts(&self) -> Option<&Vec<i64>> {
622 match self {
623 Self::BOOLEAN(index) => index.nan_counts.as_ref(),
624 Self::INT32(index) => index.nan_counts.as_ref(),
625 Self::INT64(index) => index.nan_counts.as_ref(),
626 Self::INT96(index) => index.nan_counts.as_ref(),
627 Self::FLOAT(index) => index.nan_counts.as_ref(),
628 Self::DOUBLE(index) => index.nan_counts.as_ref(),
629 Self::BYTE_ARRAY(index) => index.nan_counts.as_ref(),
630 Self::FIXED_LEN_BYTE_ARRAY(index) => index.nan_counts.as_ref(),
631 }
632 }
633
634 pub fn num_pages(&self) -> u64 {
636 colidx_enum_func!(self, num_pages)
637 }
638
639 pub fn null_count(&self, idx: usize) -> Option<i64> {
643 colidx_enum_func!(self, null_count, idx)
644 }
645
646 pub fn nan_count(&self, idx: usize) -> Option<i64> {
650 colidx_enum_func!(self, nan_count, idx)
651 }
652
653 pub fn repetition_level_histogram(&self, idx: usize) -> Option<&[i64]> {
655 colidx_enum_func!(self, repetition_level_histogram, idx)
656 }
657
658 pub fn definition_level_histogram(&self, idx: usize) -> Option<&[i64]> {
660 colidx_enum_func!(self, definition_level_histogram, idx)
661 }
662
663 #[inline]
665 pub fn is_null_page(&self, idx: usize) -> bool {
666 colidx_enum_func!(self, is_null_page, idx)
667 }
668}
669
670pub trait ColumnIndexIterators {
672 type Item;
675
676 fn min_values_iter(colidx: &ColumnIndexMetaData) -> impl Iterator<Item = Option<Self::Item>>;
678
679 fn max_values_iter(colidx: &ColumnIndexMetaData) -> impl Iterator<Item = Option<Self::Item>>;
681}
682
683macro_rules! column_index_iters {
684 ($item: ident, $variant: ident, $conv:expr) => {
685 impl ColumnIndexIterators for $item {
686 type Item = $item;
687
688 fn min_values_iter(
689 colidx: &ColumnIndexMetaData,
690 ) -> impl Iterator<Item = Option<Self::Item>> {
691 if let ColumnIndexMetaData::$variant(index) = colidx {
692 index.min_values_iter().map($conv)
693 } else {
694 panic!(concat!("Wrong type for ", stringify!($item), " iterator"))
695 }
696 }
697
698 fn max_values_iter(
699 colidx: &ColumnIndexMetaData,
700 ) -> impl Iterator<Item = Option<Self::Item>> {
701 if let ColumnIndexMetaData::$variant(index) = colidx {
702 index.max_values_iter().map($conv)
703 } else {
704 panic!(concat!("Wrong type for ", stringify!($item), " iterator"))
705 }
706 }
707 }
708 };
709}
710
711column_index_iters!(bool, BOOLEAN, |v| v.copied());
712column_index_iters!(i32, INT32, |v| v.copied());
713column_index_iters!(i64, INT64, |v| v.copied());
714column_index_iters!(Int96, INT96, |v| v.copied());
715column_index_iters!(f32, FLOAT, |v| v.copied());
716column_index_iters!(f64, DOUBLE, |v| v.copied());
717column_index_iters!(ByteArray, BYTE_ARRAY, |v| v
718 .map(|v| ByteArray::from(v.to_owned())));
719column_index_iters!(FixedLenByteArray, FIXED_LEN_BYTE_ARRAY, |v| v
720 .map(|v| FixedLenByteArray::from(v.to_owned())));
721
722impl WriteThrift for ColumnIndexMetaData {
723 const ELEMENT_TYPE: ElementType = ElementType::Struct;
724
725 fn write_thrift<W: std::io::Write>(
726 &self,
727 writer: &mut ThriftCompactOutputProtocol<W>,
728 ) -> Result<()> {
729 match self {
730 ColumnIndexMetaData::BOOLEAN(index) => index.write_thrift(writer),
731 ColumnIndexMetaData::INT32(index) => index.write_thrift(writer),
732 ColumnIndexMetaData::INT64(index) => index.write_thrift(writer),
733 ColumnIndexMetaData::INT96(index) => index.write_thrift(writer),
734 ColumnIndexMetaData::FLOAT(index) => index.write_thrift(writer),
735 ColumnIndexMetaData::DOUBLE(index) => index.write_thrift(writer),
736 ColumnIndexMetaData::BYTE_ARRAY(index) => index.write_thrift(writer),
737 ColumnIndexMetaData::FIXED_LEN_BYTE_ARRAY(index) => index.write_thrift(writer),
738 }
739 }
740}
741
742#[cfg(test)]
743mod tests {
744 use super::*;
745
746 #[test]
747 fn test_page_index_min_max_null() {
748 let column_index = PrimitiveColumnIndex {
749 column_index: ColumnIndex {
750 null_pages: vec![false],
751 boundary_order: BoundaryOrder::ASCENDING,
752 null_counts: Some(vec![0]),
753 nan_counts: None,
754 repetition_level_histograms: Some(vec![1, 2]),
755 definition_level_histograms: Some(vec![1, 2, 3]),
756 },
757 min_values: vec![-123],
758 max_values: vec![234],
759 };
760
761 assert_eq!(column_index.min_value(0), Some(&-123));
762 assert_eq!(column_index.max_value(0), Some(&234));
763 assert_eq!(column_index.null_count(0), Some(0));
764 assert_eq!(column_index.repetition_level_histogram(0).unwrap(), &[1, 2]);
765 assert_eq!(
766 column_index.definition_level_histogram(0).unwrap(),
767 &[1, 2, 3]
768 );
769 }
770
771 #[test]
772 fn test_page_index_min_max_null_none() {
773 let column_index: PrimitiveColumnIndex<i32> = PrimitiveColumnIndex::<i32> {
774 column_index: ColumnIndex {
775 null_pages: vec![true],
776 boundary_order: BoundaryOrder::ASCENDING,
777 null_counts: Some(vec![1]),
778 nan_counts: None,
779 repetition_level_histograms: None,
780 definition_level_histograms: Some(vec![1, 0]),
781 },
782 min_values: vec![Default::default()],
783 max_values: vec![Default::default()],
784 };
785
786 assert_eq!(column_index.min_value(0), None);
787 assert_eq!(column_index.max_value(0), None);
788 assert_eq!(column_index.null_count(0), Some(1));
789 assert_eq!(column_index.repetition_level_histogram(0), None);
790 assert_eq!(column_index.definition_level_histogram(0).unwrap(), &[1, 0]);
791 }
792
793 #[test]
794 fn test_invalid_column_index() {
795 let column_index = ThriftColumnIndex {
796 null_pages: vec![true, false],
797 min_values: vec![
798 &[],
799 &[], ],
801 max_values: vec![
802 &[],
803 &[], ],
805 null_counts: None,
806 nan_counts: None,
807 repetition_level_histograms: None,
808 definition_level_histograms: None,
809 boundary_order: BoundaryOrder::UNORDERED,
810 };
811
812 let err = PrimitiveColumnIndex::<i32>::try_from_thrift(column_index).unwrap_err();
813 assert_eq!(
814 err.to_string(),
815 "Parquet error: error converting value, expected 4 bytes got 0"
816 );
817 }
818
819 #[test]
820 fn test_column_index_rejects_mismatched_min_max_lengths() {
821 let column_index = ThriftColumnIndex {
824 null_pages: vec![false, false],
825 min_values: vec![&[1u8, 0, 0, 0]],
826 max_values: vec![&[10u8, 0, 0, 0]],
827 null_counts: None,
828 repetition_level_histograms: None,
829 definition_level_histograms: None,
830 boundary_order: BoundaryOrder::UNORDERED,
831 nan_counts: None,
832 };
833
834 let err = PrimitiveColumnIndex::<i32>::try_from_thrift(column_index).unwrap_err();
836 assert!(err.to_string().contains("length mismatch"));
838 }
839}