use std::{
collections::{HashMap, HashSet},
future::Future,
};
use datafusion_common::ScalarValue;
use parquet::{
arrow::async_reader::{AsyncFileReader, ParquetRecordBatchStreamBuilder},
bloom_filter::Sbbf,
};
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum DictionaryHintValue {
Utf8(String),
Binary(Vec<u8>),
}
impl DictionaryHintValue {
pub fn from_scalar(value: &ScalarValue) -> Option<Self> {
match value {
ScalarValue::Utf8(Some(v))
| ScalarValue::LargeUtf8(Some(v))
| ScalarValue::Utf8View(Some(v)) => Some(Self::Utf8(v.clone())),
ScalarValue::Binary(Some(v))
| ScalarValue::LargeBinary(Some(v))
| ScalarValue::BinaryView(Some(v))
| ScalarValue::FixedSizeBinary(_, Some(v)) => Some(Self::Binary(v.clone())),
_ => None,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum DictionaryHintEvidence {
Exact(HashSet<DictionaryHintValue>),
Unknown,
}
#[derive(Debug, Clone)]
pub struct CachedDictionaryHintProvider<P> {
inner: P,
cache: HashMap<(usize, usize), DictionaryHintEvidence>,
}
impl<P> CachedDictionaryHintProvider<P> {
pub fn new(inner: P) -> Self {
Self {
inner,
cache: HashMap::new(),
}
}
pub fn with_capacity(inner: P, capacity: usize) -> Self {
Self {
inner,
cache: HashMap::with_capacity(capacity),
}
}
pub fn inner(&self) -> &P {
&self.inner
}
pub fn retarget_inner(&mut self, inner: P) -> P {
self.cache.clear();
std::mem::replace(&mut self.inner, inner)
}
pub fn into_inner(self) -> P {
self.inner
}
pub fn clear_dictionary_cache(&mut self) {
self.cache.clear();
}
pub fn cached_entries(&self) -> usize {
self.cache.len()
}
}
impl<P: AsyncBloomFilterProvider> AsyncBloomFilterProvider for CachedDictionaryHintProvider<P> {
fn bloom_filter(
&mut self,
row_group_idx: usize,
column_idx: usize,
) -> impl Future<Output = Option<Sbbf>> + '_ {
self.inner.bloom_filter(row_group_idx, column_idx)
}
fn bloom_filters_batch<'a>(
&'a mut self,
requests: &'a [(usize, usize)],
) -> impl Future<Output = HashMap<(usize, usize), Sbbf>> + 'a {
self.inner.bloom_filters_batch(requests)
}
fn dictionary_hints(
&mut self,
row_group_idx: usize,
column_idx: usize,
) -> impl Future<Output = DictionaryHintEvidence> + '_ {
async move {
let key = (row_group_idx, column_idx);
if let Some(evidence) = self.cache.get(&key) {
return evidence.clone();
}
let evidence = self.inner.dictionary_hints(row_group_idx, column_idx).await;
if let DictionaryHintEvidence::Exact(_) = &evidence {
self.cache.insert(key, evidence.clone());
}
evidence
}
}
fn dictionary_hints_batch<'a>(
&'a mut self,
requests: &'a [(usize, usize)],
) -> impl Future<Output = HashMap<(usize, usize), DictionaryHintEvidence>> + 'a {
async move {
let mut result = HashMap::with_capacity(requests.len());
let mut missing = Vec::new();
for &request in requests {
if let Some(evidence) = self.cache.get(&request) {
result.insert(request, evidence.clone());
} else {
missing.push(request);
}
}
if !missing.is_empty() {
let loaded = self.inner.dictionary_hints_batch(&missing).await;
for request in missing {
let evidence = loaded
.get(&request)
.cloned()
.unwrap_or(DictionaryHintEvidence::Unknown);
if let DictionaryHintEvidence::Exact(_) = &evidence {
self.cache.insert(request, evidence.clone());
}
result.insert(request, evidence);
}
}
result
}
}
}
pub trait AsyncBloomFilterProvider {
fn bloom_filter(
&mut self,
row_group_idx: usize,
column_idx: usize,
) -> impl Future<Output = Option<Sbbf>> + '_;
fn bloom_filters_batch<'a>(
&'a mut self,
requests: &'a [(usize, usize)],
) -> impl Future<Output = HashMap<(usize, usize), Sbbf>> + 'a {
async move {
let mut result = HashMap::new();
for &(row_group_idx, column_idx) in requests {
if let Some(filter) = self.bloom_filter(row_group_idx, column_idx).await {
result.insert((row_group_idx, column_idx), filter);
}
}
result
}
}
fn dictionary_hints(
&mut self,
_row_group_idx: usize,
_column_idx: usize,
) -> impl Future<Output = DictionaryHintEvidence> + '_ {
async { DictionaryHintEvidence::Unknown }
}
fn dictionary_hints_batch<'a>(
&'a mut self,
requests: &'a [(usize, usize)],
) -> impl Future<Output = HashMap<(usize, usize), DictionaryHintEvidence>> + 'a {
async move {
let mut result = HashMap::new();
for &(row_group_idx, column_idx) in requests {
let hints = self.dictionary_hints(row_group_idx, column_idx).await;
result.insert((row_group_idx, column_idx), hints);
}
result
}
}
}
impl<T: AsyncFileReader + Send + 'static> AsyncBloomFilterProvider
for ParquetRecordBatchStreamBuilder<T>
{
fn bloom_filter(
&mut self,
row_group_idx: usize,
column_idx: usize,
) -> impl Future<Output = Option<Sbbf>> + '_ {
async move {
match self
.get_row_group_column_bloom_filter(row_group_idx, column_idx)
.await
{
Ok(Some(filter)) => Some(filter),
_ => None,
}
}
}
}