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}
47
48impl ColumnIndex {
49 pub fn num_pages(&self) -> u64 {
51 self.null_pages.len() as u64
52 }
53
54 pub fn null_count(&self, idx: usize) -> Option<i64> {
58 self.null_counts.as_ref().map(|nc| nc[idx])
59 }
60
61 pub fn repetition_level_histogram(&self, idx: usize) -> Option<&[i64]> {
63 if let Some(rep_hists) = self.repetition_level_histograms.as_ref() {
64 let num_lvls = rep_hists.len() / self.num_pages() as usize;
65 let start = num_lvls * idx;
66 Some(&rep_hists[start..start + num_lvls])
67 } else {
68 None
69 }
70 }
71
72 pub fn definition_level_histogram(&self, idx: usize) -> Option<&[i64]> {
74 if let Some(def_hists) = self.definition_level_histograms.as_ref() {
75 let num_lvls = def_hists.len() / self.num_pages() as usize;
76 let start = num_lvls * idx;
77 Some(&def_hists[start..start + num_lvls])
78 } else {
79 None
80 }
81 }
82
83 pub fn is_null_page(&self, idx: usize) -> bool {
85 self.null_pages[idx]
86 }
87}
88
89#[derive(Debug, Clone, PartialEq)]
91pub struct PrimitiveColumnIndex<T> {
92 pub(crate) column_index: ColumnIndex,
93 pub(crate) min_values: Vec<T>,
94 pub(crate) max_values: Vec<T>,
95}
96
97impl<T: ParquetValueType> PrimitiveColumnIndex<T> {
98 pub(crate) fn try_new(
99 null_pages: Vec<bool>,
100 boundary_order: BoundaryOrder,
101 null_counts: Option<Vec<i64>>,
102 repetition_level_histograms: Option<Vec<i64>>,
103 definition_level_histograms: Option<Vec<i64>>,
104 min_bytes: Vec<&[u8]>,
105 max_bytes: Vec<&[u8]>,
106 ) -> Result<Self> {
107 let len = null_pages.len();
108
109 if min_bytes.len() != len || max_bytes.len() != len {
110 return Err(ParquetError::General(format!(
111 "ColumnIndex min/max length mismatch: expected {len}, got min={} max={}",
112 min_bytes.len(),
113 max_bytes.len()
114 )));
115 }
116 if let Some(ref nc) = null_counts {
117 if nc.len() != len {
118 return Err(ParquetError::General(format!(
119 "ColumnIndex null_counts length mismatch: expected {len}, got {}",
120 nc.len()
121 )));
122 }
123 }
124 if let Some(ref rep) = repetition_level_histograms {
125 if len != 0 && rep.len() % len != 0 {
126 return Err(ParquetError::General(
127 "Invalid repetition_level_histograms length".to_string(),
128 ));
129 }
130 }
131 if let Some(ref def) = definition_level_histograms {
132 if len != 0 && def.len() % len != 0 {
133 return Err(ParquetError::General(
134 "Invalid definition_level_histograms length".to_string(),
135 ));
136 }
137 }
138
139 let mut min_values = Vec::with_capacity(len);
140 let mut max_values = Vec::with_capacity(len);
141
142 for (i, is_null) in null_pages.iter().enumerate().take(len) {
143 if !is_null {
144 let min = min_bytes[i];
145 min_values.push(T::try_from_le_slice(min)?);
146
147 let max = max_bytes[i];
148 max_values.push(T::try_from_le_slice(max)?);
149 } else {
150 min_values.push(Default::default());
152 max_values.push(Default::default());
153 }
154 }
155
156 Ok(Self {
157 column_index: ColumnIndex {
158 null_pages,
159 boundary_order,
160 null_counts,
161 repetition_level_histograms,
162 definition_level_histograms,
163 },
164 min_values,
165 max_values,
166 })
167 }
168
169 pub(super) fn try_from_thrift(index: ThriftColumnIndex) -> Result<Self> {
170 Self::try_new(
171 index.null_pages,
172 index.boundary_order,
173 index.null_counts,
174 index.repetition_level_histograms,
175 index.definition_level_histograms,
176 index.min_values,
177 index.max_values,
178 )
179 }
180}
181
182impl<T> PrimitiveColumnIndex<T> {
183 pub fn min_values(&self) -> &[T] {
188 &self.min_values
189 }
190
191 pub fn max_values(&self) -> &[T] {
196 &self.max_values
197 }
198
199 pub fn min_values_iter(&self) -> impl Iterator<Item = Option<&T>> {
203 self.min_values.iter().enumerate().map(|(i, min)| {
204 if self.is_null_page(i) {
205 None
206 } else {
207 Some(min)
208 }
209 })
210 }
211
212 pub fn max_values_iter(&self) -> impl Iterator<Item = Option<&T>> {
216 self.max_values.iter().enumerate().map(|(i, min)| {
217 if self.is_null_page(i) {
218 None
219 } else {
220 Some(min)
221 }
222 })
223 }
224
225 #[inline]
229 pub fn min_value(&self, idx: usize) -> Option<&T> {
230 if self.null_pages[idx] {
231 None
232 } else {
233 Some(&self.min_values[idx])
234 }
235 }
236
237 #[inline]
241 pub fn max_value(&self, idx: usize) -> Option<&T> {
242 if self.null_pages[idx] {
243 None
244 } else {
245 Some(&self.max_values[idx])
246 }
247 }
248}
249
250impl<T> Deref for PrimitiveColumnIndex<T> {
251 type Target = ColumnIndex;
252
253 fn deref(&self) -> &Self::Target {
254 &self.column_index
255 }
256}
257
258impl<T: ParquetValueType> WriteThrift for PrimitiveColumnIndex<T> {
259 const ELEMENT_TYPE: ElementType = ElementType::Struct;
260 fn write_thrift<W: std::io::Write>(
261 &self,
262 writer: &mut ThriftCompactOutputProtocol<W>,
263 ) -> Result<()> {
264 self.null_pages.write_thrift_field(writer, 1, 0)?;
265
266 let len = self.null_pages.len();
268 writer.write_field_begin(FieldType::List, 2, 1)?;
269 writer.write_list_begin(ElementType::Binary, len)?;
270 for i in 0..len {
271 let min = self.min_value(i).map(|m| m.as_bytes()).unwrap_or(&[]);
272 min.write_thrift(writer)?;
273 }
274 writer.write_field_begin(FieldType::List, 3, 2)?;
275 writer.write_list_begin(ElementType::Binary, len)?;
276 for i in 0..len {
277 let max = self.max_value(i).map(|m| m.as_bytes()).unwrap_or(&[]);
278 max.write_thrift(writer)?;
279 }
280 let mut last_field_id = self.boundary_order.write_thrift_field(writer, 4, 3)?;
281 if let Some(null_counts) = &self.null_counts {
282 last_field_id = null_counts.write_thrift_field(writer, 5, last_field_id)?;
283 }
284 if let Some(repetition_level_histograms) = &self.repetition_level_histograms {
285 last_field_id =
286 repetition_level_histograms.write_thrift_field(writer, 6, last_field_id)?;
287 }
288 if let Some(definition_level_histograms) = &self.definition_level_histograms {
289 definition_level_histograms.write_thrift_field(writer, 7, last_field_id)?;
290 }
291 writer.write_struct_end()
292 }
293}
294
295#[derive(Debug, Clone, PartialEq)]
297pub struct ByteArrayColumnIndex {
298 pub(crate) column_index: ColumnIndex,
299 pub(crate) min_bytes: Vec<u8>,
301 pub(crate) min_offsets: Vec<usize>,
302 pub(crate) max_bytes: Vec<u8>,
303 pub(crate) max_offsets: Vec<usize>,
304}
305
306impl ByteArrayColumnIndex {
307 pub(crate) fn try_new(
308 null_pages: Vec<bool>,
309 boundary_order: BoundaryOrder,
310 null_counts: Option<Vec<i64>>,
311 repetition_level_histograms: Option<Vec<i64>>,
312 definition_level_histograms: Option<Vec<i64>>,
313 min_values: Vec<&[u8]>,
314 max_values: Vec<&[u8]>,
315 ) -> Result<Self> {
316 let len = null_pages.len();
317
318 if min_values.len() != len || max_values.len() != len {
319 return Err(ParquetError::General(format!(
320 "ColumnIndex min/max length mismatch: expected {len}, got min={} max={}",
321 min_values.len(),
322 max_values.len()
323 )));
324 }
325 if let Some(ref nc) = null_counts {
326 if nc.len() != len {
327 return Err(ParquetError::General(format!(
328 "ColumnIndex null_counts length mismatch: expected {len}, got {}",
329 nc.len()
330 )));
331 }
332 }
333 if let Some(ref rep) = repetition_level_histograms {
334 if len != 0 && rep.len() % len != 0 {
335 return Err(ParquetError::General(
336 "Invalid repetition_level_histograms length".to_string(),
337 ));
338 }
339 }
340 if let Some(ref def) = definition_level_histograms {
341 if len != 0 && def.len() % len != 0 {
342 return Err(ParquetError::General(
343 "Invalid definition_level_histograms length".to_string(),
344 ));
345 }
346 }
347
348 let min_len = min_values.iter().map(|&v| v.len()).sum();
349 let max_len = max_values.iter().map(|&v| v.len()).sum();
350 let mut min_bytes = vec![0u8; min_len];
351 let mut max_bytes = vec![0u8; max_len];
352
353 let mut min_offsets = vec![0usize; len + 1];
354 let mut max_offsets = vec![0usize; len + 1];
355
356 let mut min_pos = 0;
357 let mut max_pos = 0;
358
359 for (i, is_null) in null_pages.iter().enumerate().take(len) {
360 if !is_null {
361 let min = min_values[i];
362 let dst = &mut min_bytes[min_pos..min_pos + min.len()];
363 dst.copy_from_slice(min);
364 min_offsets[i] = min_pos;
365 min_pos += min.len();
366
367 let max = max_values[i];
368 let dst = &mut max_bytes[max_pos..max_pos + max.len()];
369 dst.copy_from_slice(max);
370 max_offsets[i] = max_pos;
371 max_pos += max.len();
372 } else {
373 min_offsets[i] = min_pos;
374 max_offsets[i] = max_pos;
375 }
376 }
377
378 min_offsets[len] = min_pos;
379 max_offsets[len] = max_pos;
380
381 Ok(Self {
382 column_index: ColumnIndex {
383 null_pages,
384 boundary_order,
385 null_counts,
386 repetition_level_histograms,
387 definition_level_histograms,
388 },
389 min_bytes,
390 min_offsets,
391 max_bytes,
392 max_offsets,
393 })
394 }
395
396 pub(super) fn try_from_thrift(index: ThriftColumnIndex) -> Result<Self> {
397 Self::try_new(
398 index.null_pages,
399 index.boundary_order,
400 index.null_counts,
401 index.repetition_level_histograms,
402 index.definition_level_histograms,
403 index.min_values,
404 index.max_values,
405 )
406 }
407
408 pub fn min_value(&self, idx: usize) -> Option<&[u8]> {
412 if self.null_pages[idx] {
413 None
414 } else {
415 let start = self.min_offsets[idx];
416 let end = self.min_offsets[idx + 1];
417 Some(&self.min_bytes[start..end])
418 }
419 }
420
421 pub fn max_value(&self, idx: usize) -> Option<&[u8]> {
425 if self.null_pages[idx] {
426 None
427 } else {
428 let start = self.max_offsets[idx];
429 let end = self.max_offsets[idx + 1];
430 Some(&self.max_bytes[start..end])
431 }
432 }
433
434 pub fn min_values_iter(&self) -> impl Iterator<Item = Option<&[u8]>> {
438 (0..self.num_pages() as usize).map(|i| self.min_value(i))
439 }
440
441 pub fn max_values_iter(&self) -> impl Iterator<Item = Option<&[u8]>> {
445 (0..self.num_pages() as usize).map(|i| self.max_value(i))
446 }
447}
448
449impl Deref for ByteArrayColumnIndex {
450 type Target = ColumnIndex;
451
452 fn deref(&self) -> &Self::Target {
453 &self.column_index
454 }
455}
456
457impl WriteThrift for ByteArrayColumnIndex {
458 const ELEMENT_TYPE: ElementType = ElementType::Struct;
459 fn write_thrift<W: std::io::Write>(
460 &self,
461 writer: &mut ThriftCompactOutputProtocol<W>,
462 ) -> Result<()> {
463 self.null_pages.write_thrift_field(writer, 1, 0)?;
464
465 let len = self.null_pages.len();
467 writer.write_field_begin(FieldType::List, 2, 1)?;
468 writer.write_list_begin(ElementType::Binary, len)?;
469 for i in 0..len {
470 let min = self.min_value(i).unwrap_or(&[]);
471 min.write_thrift(writer)?;
472 }
473 writer.write_field_begin(FieldType::List, 3, 2)?;
474 writer.write_list_begin(ElementType::Binary, len)?;
475 for i in 0..len {
476 let max = self.max_value(i).unwrap_or(&[]);
477 max.write_thrift(writer)?;
478 }
479 let mut last_field_id = self.boundary_order.write_thrift_field(writer, 4, 3)?;
480 if let Some(null_counts) = &self.null_counts {
481 last_field_id = null_counts.write_thrift_field(writer, 5, last_field_id)?;
482 }
483 if let Some(repetition_level_histograms) = &self.repetition_level_histograms {
484 last_field_id =
485 repetition_level_histograms.write_thrift_field(writer, 6, last_field_id)?;
486 }
487 if let Some(definition_level_histograms) = &self.definition_level_histograms {
488 definition_level_histograms.write_thrift_field(writer, 7, last_field_id)?;
489 }
490 writer.write_struct_end()
491 }
492}
493
494macro_rules! colidx_enum_func {
496 ($self:ident, $func:ident, $arg:ident) => {{
497 match *$self {
498 Self::BOOLEAN(ref typed) => typed.$func($arg),
499 Self::INT32(ref typed) => typed.$func($arg),
500 Self::INT64(ref typed) => typed.$func($arg),
501 Self::INT96(ref typed) => typed.$func($arg),
502 Self::FLOAT(ref typed) => typed.$func($arg),
503 Self::DOUBLE(ref typed) => typed.$func($arg),
504 Self::BYTE_ARRAY(ref typed) => typed.$func($arg),
505 Self::FIXED_LEN_BYTE_ARRAY(ref typed) => typed.$func($arg),
506 _ => panic!(concat!(
507 "Cannot call ",
508 stringify!($func),
509 " on ColumnIndexMetaData::NONE"
510 )),
511 }
512 }};
513 ($self:ident, $func:ident) => {{
514 match *$self {
515 Self::BOOLEAN(ref typed) => typed.$func(),
516 Self::INT32(ref typed) => typed.$func(),
517 Self::INT64(ref typed) => typed.$func(),
518 Self::INT96(ref typed) => typed.$func(),
519 Self::FLOAT(ref typed) => typed.$func(),
520 Self::DOUBLE(ref typed) => typed.$func(),
521 Self::BYTE_ARRAY(ref typed) => typed.$func(),
522 Self::FIXED_LEN_BYTE_ARRAY(ref typed) => typed.$func(),
523 _ => panic!(concat!(
524 "Cannot call ",
525 stringify!($func),
526 " on ColumnIndexMetaData::NONE"
527 )),
528 }
529 }};
530}
531
532#[derive(Debug, Clone, PartialEq)]
539#[allow(non_camel_case_types)]
540pub enum ColumnIndexMetaData {
541 NONE,
545 BOOLEAN(PrimitiveColumnIndex<bool>),
547 INT32(PrimitiveColumnIndex<i32>),
549 INT64(PrimitiveColumnIndex<i64>),
551 INT96(PrimitiveColumnIndex<Int96>),
553 FLOAT(PrimitiveColumnIndex<f32>),
555 DOUBLE(PrimitiveColumnIndex<f64>),
557 BYTE_ARRAY(ByteArrayColumnIndex),
559 FIXED_LEN_BYTE_ARRAY(ByteArrayColumnIndex),
561}
562
563impl ColumnIndexMetaData {
564 pub fn is_sorted(&self) -> bool {
566 if let Some(order) = self.get_boundary_order() {
568 order != BoundaryOrder::UNORDERED
569 } else {
570 false
571 }
572 }
573
574 pub fn get_boundary_order(&self) -> Option<BoundaryOrder> {
576 match self {
577 Self::NONE => None,
578 Self::BOOLEAN(index) => Some(index.boundary_order),
579 Self::INT32(index) => Some(index.boundary_order),
580 Self::INT64(index) => Some(index.boundary_order),
581 Self::INT96(index) => Some(index.boundary_order),
582 Self::FLOAT(index) => Some(index.boundary_order),
583 Self::DOUBLE(index) => Some(index.boundary_order),
584 Self::BYTE_ARRAY(index) => Some(index.boundary_order),
585 Self::FIXED_LEN_BYTE_ARRAY(index) => Some(index.boundary_order),
586 }
587 }
588
589 pub fn null_counts(&self) -> Option<&Vec<i64>> {
593 match self {
594 Self::NONE => None,
595 Self::BOOLEAN(index) => index.null_counts.as_ref(),
596 Self::INT32(index) => index.null_counts.as_ref(),
597 Self::INT64(index) => index.null_counts.as_ref(),
598 Self::INT96(index) => index.null_counts.as_ref(),
599 Self::FLOAT(index) => index.null_counts.as_ref(),
600 Self::DOUBLE(index) => index.null_counts.as_ref(),
601 Self::BYTE_ARRAY(index) => index.null_counts.as_ref(),
602 Self::FIXED_LEN_BYTE_ARRAY(index) => index.null_counts.as_ref(),
603 }
604 }
605
606 pub fn num_pages(&self) -> u64 {
608 colidx_enum_func!(self, num_pages)
609 }
610
611 pub fn null_count(&self, idx: usize) -> Option<i64> {
615 colidx_enum_func!(self, null_count, idx)
616 }
617
618 pub fn repetition_level_histogram(&self, idx: usize) -> Option<&[i64]> {
620 colidx_enum_func!(self, repetition_level_histogram, idx)
621 }
622
623 pub fn definition_level_histogram(&self, idx: usize) -> Option<&[i64]> {
625 colidx_enum_func!(self, definition_level_histogram, idx)
626 }
627
628 #[inline]
630 pub fn is_null_page(&self, idx: usize) -> bool {
631 colidx_enum_func!(self, is_null_page, idx)
632 }
633}
634
635pub trait ColumnIndexIterators {
637 type Item;
640
641 fn min_values_iter(colidx: &ColumnIndexMetaData) -> impl Iterator<Item = Option<Self::Item>>;
643
644 fn max_values_iter(colidx: &ColumnIndexMetaData) -> impl Iterator<Item = Option<Self::Item>>;
646}
647
648macro_rules! column_index_iters {
649 ($item: ident, $variant: ident, $conv:expr) => {
650 impl ColumnIndexIterators for $item {
651 type Item = $item;
652
653 fn min_values_iter(
654 colidx: &ColumnIndexMetaData,
655 ) -> impl Iterator<Item = Option<Self::Item>> {
656 if let ColumnIndexMetaData::$variant(index) = colidx {
657 index.min_values_iter().map($conv)
658 } else {
659 panic!(concat!("Wrong type for ", stringify!($item), " iterator"))
660 }
661 }
662
663 fn max_values_iter(
664 colidx: &ColumnIndexMetaData,
665 ) -> impl Iterator<Item = Option<Self::Item>> {
666 if let ColumnIndexMetaData::$variant(index) = colidx {
667 index.max_values_iter().map($conv)
668 } else {
669 panic!(concat!("Wrong type for ", stringify!($item), " iterator"))
670 }
671 }
672 }
673 };
674}
675
676column_index_iters!(bool, BOOLEAN, |v| v.copied());
677column_index_iters!(i32, INT32, |v| v.copied());
678column_index_iters!(i64, INT64, |v| v.copied());
679column_index_iters!(Int96, INT96, |v| v.copied());
680column_index_iters!(f32, FLOAT, |v| v.copied());
681column_index_iters!(f64, DOUBLE, |v| v.copied());
682column_index_iters!(ByteArray, BYTE_ARRAY, |v| v
683 .map(|v| ByteArray::from(v.to_owned())));
684column_index_iters!(FixedLenByteArray, FIXED_LEN_BYTE_ARRAY, |v| v
685 .map(|v| FixedLenByteArray::from(v.to_owned())));
686
687impl WriteThrift for ColumnIndexMetaData {
688 const ELEMENT_TYPE: ElementType = ElementType::Struct;
689
690 fn write_thrift<W: std::io::Write>(
691 &self,
692 writer: &mut ThriftCompactOutputProtocol<W>,
693 ) -> Result<()> {
694 match self {
695 ColumnIndexMetaData::BOOLEAN(index) => index.write_thrift(writer),
696 ColumnIndexMetaData::INT32(index) => index.write_thrift(writer),
697 ColumnIndexMetaData::INT64(index) => index.write_thrift(writer),
698 ColumnIndexMetaData::INT96(index) => index.write_thrift(writer),
699 ColumnIndexMetaData::FLOAT(index) => index.write_thrift(writer),
700 ColumnIndexMetaData::DOUBLE(index) => index.write_thrift(writer),
701 ColumnIndexMetaData::BYTE_ARRAY(index) => index.write_thrift(writer),
702 ColumnIndexMetaData::FIXED_LEN_BYTE_ARRAY(index) => index.write_thrift(writer),
703 _ => Err(general_err!("Cannot serialize NONE index")),
704 }
705 }
706}
707
708#[cfg(test)]
709mod tests {
710 use super::*;
711
712 #[test]
713 fn test_page_index_min_max_null() {
714 let column_index = PrimitiveColumnIndex {
715 column_index: ColumnIndex {
716 null_pages: vec![false],
717 boundary_order: BoundaryOrder::ASCENDING,
718 null_counts: Some(vec![0]),
719 repetition_level_histograms: Some(vec![1, 2]),
720 definition_level_histograms: Some(vec![1, 2, 3]),
721 },
722 min_values: vec![-123],
723 max_values: vec![234],
724 };
725
726 assert_eq!(column_index.min_value(0), Some(&-123));
727 assert_eq!(column_index.max_value(0), Some(&234));
728 assert_eq!(column_index.null_count(0), Some(0));
729 assert_eq!(column_index.repetition_level_histogram(0).unwrap(), &[1, 2]);
730 assert_eq!(
731 column_index.definition_level_histogram(0).unwrap(),
732 &[1, 2, 3]
733 );
734 }
735
736 #[test]
737 fn test_page_index_min_max_null_none() {
738 let column_index: PrimitiveColumnIndex<i32> = PrimitiveColumnIndex::<i32> {
739 column_index: ColumnIndex {
740 null_pages: vec![true],
741 boundary_order: BoundaryOrder::ASCENDING,
742 null_counts: Some(vec![1]),
743 repetition_level_histograms: None,
744 definition_level_histograms: Some(vec![1, 0]),
745 },
746 min_values: vec![Default::default()],
747 max_values: vec![Default::default()],
748 };
749
750 assert_eq!(column_index.min_value(0), None);
751 assert_eq!(column_index.max_value(0), None);
752 assert_eq!(column_index.null_count(0), Some(1));
753 assert_eq!(column_index.repetition_level_histogram(0), None);
754 assert_eq!(column_index.definition_level_histogram(0).unwrap(), &[1, 0]);
755 }
756
757 #[test]
758 fn test_invalid_column_index() {
759 let column_index = ThriftColumnIndex {
760 null_pages: vec![true, false],
761 min_values: vec![
762 &[],
763 &[], ],
765 max_values: vec![
766 &[],
767 &[], ],
769 null_counts: None,
770 repetition_level_histograms: None,
771 definition_level_histograms: None,
772 boundary_order: BoundaryOrder::UNORDERED,
773 };
774
775 let err = PrimitiveColumnIndex::<i32>::try_from_thrift(column_index).unwrap_err();
776 assert_eq!(
777 err.to_string(),
778 "Parquet error: error converting value, expected 4 bytes got 0"
779 );
780 }
781
782 #[test]
783 fn test_column_index_rejects_mismatched_min_max_lengths() {
784 let column_index = ThriftColumnIndex {
787 null_pages: vec![false, false],
788 min_values: vec![&[1u8, 0, 0, 0]],
789 max_values: vec![&[10u8, 0, 0, 0]],
790 null_counts: None,
791 repetition_level_histograms: None,
792 definition_level_histograms: None,
793 boundary_order: BoundaryOrder::UNORDERED,
794 };
795
796 let err = PrimitiveColumnIndex::<i32>::try_from_thrift(column_index).unwrap_err();
798 assert!(err.to_string().contains("length mismatch"));
800 }
801}