1use bytes::Bytes;
19
20use crate::basic::{ConvertedType, Encoding, LogicalType, Type};
21use crate::bloom_filter::Sbbf;
22use crate::column::writer::{fallback_encoding, has_dictionary_support, update_max, update_min};
23use crate::data_type::DataType;
24use crate::data_type::private::ParquetValueType;
25use crate::encodings::encoding::{DictEncoder, Encoder, get_encoder_with_properties};
26use crate::errors::{ParquetError, Result};
27use crate::file::properties::{EnabledStatistics, ResolvedColumnProperties, WriterProperties};
28use crate::geospatial::accumulator::{GeoStatsAccumulator, try_new_geo_stats_accumulator};
29use crate::geospatial::statistics::GeospatialStatistics;
30use crate::schema::types::ColumnDescPtr;
31
32pub trait ColumnValues {
34 fn len(&self) -> usize;
36}
37
38#[cfg(feature = "arrow")]
39impl ColumnValues for dyn arrow_array::Array {
40 fn len(&self) -> usize {
41 arrow_array::Array::len(self)
42 }
43}
44
45impl<T: ParquetValueType> ColumnValues for [T] {
46 fn len(&self) -> usize {
47 self.len()
48 }
49}
50
51pub struct DictionaryPage {
53 pub buf: Bytes,
54 pub num_values: usize,
55 pub is_sorted: bool,
56}
57
58pub struct DataPageValues<T> {
60 pub buf: Bytes,
61 pub num_values: usize,
62 pub encoding: Encoding,
63 pub min_value: Option<T>,
64 pub max_value: Option<T>,
65 pub nan_count: Option<u64>,
66 pub variable_length_bytes: Option<i64>,
67}
68
69pub trait ColumnValueEncoder {
72 type T: ParquetValueType;
76
77 type Values: ColumnValues + ?Sized;
79
80 #[expect(
85 private_interfaces,
86 reason = "this trait is not nameable outside the crate"
87 )]
88 fn try_new(
89 descr: &ColumnDescPtr,
90 props: &WriterProperties,
91 column_props: &ResolvedColumnProperties,
92 ) -> Result<Self>
93 where
94 Self: Sized;
95
96 fn write(&mut self, values: &Self::Values, offset: usize, len: usize) -> Result<()>;
98
99 fn write_gather(&mut self, values: &Self::Values, indices: &[usize]) -> Result<()>;
101
102 fn count_values_within_byte_budget(
119 _values: &Self::Values,
120 _offset: usize,
121 _len: usize,
122 _byte_budget: usize,
123 ) -> Option<usize> {
124 None
125 }
126
127 fn count_values_within_byte_budget_gather(
131 _values: &Self::Values,
132 _indices: &[usize],
133 _byte_budget: usize,
134 ) -> Option<usize> {
135 None
136 }
137
138 fn num_values(&self) -> usize;
140
141 fn has_dictionary(&self) -> bool;
143
144 fn compresses_against_previous_value(&self) -> bool {
162 false
163 }
164
165 fn estimated_memory_size(&self) -> usize;
168
169 fn estimated_dict_page_size(&self) -> Option<usize>;
171
172 fn estimated_data_page_size(&self) -> usize;
177
178 fn flush_dict_page(&mut self) -> Result<Option<DictionaryPage>>;
184
185 fn flush_data_page(&mut self) -> Result<DataPageValues<Self::T>>;
187
188 fn flush_bloom_filter(&mut self) -> Option<Sbbf>;
192
193 fn flush_geospatial_statistics(&mut self) -> Option<Box<GeospatialStatistics>>;
196}
197
198pub struct ColumnValueEncoderImpl<T: DataType> {
199 encoder: Box<dyn Encoder<T>>,
200 dict_encoder: Option<DictEncoder<T>>,
201 descr: ColumnDescPtr,
202 num_values: usize,
203 statistics_enabled: EnabledStatistics,
204 min_value: Option<T::T>,
205 max_value: Option<T::T>,
206 nan_count: Option<u64>,
207 bloom_filter: Option<Sbbf>,
208 bloom_filter_target_fpp: f64,
209 variable_length_bytes: Option<i64>,
210 geo_stats_accumulator: Option<Box<dyn GeoStatsAccumulator>>,
211}
212
213impl<T: DataType> ColumnValueEncoderImpl<T> {
214 fn is_floating_point_column(&self) -> bool {
215 matches!(self.descr.physical_type(), Type::FLOAT | Type::DOUBLE)
216 || self.descr.logical_type_ref() == Some(&LogicalType::Float16)
217 }
218
219 fn write_slice(&mut self, slice: &[T::T]) -> Result<()> {
220 if self.statistics_enabled != EnabledStatistics::None
221 && self.descr.converted_type() != ConvertedType::INTERVAL
223 {
224 if let Some(accumulator) = self.geo_stats_accumulator.as_deref_mut() {
225 update_geo_stats_accumulator(accumulator, slice.iter());
226 } else if let Some((min, max, nan_count)) =
227 T::T::min_max(self.descr.get_basic_info(), slice)
228 {
229 update_min(&self.descr, &min, &mut self.min_value);
230 update_max(&self.descr, &max, &mut self.max_value);
231 if self.is_floating_point_column() {
232 *self.nan_count.get_or_insert(0) += nan_count;
233 }
234 }
235
236 if let Some(var_bytes) = T::T::variable_length_bytes(slice) {
237 *self.variable_length_bytes.get_or_insert(0) += var_bytes;
238 }
239 }
240
241 match &mut self.dict_encoder {
244 Some(encoder) => encoder.put(slice),
245 _ => {
246 if let Some(bloom_filter) = &mut self.bloom_filter {
247 for value in slice {
248 bloom_filter.insert(value);
249 }
250 }
251 self.encoder.put(slice)
252 }
253 }
254 }
255}
256
257impl<T: DataType> ColumnValueEncoder for ColumnValueEncoderImpl<T> {
258 type T = T::T;
259
260 type Values = [T::T];
261
262 fn flush_bloom_filter(&mut self) -> Option<Sbbf> {
263 let mut sbbf = self.bloom_filter.take()?;
264 sbbf.fold_to_target_fpp(self.bloom_filter_target_fpp);
265 Some(sbbf)
266 }
267
268 #[expect(
269 private_interfaces,
270 reason = "this trait is not nameable outside the crate"
271 )]
272 fn try_new(
273 descr: &ColumnDescPtr,
274 props: &WriterProperties,
275 column_props: &ResolvedColumnProperties,
276 ) -> Result<Self> {
277 let dict_supported =
278 column_props.dictionary_enabled && has_dictionary_support(T::get_physical_type());
279 let dict_encoder = dict_supported.then(|| DictEncoder::new(descr.clone()));
280
281 let encoder = get_encoder_with_properties(
283 column_props
284 .encoding
285 .unwrap_or_else(|| fallback_encoding(T::get_physical_type(), props)),
286 descr,
287 column_props,
288 )?;
289
290 let statistics_enabled = column_props.statistics_enabled;
291
292 let (bloom_filter, bloom_filter_target_fpp) = create_bloom_filter(column_props)?;
293
294 let geo_stats_accumulator = try_new_geo_stats_accumulator(descr);
295
296 Ok(Self {
297 encoder,
298 dict_encoder,
299 descr: descr.clone(),
300 num_values: 0,
301 statistics_enabled,
302 bloom_filter,
303 bloom_filter_target_fpp,
304 min_value: None,
305 max_value: None,
306 nan_count: None,
307 variable_length_bytes: None,
308 geo_stats_accumulator,
309 })
310 }
311
312 fn write(&mut self, values: &[T::T], offset: usize, len: usize) -> Result<()> {
313 self.num_values += len;
314
315 let slice = values.get(offset..offset + len).ok_or_else(|| {
316 general_err!(
317 "Expected to write {} values, but have only {}",
318 len,
319 values.len() - offset
320 )
321 })?;
322
323 self.write_slice(slice)
324 }
325
326 fn write_gather(&mut self, values: &Self::Values, indices: &[usize]) -> Result<()> {
327 self.num_values += indices.len();
328 let slice: Vec<_> = indices.iter().map(|idx| values[*idx].clone()).collect();
329 self.write_slice(&slice)
330 }
331
332 fn count_values_within_byte_budget(
333 values: &[T::T],
334 offset: usize,
335 len: usize,
336 byte_budget: usize,
337 ) -> Option<usize> {
338 let end = (offset + len).min(values.len());
342 let start = offset.min(end);
343 count_within_budget::<T>(
344 end - start,
345 byte_budget,
346 values[start..end].iter().map(Some),
347 )
348 }
349
350 fn count_values_within_byte_budget_gather(
351 values: &[T::T],
352 indices: &[usize],
353 byte_budget: usize,
354 ) -> Option<usize> {
355 count_within_budget::<T>(
359 indices.len(),
360 byte_budget,
361 indices.iter().map(|&i| values.get(i)),
362 )
363 }
364
365 fn num_values(&self) -> usize {
366 self.num_values
367 }
368
369 fn has_dictionary(&self) -> bool {
370 self.dict_encoder.is_some()
371 }
372
373 fn compresses_against_previous_value(&self) -> bool {
374 self.dict_encoder.is_none() && self.encoder.encoding() == Encoding::DELTA_BYTE_ARRAY
377 }
378
379 fn estimated_memory_size(&self) -> usize {
380 let encoder_size = self.encoder.estimated_memory_size();
381
382 let dict_encoder_size = self
383 .dict_encoder
384 .as_ref()
385 .map(|encoder| encoder.estimated_memory_size())
386 .unwrap_or_default();
387
388 let bloom_filter_size = self
389 .bloom_filter
390 .as_ref()
391 .map(|bf| bf.estimated_memory_size())
392 .unwrap_or_default();
393
394 encoder_size + dict_encoder_size + bloom_filter_size
395 }
396
397 fn estimated_dict_page_size(&self) -> Option<usize> {
398 Some(self.dict_encoder.as_ref()?.dict_encoded_size())
399 }
400
401 fn estimated_data_page_size(&self) -> usize {
402 match &self.dict_encoder {
403 Some(encoder) => encoder.estimated_data_encoded_size(),
404 _ => self.encoder.estimated_data_encoded_size(),
405 }
406 }
407
408 fn flush_dict_page(&mut self) -> Result<Option<DictionaryPage>> {
409 match self.dict_encoder.take() {
410 Some(encoder) => {
411 if self.num_values != 0 {
412 return Err(general_err!(
413 "Must flush data pages before flushing dictionary"
414 ));
415 }
416
417 if let Some(bloom_filter) = &mut self.bloom_filter {
418 for value in encoder.uniques() {
419 bloom_filter.insert(value);
420 }
421 }
422
423 let buf = encoder.write_dict()?;
424
425 Ok(Some(DictionaryPage {
426 buf,
427 num_values: encoder.num_entries(),
428 is_sorted: encoder.is_sorted(),
429 }))
430 }
431 _ => Ok(None),
432 }
433 }
434
435 fn flush_data_page(&mut self) -> Result<DataPageValues<T::T>> {
436 let (buf, encoding) = match &mut self.dict_encoder {
437 Some(encoder) => (encoder.write_indices()?, Encoding::RLE_DICTIONARY),
438 _ => (self.encoder.flush_buffer()?, self.encoder.encoding()),
439 };
440
441 Ok(DataPageValues {
442 buf,
443 encoding,
444 num_values: std::mem::take(&mut self.num_values),
445 min_value: self.min_value.take(),
446 max_value: self.max_value.take(),
447 nan_count: self.nan_count.take(),
448 variable_length_bytes: self.variable_length_bytes.take(),
449 })
450 }
451
452 fn flush_geospatial_statistics(&mut self) -> Option<Box<GeospatialStatistics>> {
453 self.geo_stats_accumulator.as_mut().map(|a| a.finish())?
454 }
455}
456
457pub(crate) fn create_bloom_filter(
460 column_props: &ResolvedColumnProperties,
461) -> Result<(Option<Sbbf>, f64)> {
462 match column_props.bloom_filter_properties.as_ref() {
463 Some(bf_props) => Ok((
464 Some(Sbbf::new_with_ndv_fpp(bf_props.ndv(), bf_props.fpp())?),
465 bf_props.fpp(),
466 )),
467 None => Ok((None, 0.0)),
468 }
469}
470
471fn update_geo_stats_accumulator<'a, T, I>(bounder: &mut dyn GeoStatsAccumulator, iter: I)
472where
473 T: ParquetValueType + 'a,
474 I: Iterator<Item = &'a T>,
475{
476 if bounder.is_valid() {
477 for val in iter {
478 bounder.update_wkb(val.as_bytes());
479 }
480 }
481}
482
483#[inline]
497fn plain_encoded_byte_size<T: DataType>(value: &T::T) -> usize {
498 let (overhead, bytes) = value.dict_encoding_size();
499 match <T::T as ParquetValueType>::PHYSICAL_TYPE {
500 Type::BYTE_ARRAY => overhead + bytes,
502 Type::FIXED_LEN_BYTE_ARRAY => bytes,
505 _ => overhead,
509 }
510}
511
512#[inline]
527fn count_within_budget<'a, T: DataType>(
528 n: usize,
529 byte_budget: usize,
530 vals: impl Iterator<Item = Option<&'a T::T>>,
531) -> Option<usize>
532where
533 T::T: 'a,
534{
535 let phys = <T::T as ParquetValueType>::PHYSICAL_TYPE;
538 if phys != Type::BYTE_ARRAY && phys != Type::FIXED_LEN_BYTE_ARRAY {
539 let per = std::mem::size_of::<T::T>().max(1);
540 return Some((byte_budget / per).max(1).min(n));
541 }
542 let mut cum: usize = 0;
544 for (i, v) in vals.enumerate() {
545 if let Some(v) = v {
546 cum = cum.saturating_add(plain_encoded_byte_size::<T>(v));
547 }
548 if cum > byte_budget {
549 return Some(i + 1);
550 }
551 }
552 Some(n)
553}