1use crate::basic::Encoding;
19use crate::bloom_filter::Sbbf;
20use crate::column::writer::encoder::{
21 ColumnValueEncoder, DataPageValues, DictionaryPage, create_bloom_filter,
22};
23use crate::data_type::{AsBytes, ByteArray, Int32Type};
24use crate::encodings::encoding::{DeltaBitPackEncoder, Encoder};
25use crate::encodings::rle::RleEncoder;
26use crate::errors::{ParquetError, Result};
27use crate::file::properties::{
28 EnabledStatistics, ResolvedColumnProperties, WriterProperties, WriterVersion,
29};
30use crate::geospatial::accumulator::{GeoStatsAccumulator, try_new_geo_stats_accumulator};
31use crate::geospatial::statistics::GeospatialStatistics;
32use crate::schema::types::ColumnDescPtr;
33use crate::util::bit_util::num_required_bits;
34use crate::util::interner::{Interner, Storage};
35use crate::util::prefix::common_prefix_length;
36use arrow_array::types::ByteArrayType;
37use arrow_array::{
38 Array, ArrayAccessor, BinaryArray, BinaryViewArray, DictionaryArray, FixedSizeBinaryArray,
39 GenericByteArray, LargeBinaryArray, LargeStringArray, StringArray, StringViewArray,
40};
41use arrow_buffer::{ArrowNativeType, Buffer};
42use arrow_schema::DataType;
43
44macro_rules! downcast_dict_impl {
45 ($array:ident, $key:ident, $val:ident, $op:expr $(, $arg:expr)*) => {{
46 $op($array
47 .as_any()
48 .downcast_ref::<DictionaryArray<arrow_array::types::$key>>()
49 .unwrap()
50 .downcast_dict::<$val>()
51 .unwrap()$(, $arg)*)
52 }};
53}
54
55macro_rules! downcast_dict_op {
56 ($key_type:expr, $val:ident, $array:ident, $op:expr $(, $arg:expr)*) => {
57 match $key_type.as_ref() {
58 DataType::UInt8 => downcast_dict_impl!($array, UInt8Type, $val, $op$(, $arg)*),
59 DataType::UInt16 => downcast_dict_impl!($array, UInt16Type, $val, $op$(, $arg)*),
60 DataType::UInt32 => downcast_dict_impl!($array, UInt32Type, $val, $op$(, $arg)*),
61 DataType::UInt64 => downcast_dict_impl!($array, UInt64Type, $val, $op$(, $arg)*),
62 DataType::Int8 => downcast_dict_impl!($array, Int8Type, $val, $op$(, $arg)*),
63 DataType::Int16 => downcast_dict_impl!($array, Int16Type, $val, $op$(, $arg)*),
64 DataType::Int32 => downcast_dict_impl!($array, Int32Type, $val, $op$(, $arg)*),
65 DataType::Int64 => downcast_dict_impl!($array, Int64Type, $val, $op$(, $arg)*),
66 _ => unreachable!(),
67 }
68 };
69}
70
71macro_rules! downcast_op {
72 ($data_type:expr, $array:ident, $op:expr $(, $arg:expr)*) => {
73 match $data_type {
74 DataType::Utf8 => $op($array.as_any().downcast_ref::<StringArray>().unwrap()$(, $arg)*),
75 DataType::LargeUtf8 => {
76 $op($array.as_any().downcast_ref::<LargeStringArray>().unwrap()$(, $arg)*)
77 }
78 DataType::Utf8View => $op($array.as_any().downcast_ref::<StringViewArray>().unwrap()$(, $arg)*),
79 DataType::Binary => {
80 $op($array.as_any().downcast_ref::<BinaryArray>().unwrap()$(, $arg)*)
81 }
82 DataType::LargeBinary => {
83 $op($array.as_any().downcast_ref::<LargeBinaryArray>().unwrap()$(, $arg)*)
84 }
85 DataType::BinaryView => {
86 $op($array.as_any().downcast_ref::<BinaryViewArray>().unwrap()$(, $arg)*)
87 }
88 DataType::Dictionary(key, value) => match value.as_ref() {
89 DataType::Utf8 => downcast_dict_op!(key, StringArray, $array, $op$(, $arg)*),
90 DataType::LargeUtf8 => {
91 downcast_dict_op!(key, LargeStringArray, $array, $op$(, $arg)*)
92 }
93 DataType::Utf8View => {
94 downcast_dict_op!(key, StringViewArray, $array, $op$(, $arg)*)
95 }
96 DataType::Binary => downcast_dict_op!(key, BinaryArray, $array, $op$(, $arg)*),
97 DataType::LargeBinary => {
98 downcast_dict_op!(key, LargeBinaryArray, $array, $op$(, $arg)*)
99 }
100 DataType::BinaryView => {
101 downcast_dict_op!(key, BinaryViewArray, $array, $op$(, $arg)*)
102 }
103 DataType::FixedSizeBinary(_) => {
104 downcast_dict_op!(key, FixedSizeBinaryArray, $array, $op$(, $arg)*)
105 }
106 d => unreachable!("cannot downcast {} dictionary value to byte array", d),
107 },
108 d => unreachable!("cannot downcast {} to byte array", d),
109 }
110 };
111}
112
113struct FallbackEncoder {
115 encoder: FallbackEncoderImpl,
116 num_values: usize,
117 variable_length_bytes: i64,
118}
119
120enum FallbackEncoderImpl {
124 Plain {
125 buffer: Vec<u8>,
126 },
127 DeltaLength {
128 buffer: Vec<u8>,
129 lengths: Box<DeltaBitPackEncoder<Int32Type>>,
130 },
131 Delta {
132 buffer: Vec<u8>,
133 last_value: Vec<u8>,
134 prefix_lengths: Box<DeltaBitPackEncoder<Int32Type>>,
135 suffix_lengths: Box<DeltaBitPackEncoder<Int32Type>>,
136 },
137}
138
139impl FallbackEncoder {
140 fn new(props: &WriterProperties, column_props: &ResolvedColumnProperties) -> Result<Self> {
143 let encoding = column_props
145 .encoding
146 .unwrap_or_else(|| match props.writer_version() {
147 WriterVersion::PARQUET_1_0 => Encoding::PLAIN,
148 WriterVersion::PARQUET_2_0 => Encoding::DELTA_BYTE_ARRAY,
149 });
150
151 let encoder = match encoding {
152 Encoding::PLAIN => FallbackEncoderImpl::Plain { buffer: vec![] },
153 Encoding::DELTA_LENGTH_BYTE_ARRAY => FallbackEncoderImpl::DeltaLength {
154 buffer: vec![],
155 lengths: Box::new(DeltaBitPackEncoder::new()),
156 },
157 Encoding::DELTA_BYTE_ARRAY => FallbackEncoderImpl::Delta {
158 buffer: vec![],
159 last_value: vec![],
160 prefix_lengths: Box::new(DeltaBitPackEncoder::new()),
161 suffix_lengths: Box::new(DeltaBitPackEncoder::new()),
162 },
163 _ => {
164 return Err(general_err!(
165 "unsupported encoding {} for byte array",
166 encoding
167 ));
168 }
169 };
170
171 Ok(Self {
172 encoder,
173 num_values: 0,
174 variable_length_bytes: 0,
175 })
176 }
177
178 fn encode<T>(&mut self, values: T, indices: impl ExactSizeIterator<Item = usize>)
180 where
181 T: ArrayAccessor + Copy,
182 T::Item: AsRef<[u8]>,
183 {
184 self.num_values += indices.len();
185 match &mut self.encoder {
186 FallbackEncoderImpl::Plain { buffer } => {
187 for idx in indices {
188 let value = values.value(idx);
189 let value = value.as_ref();
190 buffer.extend_from_slice((value.len() as u32).as_bytes());
191 buffer.extend_from_slice(value);
192 self.variable_length_bytes += value.len() as i64;
193 }
194 }
195 FallbackEncoderImpl::DeltaLength { buffer, lengths } => {
196 for idx in indices {
197 let value = values.value(idx);
198 let value = value.as_ref();
199 lengths.put(&[value.len() as i32]).unwrap();
200 buffer.extend_from_slice(value);
201 self.variable_length_bytes += value.len() as i64;
202 }
203 }
204 FallbackEncoderImpl::Delta {
205 buffer,
206 last_value,
207 prefix_lengths,
208 suffix_lengths,
209 } => {
210 for idx in indices {
211 let value = values.value(idx);
212 let value = value.as_ref();
213
214 let prefix_length = common_prefix_length(last_value, value);
215 let suffix_length = value.len() - prefix_length;
216
217 last_value.clear();
218 last_value.extend_from_slice(value);
219
220 buffer.extend_from_slice(&value[prefix_length..]);
221 prefix_lengths.put(&[prefix_length as i32]).unwrap();
222 suffix_lengths.put(&[suffix_length as i32]).unwrap();
223 self.variable_length_bytes += value.len() as i64;
224 }
225 }
226 }
227 }
228
229 fn estimated_data_page_size(&self) -> usize {
234 match &self.encoder {
235 FallbackEncoderImpl::Plain { buffer, .. } => buffer.len(),
236 FallbackEncoderImpl::DeltaLength { buffer, lengths } => {
237 buffer.len() + lengths.estimated_data_encoded_size()
238 }
239 FallbackEncoderImpl::Delta {
240 buffer,
241 prefix_lengths,
242 suffix_lengths,
243 ..
244 } => {
245 buffer.len()
246 + prefix_lengths.estimated_data_encoded_size()
247 + suffix_lengths.estimated_data_encoded_size()
248 }
249 }
250 }
251
252 fn flush_data_page(
253 &mut self,
254 min_value: Option<ByteArray>,
255 max_value: Option<ByteArray>,
256 ) -> Result<DataPageValues<ByteArray>> {
257 let (buf, encoding) = match &mut self.encoder {
258 FallbackEncoderImpl::Plain { buffer } => (std::mem::take(buffer), Encoding::PLAIN),
259 FallbackEncoderImpl::DeltaLength { buffer, lengths } => {
260 let lengths = lengths.flush_buffer()?;
261
262 let mut out = Vec::with_capacity(lengths.len() + buffer.len());
263 out.extend_from_slice(&lengths);
264 out.extend_from_slice(buffer);
265 buffer.clear();
266 (out, Encoding::DELTA_LENGTH_BYTE_ARRAY)
267 }
268 FallbackEncoderImpl::Delta {
269 buffer,
270 prefix_lengths,
271 suffix_lengths,
272 last_value,
273 } => {
274 let prefix_lengths = prefix_lengths.flush_buffer()?;
275 let suffix_lengths = suffix_lengths.flush_buffer()?;
276
277 let mut out =
278 Vec::with_capacity(prefix_lengths.len() + suffix_lengths.len() + buffer.len());
279 out.extend_from_slice(&prefix_lengths);
280 out.extend_from_slice(&suffix_lengths);
281 out.extend_from_slice(buffer);
282 buffer.clear();
283 last_value.clear();
284 (out, Encoding::DELTA_BYTE_ARRAY)
285 }
286 };
287
288 let variable_length_bytes = Some(self.variable_length_bytes);
290 self.variable_length_bytes = 0;
291
292 Ok(DataPageValues {
293 buf: buf.into(),
294 num_values: std::mem::take(&mut self.num_values),
295 encoding,
296 min_value,
297 max_value,
298 nan_count: None,
299 variable_length_bytes,
300 })
301 }
302}
303
304#[derive(Debug, Default)]
306struct ByteArrayStorage {
307 page: Vec<u8>,
309
310 values: Vec<std::ops::Range<usize>>,
311}
312
313impl Storage for ByteArrayStorage {
314 type Key = u64;
315 type Value = [u8];
316
317 fn get(&self, idx: Self::Key) -> &Self::Value {
318 &self.page[self.values[idx as usize].clone()]
319 }
320
321 fn push(&mut self, value: &Self::Value) -> Self::Key {
322 let key = self.values.len();
323
324 self.page.reserve(4 + value.len());
325 self.page.extend_from_slice((value.len() as u32).as_bytes());
326
327 let start = self.page.len();
328 self.page.extend_from_slice(value);
329 self.values.push(start..self.page.len());
330
331 key as u64
332 }
333
334 fn estimated_memory_size(&self) -> usize {
335 self.page.capacity() * std::mem::size_of::<u8>()
336 + self.values.capacity() * std::mem::size_of::<std::ops::Range<usize>>()
337 }
338}
339
340#[derive(Debug, Default)]
342struct DictEncoder {
343 interner: Interner<ByteArrayStorage>,
344 indices: Vec<u64>,
345 variable_length_bytes: i64,
346}
347
348impl DictEncoder {
349 fn encode<T>(&mut self, values: T, indices: impl ExactSizeIterator<Item = usize>)
351 where
352 T: ArrayAccessor + Copy,
353 T::Item: AsRef<[u8]>,
354 {
355 self.indices.reserve(indices.len());
356
357 for idx in indices {
358 let value = values.value(idx);
359 let interned = self.interner.intern(value.as_ref());
360 self.indices.push(interned);
361 self.variable_length_bytes += value.as_ref().len() as i64;
362 }
363 }
364
365 fn bit_width(&self) -> u8 {
366 let length = self.interner.storage().values.len();
367 num_required_bits(length.saturating_sub(1) as u64)
368 }
369
370 fn estimated_memory_size(&self) -> usize {
371 self.interner.estimated_memory_size() + self.indices.capacity() * std::mem::size_of::<u64>()
372 }
373
374 fn estimated_data_page_size(&self) -> usize {
375 let bit_width = self.bit_width();
376 1 + RleEncoder::max_buffer_size(bit_width, self.indices.len())
377 }
378
379 fn estimated_dict_page_size(&self) -> usize {
380 self.interner.storage().page.len()
381 }
382
383 fn flush_dict_page(self) -> DictionaryPage {
384 let storage = self.interner.into_inner();
385
386 DictionaryPage {
387 buf: storage.page.into(),
388 num_values: storage.values.len(),
389 is_sorted: false,
390 }
391 }
392
393 fn flush_data_page(
394 &mut self,
395 min_value: Option<ByteArray>,
396 max_value: Option<ByteArray>,
397 ) -> DataPageValues<ByteArray> {
398 let num_values = self.indices.len();
399 let buffer_len = self.estimated_data_page_size();
400 let mut buffer = Vec::with_capacity(buffer_len);
401 buffer.push(self.bit_width());
402
403 let mut encoder = RleEncoder::new_from_buf(self.bit_width(), buffer);
404 for index in &self.indices {
405 encoder.put(*index)
406 }
407
408 self.indices.clear();
409
410 let variable_length_bytes = Some(self.variable_length_bytes);
412 self.variable_length_bytes = 0;
413
414 DataPageValues {
415 buf: encoder.consume().into(),
416 num_values,
417 encoding: Encoding::RLE_DICTIONARY,
418 min_value,
419 max_value,
420 nan_count: None,
421 variable_length_bytes,
422 }
423 }
424}
425
426pub struct ByteArrayEncoder {
427 fallback: FallbackEncoder,
428 dict_encoder: Option<DictEncoder>,
429 statistics_enabled: EnabledStatistics,
430 min_value: Option<ByteArray>,
431 max_value: Option<ByteArray>,
432 bloom_filter: Option<Sbbf>,
433 bloom_filter_target_fpp: f64,
434 geo_stats_accumulator: Option<Box<dyn GeoStatsAccumulator>>,
435}
436
437impl ColumnValueEncoder for ByteArrayEncoder {
438 type T = ByteArray;
439 type Values = dyn Array;
440 fn flush_bloom_filter(&mut self) -> Option<Sbbf> {
441 let mut sbbf = self.bloom_filter.take()?;
442 sbbf.fold_to_target_fpp(self.bloom_filter_target_fpp);
443 Some(sbbf)
444 }
445
446 fn try_new(
447 descr: &ColumnDescPtr,
448 props: &WriterProperties,
449 column_props: &ResolvedColumnProperties,
450 ) -> Result<Self>
451 where
452 Self: Sized,
453 {
454 let dictionary = column_props.dictionary_enabled.then(DictEncoder::default);
455
456 let fallback = FallbackEncoder::new(props, column_props)?;
457
458 let (bloom_filter, bloom_filter_target_fpp) = create_bloom_filter(column_props)?;
459
460 let statistics_enabled = column_props.statistics_enabled;
461
462 let geo_stats_accumulator = try_new_geo_stats_accumulator(descr);
463
464 Ok(Self {
465 fallback,
466 statistics_enabled,
467 bloom_filter,
468 bloom_filter_target_fpp,
469 dict_encoder: dictionary,
470 min_value: None,
471 max_value: None,
472 geo_stats_accumulator,
473 })
474 }
475
476 fn write(&mut self, _values: &Self::Values, _offset: usize, _len: usize) -> Result<()> {
477 unreachable!("should call write_gather instead")
478 }
479
480 fn write_gather(&mut self, values: &Self::Values, indices: &[usize]) -> Result<()> {
481 downcast_op!(
482 values.data_type(),
483 values,
484 encode,
485 indices.iter().copied(),
486 self
487 );
488 Ok(())
489 }
490
491 fn count_values_within_byte_budget_gather(
492 values: &Self::Values,
493 indices: &[usize],
494 byte_budget: usize,
495 ) -> Option<usize> {
496 let count = match values.data_type() {
510 DataType::Utf8 => count_within_budget_offsets(
511 values.as_any().downcast_ref::<StringArray>().unwrap(),
512 indices,
513 byte_budget,
514 ),
515 DataType::LargeUtf8 => count_within_budget_offsets(
516 values.as_any().downcast_ref::<LargeStringArray>().unwrap(),
517 indices,
518 byte_budget,
519 ),
520 DataType::Binary => count_within_budget_offsets(
521 values.as_any().downcast_ref::<BinaryArray>().unwrap(),
522 indices,
523 byte_budget,
524 ),
525 DataType::LargeBinary => count_within_budget_offsets(
526 values.as_any().downcast_ref::<LargeBinaryArray>().unwrap(),
527 indices,
528 byte_budget,
529 ),
530 DataType::Utf8View => {
535 let array = values.as_any().downcast_ref::<StringViewArray>().unwrap();
536 count_within_budget_views(
537 array.views(),
538 indices,
539 byte_budget,
540 max_view_value_len(array.data_buffers()),
541 )
542 }
543 DataType::BinaryView => {
544 let array = values.as_any().downcast_ref::<BinaryViewArray>().unwrap();
545 count_within_budget_views(
546 array.views(),
547 indices,
548 byte_budget,
549 max_view_value_len(array.data_buffers()),
550 )
551 }
552 DataType::Dictionary(_, _) => indices.len(),
558 data_type => unreachable!("ByteArrayEncoder cannot be constructed for {data_type:?}"),
565 };
566 Some(count)
567 }
568
569 fn num_values(&self) -> usize {
570 match &self.dict_encoder {
571 Some(encoder) => encoder.indices.len(),
572 None => self.fallback.num_values,
573 }
574 }
575
576 fn has_dictionary(&self) -> bool {
577 self.dict_encoder.is_some()
578 }
579
580 fn compresses_against_previous_value(&self) -> bool {
581 self.dict_encoder.is_none()
585 && matches!(self.fallback.encoder, FallbackEncoderImpl::Delta { .. })
586 }
587
588 fn estimated_memory_size(&self) -> usize {
589 let encoder_size = match &self.dict_encoder {
590 Some(encoder) => encoder.estimated_memory_size(),
591 None => self.fallback.estimated_data_page_size(),
594 };
595
596 let bloom_filter_size = self
597 .bloom_filter
598 .as_ref()
599 .map(|bf| bf.estimated_memory_size())
600 .unwrap_or_default();
601
602 let stats_size = self.min_value.as_ref().map(|v| v.len()).unwrap_or_default()
603 + self.max_value.as_ref().map(|v| v.len()).unwrap_or_default();
604
605 encoder_size + bloom_filter_size + stats_size
606 }
607
608 fn estimated_dict_page_size(&self) -> Option<usize> {
609 Some(self.dict_encoder.as_ref()?.estimated_dict_page_size())
610 }
611
612 fn estimated_data_page_size(&self) -> usize {
617 match &self.dict_encoder {
618 Some(encoder) => encoder.estimated_data_page_size(),
619 None => self.fallback.estimated_data_page_size(),
620 }
621 }
622
623 fn flush_dict_page(&mut self) -> Result<Option<DictionaryPage>> {
624 match self.dict_encoder.take() {
625 Some(encoder) => {
626 if !encoder.indices.is_empty() {
627 return Err(general_err!(
628 "Must flush data pages before flushing dictionary"
629 ));
630 }
631
632 Ok(Some(encoder.flush_dict_page()))
633 }
634 _ => Ok(None),
635 }
636 }
637
638 fn flush_data_page(&mut self) -> Result<DataPageValues<ByteArray>> {
639 let min_value = self.min_value.take();
640 let max_value = self.max_value.take();
641
642 match &mut self.dict_encoder {
643 Some(encoder) => Ok(encoder.flush_data_page(min_value, max_value)),
644 _ => self.fallback.flush_data_page(min_value, max_value),
645 }
646 }
647
648 fn flush_geospatial_statistics(&mut self) -> Option<Box<GeospatialStatistics>> {
649 self.geo_stats_accumulator.as_mut().map(|a| a.finish())?
650 }
651}
652
653fn encode<T, I>(values: T, indices: I, encoder: &mut ByteArrayEncoder)
657where
658 T: ArrayAccessor + Copy,
659 T::Item: Copy + Ord + AsRef<[u8]>,
660 I: ExactSizeIterator<Item = usize> + Clone,
661{
662 if encoder.statistics_enabled != EnabledStatistics::None {
663 if let Some(accumulator) = encoder.geo_stats_accumulator.as_mut() {
664 update_geo_stats_accumulator(accumulator.as_mut(), values, indices.clone());
665 } else if let Some((min, max)) = compute_min_max(values, indices.clone()) {
666 let min = min.as_ref();
671 if encoder.min_value.as_ref().is_none_or(|m| m.data() > min) {
672 encoder.min_value = Some(min.to_vec().into());
673 }
674
675 let max = max.as_ref();
676 if encoder.max_value.as_ref().is_none_or(|m| m.data() < max) {
677 encoder.max_value = Some(max.to_vec().into());
678 }
679 }
680 }
681
682 if let Some(bloom_filter) = &mut encoder.bloom_filter {
684 for idx in indices.clone() {
685 bloom_filter.insert(values.value(idx).as_ref());
686 }
687 }
688
689 match &mut encoder.dict_encoder {
690 Some(dict_encoder) => dict_encoder.encode(values, indices),
691 None => encoder.fallback.encode(values, indices),
692 }
693}
694
695fn max_view_value_len(buffers: &[Buffer]) -> usize {
697 const MAX_INLINE_VIEW_LEN: usize = 12;
699 buffers
704 .iter()
705 .map(|b| b.len())
706 .max()
707 .unwrap_or(0)
708 .max(MAX_INLINE_VIEW_LEN)
709}
710
711fn count_within_budget_views(
715 views: &[u128],
716 indices: &[usize],
717 byte_budget: usize,
718 max_value_len: usize,
719) -> usize {
720 let per_value = max_value_len + std::mem::size_of::<u32>();
731 if indices.len().saturating_mul(per_value) <= byte_budget {
732 return indices.len();
733 }
734 let mut cum: usize = 0;
737 for (i, idx) in indices.iter().enumerate() {
738 let len = (views[*idx] as u32) as usize;
739 cum = cum.saturating_add(len + std::mem::size_of::<u32>());
740 if cum > byte_budget {
741 return i + 1;
742 }
743 }
744 indices.len()
745}
746
747fn count_within_budget_offsets<T: ByteArrayType>(
754 values: &GenericByteArray<T>,
755 indices: &[usize],
756 byte_budget: usize,
757) -> usize {
758 if indices.is_empty() {
759 return 0;
760 }
761 let n = indices.len();
762 let first = indices[0];
763 let last = indices[n - 1];
764 let offsets = values.value_offsets();
765 let prefix_overhead = std::mem::size_of::<u32>();
767
768 if last >= first {
776 let payload = (offsets[last + 1] - offsets[first]).as_usize();
777 if payload + n * prefix_overhead <= byte_budget {
778 return n;
779 }
780 }
781
782 let mut cum: usize = 0;
784 for (i, idx) in indices.iter().enumerate() {
785 let len = (offsets[idx + 1] - offsets[*idx]).as_usize() + prefix_overhead;
786 cum = cum.saturating_add(len);
787 if cum > byte_budget {
788 return i + 1;
789 }
790 }
791 n
792}
793
794fn compute_min_max<T>(
798 array: T,
799 mut valid: impl Iterator<Item = usize>,
800) -> Option<(T::Item, T::Item)>
801where
802 T: ArrayAccessor,
803 T::Item: Copy + Ord + AsRef<[u8]>,
804{
805 let first_idx = valid.next()?;
806
807 let first_val = array.value(first_idx);
808 let mut min = first_val;
809 let mut max = first_val;
810 for idx in valid {
811 let val = array.value(idx);
812 min = min.min(val);
813 max = max.max(val);
814 }
815 Some((min, max))
816}
817
818fn update_geo_stats_accumulator<T>(
820 bounder: &mut dyn GeoStatsAccumulator,
821 array: T,
822 valid: impl Iterator<Item = usize>,
823) where
824 T: ArrayAccessor,
825 T::Item: Copy + Ord + AsRef<[u8]>,
826{
827 if bounder.is_valid() {
828 for idx in valid {
829 let val = array.value(idx);
830 bounder.update_wkb(val.as_ref());
831 }
832 }
833}