1use bytes::Bytes;
19
20use crate::basic::{ConvertedType, Encoding, LogicalType, Type};
21use crate::bloom_filter::Sbbf;
22use crate::column::writer::{
23 compare_greater, fallback_encoding, has_dictionary_support, is_nan, update_max, update_min,
24};
25use crate::data_type::DataType;
26use crate::data_type::private::ParquetValueType;
27use crate::encodings::encoding::{DictEncoder, Encoder, get_encoder};
28use crate::errors::{ParquetError, Result};
29use crate::file::properties::{EnabledStatistics, WriterProperties};
30use crate::geospatial::accumulator::{GeoStatsAccumulator, try_new_geo_stats_accumulator};
31use crate::geospatial::statistics::GeospatialStatistics;
32use crate::schema::types::{BasicTypeInfo, ColumnDescPtr};
33
34pub trait ColumnValues {
36 fn len(&self) -> usize;
38}
39
40#[cfg(feature = "arrow")]
41impl ColumnValues for dyn arrow_array::Array {
42 fn len(&self) -> usize {
43 arrow_array::Array::len(self)
44 }
45}
46
47impl<T: ParquetValueType> ColumnValues for [T] {
48 fn len(&self) -> usize {
49 self.len()
50 }
51}
52
53pub struct DictionaryPage {
55 pub buf: Bytes,
56 pub num_values: usize,
57 pub is_sorted: bool,
58}
59
60pub struct DataPageValues<T> {
62 pub buf: Bytes,
63 pub num_values: usize,
64 pub encoding: Encoding,
65 pub min_value: Option<T>,
66 pub max_value: Option<T>,
67 pub nan_count: Option<u64>,
68 pub variable_length_bytes: Option<i64>,
69}
70
71pub trait ColumnValueEncoder {
74 type T: ParquetValueType;
78
79 type Values: ColumnValues + ?Sized;
81
82 fn try_new(descr: &ColumnDescPtr, props: &WriterProperties) -> Result<Self>
84 where
85 Self: Sized;
86
87 fn write(&mut self, values: &Self::Values, offset: usize, len: usize) -> Result<()>;
89
90 fn write_gather(&mut self, values: &Self::Values, indices: &[usize]) -> Result<()>;
92
93 fn count_values_within_byte_budget(
110 _values: &Self::Values,
111 _offset: usize,
112 _len: usize,
113 _byte_budget: usize,
114 ) -> Option<usize> {
115 None
116 }
117
118 fn count_values_within_byte_budget_gather(
122 _values: &Self::Values,
123 _indices: &[usize],
124 _byte_budget: usize,
125 ) -> Option<usize> {
126 None
127 }
128
129 fn num_values(&self) -> usize;
131
132 fn has_dictionary(&self) -> bool;
134
135 fn estimated_memory_size(&self) -> usize;
138
139 fn estimated_dict_page_size(&self) -> Option<usize>;
141
142 fn estimated_data_page_size(&self) -> usize;
147
148 fn flush_dict_page(&mut self) -> Result<Option<DictionaryPage>>;
154
155 fn flush_data_page(&mut self) -> Result<DataPageValues<Self::T>>;
157
158 fn flush_bloom_filter(&mut self) -> Option<Sbbf>;
162
163 fn flush_geospatial_statistics(&mut self) -> Option<Box<GeospatialStatistics>>;
166}
167
168pub struct ColumnValueEncoderImpl<T: DataType> {
169 encoder: Box<dyn Encoder<T>>,
170 dict_encoder: Option<DictEncoder<T>>,
171 descr: ColumnDescPtr,
172 num_values: usize,
173 statistics_enabled: EnabledStatistics,
174 min_value: Option<T::T>,
175 max_value: Option<T::T>,
176 nan_count: Option<u64>,
177 bloom_filter: Option<Sbbf>,
178 bloom_filter_target_fpp: f64,
179 variable_length_bytes: Option<i64>,
180 geo_stats_accumulator: Option<Box<dyn GeoStatsAccumulator>>,
181}
182
183impl<T: DataType> ColumnValueEncoderImpl<T> {
184 fn is_floating_point_column(&self) -> bool {
185 matches!(self.descr.physical_type(), Type::FLOAT | Type::DOUBLE)
186 || self.descr.logical_type_ref() == Some(&LogicalType::Float16)
187 }
188
189 fn write_slice(&mut self, slice: &[T::T]) -> Result<()> {
190 if self.statistics_enabled != EnabledStatistics::None
191 && self.descr.converted_type() != ConvertedType::INTERVAL
193 {
194 if let Some(accumulator) = self.geo_stats_accumulator.as_deref_mut() {
195 update_geo_stats_accumulator(accumulator, slice.iter());
196 } else if let Some((min, max, nan_count)) =
197 get_min_max(self.descr.get_basic_info(), slice.iter())
198 {
199 update_min(&self.descr, &min, &mut self.min_value);
200 update_max(&self.descr, &max, &mut self.max_value);
201 if self.is_floating_point_column() {
202 *self.nan_count.get_or_insert(0) += nan_count;
203 }
204 }
205
206 if let Some(var_bytes) = T::T::variable_length_bytes(slice) {
207 *self.variable_length_bytes.get_or_insert(0) += var_bytes;
208 }
209 }
210
211 if let Some(bloom_filter) = &mut self.bloom_filter {
213 for value in slice {
214 bloom_filter.insert(value);
215 }
216 }
217
218 match &mut self.dict_encoder {
219 Some(encoder) => encoder.put(slice),
220 _ => self.encoder.put(slice),
221 }
222 }
223}
224
225impl<T: DataType> ColumnValueEncoder for ColumnValueEncoderImpl<T> {
226 type T = T::T;
227
228 type Values = [T::T];
229
230 fn flush_bloom_filter(&mut self) -> Option<Sbbf> {
231 let mut sbbf = self.bloom_filter.take()?;
232 sbbf.fold_to_target_fpp(self.bloom_filter_target_fpp);
233 Some(sbbf)
234 }
235
236 fn try_new(descr: &ColumnDescPtr, props: &WriterProperties) -> Result<Self> {
237 let dict_supported = props.dictionary_enabled(descr.path())
238 && has_dictionary_support(T::get_physical_type(), props);
239 let dict_encoder = dict_supported.then(|| DictEncoder::new(descr.clone()));
240
241 let encoder = get_encoder(
243 props
244 .encoding(descr.path())
245 .unwrap_or_else(|| fallback_encoding(T::get_physical_type(), props)),
246 descr,
247 )?;
248
249 let statistics_enabled = props.statistics_enabled(descr.path());
250
251 let (bloom_filter, bloom_filter_target_fpp) = create_bloom_filter(props, descr)?;
252
253 let geo_stats_accumulator = try_new_geo_stats_accumulator(descr);
254
255 Ok(Self {
256 encoder,
257 dict_encoder,
258 descr: descr.clone(),
259 num_values: 0,
260 statistics_enabled,
261 bloom_filter,
262 bloom_filter_target_fpp,
263 min_value: None,
264 max_value: None,
265 nan_count: None,
266 variable_length_bytes: None,
267 geo_stats_accumulator,
268 })
269 }
270
271 fn write(&mut self, values: &[T::T], offset: usize, len: usize) -> Result<()> {
272 self.num_values += len;
273
274 let slice = values.get(offset..offset + len).ok_or_else(|| {
275 general_err!(
276 "Expected to write {} values, but have only {}",
277 len,
278 values.len() - offset
279 )
280 })?;
281
282 self.write_slice(slice)
283 }
284
285 fn write_gather(&mut self, values: &Self::Values, indices: &[usize]) -> Result<()> {
286 self.num_values += indices.len();
287 let slice: Vec<_> = indices.iter().map(|idx| values[*idx].clone()).collect();
288 self.write_slice(&slice)
289 }
290
291 fn count_values_within_byte_budget(
292 values: &[T::T],
293 offset: usize,
294 len: usize,
295 byte_budget: usize,
296 ) -> Option<usize> {
297 let end = (offset + len).min(values.len());
301 let start = offset.min(end);
302 count_within_budget::<T>(
303 end - start,
304 byte_budget,
305 values[start..end].iter().map(Some),
306 )
307 }
308
309 fn count_values_within_byte_budget_gather(
310 values: &[T::T],
311 indices: &[usize],
312 byte_budget: usize,
313 ) -> Option<usize> {
314 count_within_budget::<T>(
318 indices.len(),
319 byte_budget,
320 indices.iter().map(|&i| values.get(i)),
321 )
322 }
323
324 fn num_values(&self) -> usize {
325 self.num_values
326 }
327
328 fn has_dictionary(&self) -> bool {
329 self.dict_encoder.is_some()
330 }
331
332 fn estimated_memory_size(&self) -> usize {
333 let encoder_size = self.encoder.estimated_memory_size();
334
335 let dict_encoder_size = self
336 .dict_encoder
337 .as_ref()
338 .map(|encoder| encoder.estimated_memory_size())
339 .unwrap_or_default();
340
341 let bloom_filter_size = self
342 .bloom_filter
343 .as_ref()
344 .map(|bf| bf.estimated_memory_size())
345 .unwrap_or_default();
346
347 encoder_size + dict_encoder_size + bloom_filter_size
348 }
349
350 fn estimated_dict_page_size(&self) -> Option<usize> {
351 Some(self.dict_encoder.as_ref()?.dict_encoded_size())
352 }
353
354 fn estimated_data_page_size(&self) -> usize {
355 match &self.dict_encoder {
356 Some(encoder) => encoder.estimated_data_encoded_size(),
357 _ => self.encoder.estimated_data_encoded_size(),
358 }
359 }
360
361 fn flush_dict_page(&mut self) -> Result<Option<DictionaryPage>> {
362 match self.dict_encoder.take() {
363 Some(encoder) => {
364 if self.num_values != 0 {
365 return Err(general_err!(
366 "Must flush data pages before flushing dictionary"
367 ));
368 }
369
370 let buf = encoder.write_dict()?;
371
372 Ok(Some(DictionaryPage {
373 buf,
374 num_values: encoder.num_entries(),
375 is_sorted: encoder.is_sorted(),
376 }))
377 }
378 _ => Ok(None),
379 }
380 }
381
382 fn flush_data_page(&mut self) -> Result<DataPageValues<T::T>> {
383 let (buf, encoding) = match &mut self.dict_encoder {
384 Some(encoder) => (encoder.write_indices()?, Encoding::RLE_DICTIONARY),
385 _ => (self.encoder.flush_buffer()?, self.encoder.encoding()),
386 };
387
388 Ok(DataPageValues {
389 buf,
390 encoding,
391 num_values: std::mem::take(&mut self.num_values),
392 min_value: self.min_value.take(),
393 max_value: self.max_value.take(),
394 nan_count: self.nan_count.take(),
395 variable_length_bytes: self.variable_length_bytes.take(),
396 })
397 }
398
399 fn flush_geospatial_statistics(&mut self) -> Option<Box<GeospatialStatistics>> {
400 self.geo_stats_accumulator.as_mut().map(|a| a.finish())?
401 }
402}
403
404fn get_min_max<'a, T, I>(basic_type_info: &BasicTypeInfo, mut iter: I) -> Option<(T, T, u64)>
411where
412 T: ParquetValueType + 'a,
413 I: Iterator<Item = &'a T>,
414{
415 let first = iter.next()?;
416 let mut min_max_nan = is_nan(basic_type_info, first);
417 let mut nan_count = min_max_nan as u64;
418
419 let mut min = first;
420 let mut max = first;
421 for val in iter {
422 match (min_max_nan, is_nan(basic_type_info, val)) {
423 (false, true) => {
425 nan_count += 1;
426 continue;
427 }
428 (true, false) => {
430 min = val;
431 max = val;
432 min_max_nan = false;
433 continue;
434 }
435 (_, val_is_nan) => {
437 nan_count += val_is_nan as u64;
438 if compare_greater(basic_type_info, min, val) {
441 min = val;
442 } else if compare_greater(basic_type_info, val, max) {
443 max = val;
444 }
445 }
446 }
447 }
448
449 Some((min.clone(), max.clone(), nan_count))
450}
451
452pub(crate) fn create_bloom_filter(
455 props: &WriterProperties,
456 descr: &ColumnDescPtr,
457) -> Result<(Option<Sbbf>, f64)> {
458 match props.bloom_filter_properties(descr.path()) {
459 Some(bf_props) => Ok((
460 Some(Sbbf::new_with_ndv_fpp(bf_props.ndv(), bf_props.fpp())?),
461 bf_props.fpp(),
462 )),
463 None => Ok((None, 0.0)),
464 }
465}
466
467fn update_geo_stats_accumulator<'a, T, I>(bounder: &mut dyn GeoStatsAccumulator, iter: I)
468where
469 T: ParquetValueType + 'a,
470 I: Iterator<Item = &'a T>,
471{
472 if bounder.is_valid() {
473 for val in iter {
474 bounder.update_wkb(val.as_bytes());
475 }
476 }
477}
478
479#[inline]
493fn plain_encoded_byte_size<T: DataType>(value: &T::T) -> usize {
494 let (overhead, bytes) = value.dict_encoding_size();
495 match <T::T as ParquetValueType>::PHYSICAL_TYPE {
496 Type::BYTE_ARRAY => overhead + bytes,
498 Type::FIXED_LEN_BYTE_ARRAY => bytes,
501 _ => overhead,
505 }
506}
507
508#[inline]
523fn count_within_budget<'a, T: DataType>(
524 n: usize,
525 byte_budget: usize,
526 vals: impl Iterator<Item = Option<&'a T::T>>,
527) -> Option<usize>
528where
529 T::T: 'a,
530{
531 let phys = <T::T as ParquetValueType>::PHYSICAL_TYPE;
534 if phys != Type::BYTE_ARRAY && phys != Type::FIXED_LEN_BYTE_ARRAY {
535 let per = std::mem::size_of::<T::T>().max(1);
536 return Some((byte_budget / per).max(1).min(n));
537 }
538 let mut cum: usize = 0;
540 for (i, v) in vals.enumerate() {
541 if let Some(v) = v {
542 cum = cum.saturating_add(plain_encoded_byte_size::<T>(v));
543 }
544 if cum > byte_budget {
545 return Some(i + 1);
546 }
547 }
548 Some(n)
549}