use std::collections::HashMap;
use parquet::{basic::Type, file::metadata::ParquetMetaData};
use thrift::{ProtocolErrorKind, new_protocol_error, protocol::TType};
#[cfg(test)]
pub(super) mod tests;
#[derive(Debug, Default)]
pub(super) struct NanCounts(HashMap<(usize, usize), u64>);
impl NanCounts {
pub(super) fn get(&self, row_group: usize, column: usize) -> Option<u64> {
self.0.get(&(row_group, column)).copied()
}
pub(super) fn decode(footer: &[u8], metadata: &ParquetMetaData) -> Self {
if !metadata
.file_metadata()
.schema_descr()
.columns()
.iter()
.any(|column| matches!(column.physical_type(), Type::FLOAT | Type::DOUBLE))
{
return Self::default();
}
Self::read(footer, metadata).unwrap_or_default()
}
fn read(footer: &[u8], metadata: &ParquetMetaData) -> thrift::Result<Self> {
let mut protocol = footer;
let mut counts = HashMap::new();
read_field(&mut protocol, 4, TType::List, |p| {
read_struct_list(p, metadata.num_row_groups(), |p, row_index| {
let row = metadata.row_group(row_index);
read_field(p, 1, TType::List, |p| {
read_struct_list(p, row.num_columns(), |p, column_index| {
let count = read_field(p, 3, TType::Struct, |p| {
Ok(read_field(p, 12, TType::Struct, |p| {
read_field(p, 9, TType::I64, read_i64)
})?
.flatten())
})?
.flatten();
let column = row.column(column_index);
if matches!(column.column_type(), Type::FLOAT | Type::DOUBLE)
&& let Some(count) = count.and_then(|count| u64::try_from(count).ok())
&& let Ok(values) = u64::try_from(column.num_values())
&& count <= values
&& column
.statistics()
.and_then(|s| s.null_count_opt())
.is_none_or(|nulls| nulls <= values && count <= values - nulls)
{
counts.insert((row_index, column_index), count);
}
Ok(())
})
})?;
Ok(())
})
})?;
if !protocol.is_empty() {
return Err(invalid_metadata());
}
Ok(Self(counts))
}
}
type Protocol<'a> = &'a [u8];
fn invalid_metadata() -> thrift::Error {
new_protocol_error(ProtocolErrorKind::InvalidData, "invalid NaN-count metadata")
}
fn read_varint(p: &mut Protocol<'_>) -> thrift::Result<u64> {
let mut value = 0_u64;
for shift in (0..70).step_by(7) {
let byte = read_byte(p)?;
if shift == 63 && byte > 1 {
return Err(invalid_metadata());
}
value |= u64::from(byte & 0x7f) << shift;
if byte & 0x80 == 0 {
return Ok(value);
}
}
Err(invalid_metadata())
}
fn read_i64(p: &mut Protocol<'_>) -> thrift::Result<i64> {
let value = read_varint(p)?;
Ok((value >> 1) as i64 ^ -((value & 1) as i64))
}
fn read_byte(p: &mut Protocol<'_>) -> thrift::Result<u8> {
let (&byte, rest) = p.split_first().ok_or_else(invalid_metadata)?;
*p = rest;
Ok(byte)
}
fn skip_bytes(p: &mut Protocol<'_>, length: usize) -> thrift::Result<()> {
*p = p.get(length..).ok_or_else(invalid_metadata)?;
Ok(())
}
fn read_type(kind: u8) -> thrift::Result<TType> {
Ok(match kind {
0 => TType::Stop,
1 | 2 => TType::Bool,
3 => TType::I08,
4 => TType::I16,
5 => TType::I32,
6 => TType::I64,
7 => TType::Double,
8 => TType::String,
9 => TType::List,
12 => TType::Struct,
_ => return Err(invalid_metadata()),
})
}
fn read_field_header(p: &mut Protocol<'_>, last_id: &mut i16) -> thrift::Result<TType> {
let header = read_byte(p)?;
if header == 0 {
return Ok(TType::Stop);
}
let kind = read_type(header & 0x0f)?;
if kind == TType::Stop {
return Err(invalid_metadata());
}
let delta = i16::from(header >> 4);
*last_id = if delta == 0 {
i16::try_from(read_i64(p)?).map_err(|_| invalid_metadata())?
} else {
last_id.checked_add(delta).ok_or_else(invalid_metadata)?
};
Ok(kind)
}
fn read_field<T>(
p: &mut Protocol<'_>,
id: i16,
field_type: TType,
mut read: impl FnMut(&mut Protocol<'_>) -> thrift::Result<T>,
) -> thrift::Result<Option<T>> {
let mut last_id = 0;
let mut value = None;
loop {
let kind = read_field_header(p, &mut last_id)?;
if kind == TType::Stop {
break;
}
if last_id == id {
if kind != field_type || value.is_some() {
return Err(invalid_metadata());
}
value = Some(read(p)?);
} else if kind != TType::Bool {
skip_value(p, kind, 64)?;
}
}
Ok(value)
}
fn read_list(p: &mut Protocol<'_>) -> thrift::Result<(TType, usize)> {
let header = read_byte(p)?;
if header == 0 {
return Ok((TType::I08, 0));
}
let kind = read_type(header & 0x0f)?;
if kind == TType::Stop {
return Err(invalid_metadata());
}
let size = if header >> 4 == 15 {
i32::try_from(read_varint(p)?).map_err(|_| invalid_metadata())? as usize
} else {
usize::from(header >> 4)
};
if size > p.len() {
return Err(invalid_metadata());
}
if kind == TType::Bool && size != 0 {
return Err(invalid_metadata());
}
Ok((kind, size))
}
fn read_struct_list(
p: &mut Protocol<'_>,
expected: usize,
mut read: impl FnMut(&mut Protocol<'_>, usize) -> thrift::Result<()>,
) -> thrift::Result<()> {
let (kind, size) = read_list(p)?;
if kind != TType::Struct || size != expected {
return Err(invalid_metadata());
}
for index in 0..expected {
read(p, index)?;
}
Ok(())
}
fn skip_value(p: &mut Protocol<'_>, kind: TType, depth: usize) -> thrift::Result<()> {
if depth == 0 {
return Err(invalid_metadata());
}
match kind {
TType::I08 => skip_bytes(p, 1),
TType::I16 => i16::try_from(read_i64(p)?)
.map(|_| ())
.map_err(|_| invalid_metadata()),
TType::I32 => i32::try_from(read_i64(p)?)
.map(|_| ())
.map_err(|_| invalid_metadata()),
TType::I64 => read_i64(p).map(|_| ()),
TType::Double => skip_bytes(p, 8),
TType::String => {
let length = u32::try_from(read_varint(p)?).map_err(|_| invalid_metadata())?;
skip_bytes(p, length as usize)
}
TType::Struct => {
let mut last_id = 0;
loop {
let kind = read_field_header(p, &mut last_id)?;
if kind == TType::Stop {
break;
}
if kind != TType::Bool {
skip_value(p, kind, depth - 1)?;
}
}
Ok(())
}
TType::List => {
let (kind, size) = read_list(p)?;
for _ in 0..size {
skip_value(p, kind, depth - 1)?;
}
Ok(())
}
_ => Err(invalid_metadata()),
}
}