use bytes::Bytes;
use crate::basic::{ConvertedType, Encoding, LogicalType, Type};
use crate::bloom_filter::Sbbf;
use crate::column::writer::{fallback_encoding, has_dictionary_support, update_max, update_min};
use crate::data_type::DataType;
use crate::data_type::private::ParquetValueType;
use crate::encodings::encoding::{DictEncoder, Encoder, get_encoder_with_properties};
use crate::errors::{ParquetError, Result};
use crate::file::properties::{EnabledStatistics, ResolvedColumnProperties, WriterProperties};
use crate::geospatial::accumulator::{GeoStatsAccumulator, try_new_geo_stats_accumulator};
use crate::geospatial::statistics::GeospatialStatistics;
use crate::schema::types::ColumnDescPtr;
pub trait ColumnValues {
fn len(&self) -> usize;
}
#[cfg(feature = "arrow")]
impl ColumnValues for dyn arrow_array::Array {
fn len(&self) -> usize {
arrow_array::Array::len(self)
}
}
impl<T: ParquetValueType> ColumnValues for [T] {
fn len(&self) -> usize {
self.len()
}
}
pub struct DictionaryPage {
pub buf: Bytes,
pub num_values: usize,
pub is_sorted: bool,
}
pub struct DataPageValues<T> {
pub buf: Bytes,
pub num_values: usize,
pub encoding: Encoding,
pub min_value: Option<T>,
pub max_value: Option<T>,
pub nan_count: Option<u64>,
pub variable_length_bytes: Option<i64>,
}
pub trait ColumnValueEncoder {
type T: ParquetValueType;
type Values: ColumnValues + ?Sized;
#[expect(
private_interfaces,
reason = "this trait is not nameable outside the crate"
)]
fn try_new(
descr: &ColumnDescPtr,
props: &WriterProperties,
column_props: &ResolvedColumnProperties,
) -> Result<Self>
where
Self: Sized;
fn write(&mut self, values: &Self::Values, offset: usize, len: usize) -> Result<()>;
fn write_gather(&mut self, values: &Self::Values, indices: &[usize]) -> Result<()>;
fn count_values_within_byte_budget(
_values: &Self::Values,
_offset: usize,
_len: usize,
_byte_budget: usize,
) -> Option<usize> {
None
}
fn count_values_within_byte_budget_gather(
_values: &Self::Values,
_indices: &[usize],
_byte_budget: usize,
) -> Option<usize> {
None
}
fn num_values(&self) -> usize;
fn has_dictionary(&self) -> bool;
fn compresses_against_previous_value(&self) -> bool {
false
}
fn estimated_memory_size(&self) -> usize;
fn estimated_dict_page_size(&self) -> Option<usize>;
fn estimated_data_page_size(&self) -> usize;
fn flush_dict_page(&mut self) -> Result<Option<DictionaryPage>>;
fn flush_data_page(&mut self) -> Result<DataPageValues<Self::T>>;
fn flush_bloom_filter(&mut self) -> Option<Sbbf>;
fn flush_geospatial_statistics(&mut self) -> Option<Box<GeospatialStatistics>>;
}
pub struct ColumnValueEncoderImpl<T: DataType> {
encoder: Box<dyn Encoder<T>>,
dict_encoder: Option<DictEncoder<T>>,
descr: ColumnDescPtr,
num_values: usize,
statistics_enabled: EnabledStatistics,
min_value: Option<T::T>,
max_value: Option<T::T>,
nan_count: Option<u64>,
bloom_filter: Option<Sbbf>,
bloom_filter_target_fpp: f64,
variable_length_bytes: Option<i64>,
geo_stats_accumulator: Option<Box<dyn GeoStatsAccumulator>>,
}
impl<T: DataType> ColumnValueEncoderImpl<T> {
fn is_floating_point_column(&self) -> bool {
matches!(self.descr.physical_type(), Type::FLOAT | Type::DOUBLE)
|| self.descr.logical_type_ref() == Some(&LogicalType::Float16)
}
fn write_slice(&mut self, slice: &[T::T]) -> Result<()> {
if self.statistics_enabled != EnabledStatistics::None
&& self.descr.converted_type() != ConvertedType::INTERVAL
{
if let Some(accumulator) = self.geo_stats_accumulator.as_deref_mut() {
update_geo_stats_accumulator(accumulator, slice.iter());
} else if let Some((min, max, nan_count)) =
T::T::min_max(self.descr.get_basic_info(), slice)
{
update_min(&self.descr, &min, &mut self.min_value);
update_max(&self.descr, &max, &mut self.max_value);
if self.is_floating_point_column() {
*self.nan_count.get_or_insert(0) += nan_count;
}
}
if let Some(var_bytes) = T::T::variable_length_bytes(slice) {
*self.variable_length_bytes.get_or_insert(0) += var_bytes;
}
}
match &mut self.dict_encoder {
Some(encoder) => encoder.put(slice),
_ => {
if let Some(bloom_filter) = &mut self.bloom_filter {
for value in slice {
bloom_filter.insert(value);
}
}
self.encoder.put(slice)
}
}
}
}
impl<T: DataType> ColumnValueEncoder for ColumnValueEncoderImpl<T> {
type T = T::T;
type Values = [T::T];
fn flush_bloom_filter(&mut self) -> Option<Sbbf> {
let mut sbbf = self.bloom_filter.take()?;
sbbf.fold_to_target_fpp(self.bloom_filter_target_fpp);
Some(sbbf)
}
#[expect(
private_interfaces,
reason = "this trait is not nameable outside the crate"
)]
fn try_new(
descr: &ColumnDescPtr,
props: &WriterProperties,
column_props: &ResolvedColumnProperties,
) -> Result<Self> {
let dict_supported =
column_props.dictionary_enabled && has_dictionary_support(T::get_physical_type());
let dict_encoder = dict_supported.then(|| DictEncoder::new(descr.clone()));
let encoder = get_encoder_with_properties(
column_props
.encoding
.unwrap_or_else(|| fallback_encoding(T::get_physical_type(), props)),
descr,
column_props,
)?;
let statistics_enabled = column_props.statistics_enabled;
let (bloom_filter, bloom_filter_target_fpp) = create_bloom_filter(column_props)?;
let geo_stats_accumulator = try_new_geo_stats_accumulator(descr);
Ok(Self {
encoder,
dict_encoder,
descr: descr.clone(),
num_values: 0,
statistics_enabled,
bloom_filter,
bloom_filter_target_fpp,
min_value: None,
max_value: None,
nan_count: None,
variable_length_bytes: None,
geo_stats_accumulator,
})
}
fn write(&mut self, values: &[T::T], offset: usize, len: usize) -> Result<()> {
self.num_values += len;
let slice = values.get(offset..offset + len).ok_or_else(|| {
general_err!(
"Expected to write {} values, but have only {}",
len,
values.len() - offset
)
})?;
self.write_slice(slice)
}
fn write_gather(&mut self, values: &Self::Values, indices: &[usize]) -> Result<()> {
self.num_values += indices.len();
let slice: Vec<_> = indices.iter().map(|idx| values[*idx].clone()).collect();
self.write_slice(&slice)
}
fn count_values_within_byte_budget(
values: &[T::T],
offset: usize,
len: usize,
byte_budget: usize,
) -> Option<usize> {
let end = (offset + len).min(values.len());
let start = offset.min(end);
count_within_budget::<T>(
end - start,
byte_budget,
values[start..end].iter().map(Some),
)
}
fn count_values_within_byte_budget_gather(
values: &[T::T],
indices: &[usize],
byte_budget: usize,
) -> Option<usize> {
count_within_budget::<T>(
indices.len(),
byte_budget,
indices.iter().map(|&i| values.get(i)),
)
}
fn num_values(&self) -> usize {
self.num_values
}
fn has_dictionary(&self) -> bool {
self.dict_encoder.is_some()
}
fn compresses_against_previous_value(&self) -> bool {
self.dict_encoder.is_none() && self.encoder.encoding() == Encoding::DELTA_BYTE_ARRAY
}
fn estimated_memory_size(&self) -> usize {
let encoder_size = self.encoder.estimated_memory_size();
let dict_encoder_size = self
.dict_encoder
.as_ref()
.map(|encoder| encoder.estimated_memory_size())
.unwrap_or_default();
let bloom_filter_size = self
.bloom_filter
.as_ref()
.map(|bf| bf.estimated_memory_size())
.unwrap_or_default();
encoder_size + dict_encoder_size + bloom_filter_size
}
fn estimated_dict_page_size(&self) -> Option<usize> {
Some(self.dict_encoder.as_ref()?.dict_encoded_size())
}
fn estimated_data_page_size(&self) -> usize {
match &self.dict_encoder {
Some(encoder) => encoder.estimated_data_encoded_size(),
_ => self.encoder.estimated_data_encoded_size(),
}
}
fn flush_dict_page(&mut self) -> Result<Option<DictionaryPage>> {
match self.dict_encoder.take() {
Some(encoder) => {
if self.num_values != 0 {
return Err(general_err!(
"Must flush data pages before flushing dictionary"
));
}
if let Some(bloom_filter) = &mut self.bloom_filter {
for value in encoder.uniques() {
bloom_filter.insert(value);
}
}
let buf = encoder.write_dict()?;
Ok(Some(DictionaryPage {
buf,
num_values: encoder.num_entries(),
is_sorted: encoder.is_sorted(),
}))
}
_ => Ok(None),
}
}
fn flush_data_page(&mut self) -> Result<DataPageValues<T::T>> {
let (buf, encoding) = match &mut self.dict_encoder {
Some(encoder) => (encoder.write_indices()?, Encoding::RLE_DICTIONARY),
_ => (self.encoder.flush_buffer()?, self.encoder.encoding()),
};
Ok(DataPageValues {
buf,
encoding,
num_values: std::mem::take(&mut self.num_values),
min_value: self.min_value.take(),
max_value: self.max_value.take(),
nan_count: self.nan_count.take(),
variable_length_bytes: self.variable_length_bytes.take(),
})
}
fn flush_geospatial_statistics(&mut self) -> Option<Box<GeospatialStatistics>> {
self.geo_stats_accumulator.as_mut().map(|a| a.finish())?
}
}
pub(crate) fn create_bloom_filter(
column_props: &ResolvedColumnProperties,
) -> Result<(Option<Sbbf>, f64)> {
match column_props.bloom_filter_properties.as_ref() {
Some(bf_props) => Ok((
Some(Sbbf::new_with_ndv_fpp(bf_props.ndv(), bf_props.fpp())?),
bf_props.fpp(),
)),
None => Ok((None, 0.0)),
}
}
fn update_geo_stats_accumulator<'a, T, I>(bounder: &mut dyn GeoStatsAccumulator, iter: I)
where
T: ParquetValueType + 'a,
I: Iterator<Item = &'a T>,
{
if bounder.is_valid() {
for val in iter {
bounder.update_wkb(val.as_bytes());
}
}
}
#[inline]
fn plain_encoded_byte_size<T: DataType>(value: &T::T) -> usize {
let (overhead, bytes) = value.dict_encoding_size();
match <T::T as ParquetValueType>::PHYSICAL_TYPE {
Type::BYTE_ARRAY => overhead + bytes,
Type::FIXED_LEN_BYTE_ARRAY => bytes,
_ => overhead,
}
}
#[inline]
fn count_within_budget<'a, T: DataType>(
n: usize,
byte_budget: usize,
vals: impl Iterator<Item = Option<&'a T::T>>,
) -> Option<usize>
where
T::T: 'a,
{
let phys = <T::T as ParquetValueType>::PHYSICAL_TYPE;
if phys != Type::BYTE_ARRAY && phys != Type::FIXED_LEN_BYTE_ARRAY {
let per = std::mem::size_of::<T::T>().max(1);
return Some((byte_budget / per).max(1).min(n));
}
let mut cum: usize = 0;
for (i, v) in vals.enumerate() {
if let Some(v) = v {
cum = cum.saturating_add(plain_encoded_byte_size::<T>(v));
}
if cum > byte_budget {
return Some(i + 1);
}
}
Some(n)
}