pub mod cache_manager;
pub mod lru_queue;
pub mod default_cache;
use datafusion_common::arrow::datatypes::{DataType, Schema};
use datafusion_common::heap_size::{DFHeapSize, DFHeapSizeCtx};
use datafusion_common::instant::Instant;
use datafusion_common::{HashMap, TableReference};
use object_store::path::Path;
use std::collections::hash_map::DefaultHasher;
use std::fmt::{Debug, Display, Formatter};
use std::hash::{Hash, Hasher};
use std::time::Duration;
pub trait Cache<K: CacheKey, V: CacheValue>: Send + Sync {
fn get(&self, key: &K) -> Option<V>;
fn put(&self, key: &K, value: V) -> Option<V>;
fn remove(&self, k: &K) -> Option<V>;
fn contains_key(&self, k: &K) -> bool;
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
fn clear(&self);
fn name(&self) -> String;
fn cache_limit(&self) -> usize;
fn update_cache_limit(&self, limit: usize);
fn cache_ttl(&self) -> Option<Duration>;
fn update_cache_ttl(&self, _ttl: Option<Duration>);
fn drop_table_entries(
&self,
table_ref: &TableReference,
) -> datafusion_common::Result<()>;
fn list_entries(&self) -> HashMap<K, CacheEntryInfo<V>>;
}
pub trait CacheKey: Clone + Eq + Hash + Send + Sync + Debug {
fn size(&self) -> usize;
fn table_ref(&self) -> Option<&TableReference>;
}
pub trait CacheValue: Clone + Send + Sync {
fn size(&self) -> usize;
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CacheEntryInfo<V> {
pub value: V,
pub size_bytes: usize,
pub hits: usize,
pub expires: Option<Instant>,
}
impl<K: CacheKey, V: CacheValue> Debug for dyn Cache<K, V> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Cache name: {} with length: {}", self.name(), self.len())
}
}
impl CacheKey for Path {
fn size(&self) -> usize {
self.as_ref().heap_size(&mut DFHeapSizeCtx::default())
}
fn table_ref(&self) -> Option<&TableReference> {
None
}
}
impl CacheKey for TableScopedPath {
fn size(&self) -> usize {
DFHeapSize::heap_size(self, &mut DFHeapSizeCtx::default())
}
fn table_ref(&self) -> Option<&TableReference> {
self.table.as_ref()
}
}
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct TableScopedPath {
pub table: Option<TableReference>,
pub path: Path,
}
impl DFHeapSize for TableScopedPath {
fn heap_size(&self, ctx: &mut DFHeapSizeCtx) -> usize {
self.path.as_ref().heap_size(ctx) + self.table.heap_size(ctx)
}
}
impl Display for TableScopedPath {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if let Some(table) = &self.table {
write!(f, "{}, {}", self.path, table)
} else {
write!(f, "{}", self.path)
}
}
}
#[derive(Clone, Debug)]
pub struct SchemaFingerprint {
columns: Vec<(String, DataType, bool)>,
hash: u64,
}
impl SchemaFingerprint {
pub fn from_schema(file_schema: &Schema) -> Self {
let columns: Vec<(String, DataType, bool)> = file_schema
.fields()
.iter()
.map(|f| (f.name().clone(), f.data_type().clone(), f.is_nullable()))
.collect();
let mut hasher = DefaultHasher::new();
columns.hash(&mut hasher);
Self {
columns,
hash: hasher.finish(),
}
}
}
impl PartialEq for SchemaFingerprint {
fn eq(&self, other: &Self) -> bool {
self.hash == other.hash && self.columns == other.columns
}
}
impl Eq for SchemaFingerprint {}
impl Hash for SchemaFingerprint {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u64(self.hash);
}
}
impl DFHeapSize for SchemaFingerprint {
fn heap_size(&self, ctx: &mut DFHeapSizeCtx) -> usize {
self.columns.heap_size(ctx)
}
}
#[cfg(test)]
mod schema_fingerprint_tests {
use super::*;
use datafusion_common::arrow::datatypes::Field;
fn fp(fields: Vec<Field>) -> SchemaFingerprint {
SchemaFingerprint::from_schema(&Schema::new(fields))
}
#[test]
fn fingerprint_captures_nullability_and_order() {
assert_ne!(
fp(vec![Field::new("id", DataType::Int64, false)]),
fp(vec![Field::new("id", DataType::Int64, true)]),
"nullability must affect the fingerprint",
);
let ab = fp(vec![
Field::new("a", DataType::Int64, false),
Field::new("b", DataType::Utf8, true),
]);
let ba = fp(vec![
Field::new("b", DataType::Utf8, true),
Field::new("a", DataType::Int64, false),
]);
assert_ne!(ab, ba, "field order must affect the fingerprint");
}
#[test]
fn fingerprint_ignores_metadata() {
let plain = fp(vec![Field::new("id", DataType::Int64, false)]);
let field_md = SchemaFingerprint::from_schema(&Schema::new(vec![
Field::new("id", DataType::Int64, false)
.with_metadata([("note".to_string(), "x".to_string())].into()),
]));
assert_eq!(plain, field_md, "field metadata must be ignored");
let schema_md = SchemaFingerprint::from_schema(
&Schema::new(vec![Field::new("id", DataType::Int64, false)])
.with_metadata([("k".to_string(), "v".to_string())].into()),
);
assert_eq!(plain, schema_md, "schema metadata must be ignored");
}
}