blob: d27c266b768ad5e3a4a3e31eb0f6be37e4a8aacc [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use crate::cache::cache_manager::{
FileMetadata, FileMetadataCache, FileMetadataCacheEntry,
};
use crate::cache::lru_queue::LruQueue;
use crate::cache::CacheAccessor;
use datafusion_common::Statistics;
use dashmap::DashMap;
use object_store::path::Path;
use object_store::ObjectMeta;
/// Default implementation of [`FileStatisticsCache`]
///
/// Stores collected statistics for files
///
/// Cache is invalided when file size or last modification has changed
///
/// [`FileStatisticsCache`]: crate::cache::cache_manager::FileStatisticsCache
#[derive(Default)]
pub struct DefaultFileStatisticsCache {
statistics: DashMap<Path, (ObjectMeta, Arc<Statistics>)>,
}
impl CacheAccessor<Path, Arc<Statistics>> for DefaultFileStatisticsCache {
type Extra = ObjectMeta;
/// Get `Statistics` for file location.
fn get(&self, k: &Path) -> Option<Arc<Statistics>> {
self.statistics
.get(k)
.map(|s| Some(Arc::clone(&s.value().1)))
.unwrap_or(None)
}
/// Get `Statistics` for file location. Returns None if file has changed or not found.
fn get_with_extra(&self, k: &Path, e: &Self::Extra) -> Option<Arc<Statistics>> {
self.statistics
.get(k)
.map(|s| {
let (saved_meta, statistics) = s.value();
if saved_meta.size != e.size
|| saved_meta.last_modified != e.last_modified
{
// file has changed
None
} else {
Some(Arc::clone(statistics))
}
})
.unwrap_or(None)
}
/// Save collected file statistics
fn put(&self, _key: &Path, _value: Arc<Statistics>) -> Option<Arc<Statistics>> {
panic!("Put cache in DefaultFileStatisticsCache without Extra not supported.")
}
fn put_with_extra(
&self,
key: &Path,
value: Arc<Statistics>,
e: &Self::Extra,
) -> Option<Arc<Statistics>> {
self.statistics
.insert(key.clone(), (e.clone(), value))
.map(|x| x.1)
}
fn remove(&mut self, k: &Path) -> Option<Arc<Statistics>> {
self.statistics.remove(k).map(|x| x.1 .1)
}
fn contains_key(&self, k: &Path) -> bool {
self.statistics.contains_key(k)
}
fn len(&self) -> usize {
self.statistics.len()
}
fn clear(&self) {
self.statistics.clear()
}
fn name(&self) -> String {
"DefaultFileStatisticsCache".to_string()
}
}
/// Default implementation of [`ListFilesCache`]
///
/// Collected files metadata for listing files.
///
/// Cache is not invalided until user calls [`Self::remove`] or [`Self::clear`].
///
/// [`ListFilesCache`]: crate::cache::cache_manager::ListFilesCache
#[derive(Default)]
pub struct DefaultListFilesCache {
statistics: DashMap<Path, Arc<Vec<ObjectMeta>>>,
}
impl CacheAccessor<Path, Arc<Vec<ObjectMeta>>> for DefaultListFilesCache {
type Extra = ObjectMeta;
fn get(&self, k: &Path) -> Option<Arc<Vec<ObjectMeta>>> {
self.statistics.get(k).map(|x| Arc::clone(x.value()))
}
fn get_with_extra(
&self,
_k: &Path,
_e: &Self::Extra,
) -> Option<Arc<Vec<ObjectMeta>>> {
panic!("Not supported DefaultListFilesCache get_with_extra")
}
fn put(
&self,
key: &Path,
value: Arc<Vec<ObjectMeta>>,
) -> Option<Arc<Vec<ObjectMeta>>> {
self.statistics.insert(key.clone(), value)
}
fn put_with_extra(
&self,
_key: &Path,
_value: Arc<Vec<ObjectMeta>>,
_e: &Self::Extra,
) -> Option<Arc<Vec<ObjectMeta>>> {
panic!("Not supported DefaultListFilesCache put_with_extra")
}
fn remove(&mut self, k: &Path) -> Option<Arc<Vec<ObjectMeta>>> {
self.statistics.remove(k).map(|x| x.1)
}
fn contains_key(&self, k: &Path) -> bool {
self.statistics.contains_key(k)
}
fn len(&self) -> usize {
self.statistics.len()
}
fn clear(&self) {
self.statistics.clear()
}
fn name(&self) -> String {
"DefaultListFilesCache".to_string()
}
}
/// Handles the inner state of the [`DefaultFilesMetadataCache`] struct.
struct DefaultFilesMetadataCacheState {
lru_queue: LruQueue<Path, (ObjectMeta, Arc<dyn FileMetadata>)>,
memory_limit: usize,
memory_used: usize,
cache_hits: HashMap<Path, usize>,
}
impl DefaultFilesMetadataCacheState {
fn new(memory_limit: usize) -> Self {
Self {
lru_queue: LruQueue::new(),
memory_limit,
memory_used: 0,
cache_hits: HashMap::new(),
}
}
/// Returns the respective entry from the cache, if it exists and the `size` and `last_modified`
/// properties from [`ObjectMeta`] match.
/// If the entry exists, it becomes the most recently used.
fn get(&mut self, k: &ObjectMeta) -> Option<Arc<dyn FileMetadata>> {
self.lru_queue
.get(&k.location)
.map(|(object_meta, metadata)| {
if object_meta.size != k.size
|| object_meta.last_modified != k.last_modified
{
None
} else {
*self.cache_hits.entry(k.location.clone()).or_insert(0) += 1;
Some(Arc::clone(metadata))
}
})
.unwrap_or(None)
}
/// Checks if the metadata is currently cached (entry exists and the `size` and `last_modified`
/// properties of [`ObjectMeta`] match).
/// The LRU queue is not updated.
fn contains_key(&self, k: &ObjectMeta) -> bool {
self.lru_queue
.peek(&k.location)
.map(|(object_meta, _)| {
object_meta.size == k.size && object_meta.last_modified == k.last_modified
})
.unwrap_or(false)
}
/// Adds a new key-value pair to cache, meaning LRU entries might be evicted if required.
/// If the key is already in the cache, the previous metadata is returned.
/// If the size of the metadata is greater than the `memory_limit`, the value is not inserted.
fn put(
&mut self,
key: ObjectMeta,
value: Arc<dyn FileMetadata>,
) -> Option<Arc<dyn FileMetadata>> {
let value_size = value.memory_size();
// no point in trying to add this value to the cache if it cannot fit entirely
if value_size > self.memory_limit {
return None;
}
self.cache_hits.insert(key.location.clone(), 0);
// if the key is already in the cache, the old value is removed
let old_value = self.lru_queue.put(key.location.clone(), (key, value));
self.memory_used += value_size;
if let Some((_, ref old_metadata)) = old_value {
self.memory_used -= old_metadata.memory_size();
}
self.evict_entries();
old_value.map(|v| v.1)
}
/// Evicts entries from the LRU cache until `memory_used` is lower than `memory_limit`.
fn evict_entries(&mut self) {
while self.memory_used > self.memory_limit {
if let Some(removed) = self.lru_queue.pop() {
let metadata: Arc<dyn FileMetadata> = removed.1 .1;
self.memory_used -= metadata.memory_size();
} else {
// cache is empty while memory_used > memory_limit, cannot happen
debug_assert!(
false,
"cache is empty while memory_used > memory_limit, cannot happen"
);
return;
}
}
}
/// Removes an entry from the cache and returns it, if it exists.
fn remove(&mut self, k: &ObjectMeta) -> Option<Arc<dyn FileMetadata>> {
if let Some((_, old_metadata)) = self.lru_queue.remove(&k.location) {
self.memory_used -= old_metadata.memory_size();
self.cache_hits.remove(&k.location);
Some(old_metadata)
} else {
None
}
}
/// Returns the number of entries currently cached.
fn len(&self) -> usize {
self.lru_queue.len()
}
/// Removes all entries from the cache.
fn clear(&mut self) {
self.lru_queue.clear();
self.memory_used = 0;
self.cache_hits.clear();
}
}
/// Default implementation of [`FileMetadataCache`]
///
/// Collected file embedded metadata cache.
///
/// The metadata for each file is invalidated when the file size or last
/// modification time have been changed.
///
/// # Internal details
///
/// The `memory_limit` controls the maximum size of the cache, which uses a
/// Least Recently Used eviction algorithm. When adding a new entry, if the total
/// size of the cached entries exceeds `memory_limit`, the least recently used entries
/// are evicted until the total size is lower than `memory_limit`.
///
/// # `Extra` Handling
///
/// Users should use the [`Self::get`] and [`Self::put`] methods. The
/// [`Self::get_with_extra`] and [`Self::put_with_extra`] methods simply call
/// `get` and `put`, respectively.
pub struct DefaultFilesMetadataCache {
// the state is wrapped in a Mutex to ensure the operations are atomic
state: Mutex<DefaultFilesMetadataCacheState>,
}
impl DefaultFilesMetadataCache {
/// Create a new instance of [`DefaultFilesMetadataCache`].
///
/// # Arguments
/// `memory_limit`: the maximum size of the cache, in bytes
//
pub fn new(memory_limit: usize) -> Self {
Self {
state: Mutex::new(DefaultFilesMetadataCacheState::new(memory_limit)),
}
}
/// Returns the size of the cached memory, in bytes.
pub fn memory_used(&self) -> usize {
let state = self.state.lock().unwrap();
state.memory_used
}
}
impl FileMetadataCache for DefaultFilesMetadataCache {
fn cache_limit(&self) -> usize {
let state = self.state.lock().unwrap();
state.memory_limit
}
fn update_cache_limit(&self, limit: usize) {
let mut state = self.state.lock().unwrap();
state.memory_limit = limit;
state.evict_entries();
}
fn list_entries(&self) -> HashMap<Path, FileMetadataCacheEntry> {
let state = self.state.lock().unwrap();
let mut entries = HashMap::<Path, FileMetadataCacheEntry>::new();
for (path, (object_meta, metadata)) in state.lru_queue.list_entries() {
entries.insert(
path.clone(),
FileMetadataCacheEntry {
object_meta: object_meta.clone(),
size_bytes: metadata.memory_size(),
hits: *state.cache_hits.get(path).expect("entry must exist"),
extra: metadata.extra_info(),
},
);
}
entries
}
}
impl CacheAccessor<ObjectMeta, Arc<dyn FileMetadata>> for DefaultFilesMetadataCache {
type Extra = ObjectMeta;
fn get(&self, k: &ObjectMeta) -> Option<Arc<dyn FileMetadata>> {
let mut state = self.state.lock().unwrap();
state.get(k)
}
fn get_with_extra(
&self,
k: &ObjectMeta,
_e: &Self::Extra,
) -> Option<Arc<dyn FileMetadata>> {
self.get(k)
}
fn put(
&self,
key: &ObjectMeta,
value: Arc<dyn FileMetadata>,
) -> Option<Arc<dyn FileMetadata>> {
let mut state = self.state.lock().unwrap();
state.put(key.clone(), value)
}
fn put_with_extra(
&self,
key: &ObjectMeta,
value: Arc<dyn FileMetadata>,
_e: &Self::Extra,
) -> Option<Arc<dyn FileMetadata>> {
self.put(key, value)
}
fn remove(&mut self, k: &ObjectMeta) -> Option<Arc<dyn FileMetadata>> {
let mut state = self.state.lock().unwrap();
state.remove(k)
}
fn contains_key(&self, k: &ObjectMeta) -> bool {
let state = self.state.lock().unwrap();
state.contains_key(k)
}
fn len(&self) -> usize {
let state = self.state.lock().unwrap();
state.len()
}
fn clear(&self) {
let mut state = self.state.lock().unwrap();
state.clear();
}
fn name(&self) -> String {
"DefaultFilesMetadataCache".to_string()
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use std::sync::Arc;
use crate::cache::cache_manager::{
FileMetadata, FileMetadataCache, FileMetadataCacheEntry,
};
use crate::cache::cache_unit::{
DefaultFileStatisticsCache, DefaultFilesMetadataCache, DefaultListFilesCache,
};
use crate::cache::CacheAccessor;
use arrow::datatypes::{DataType, Field, Schema, TimeUnit};
use chrono::DateTime;
use datafusion_common::Statistics;
use object_store::path::Path;
use object_store::ObjectMeta;
#[test]
fn test_statistics_cache() {
let meta = ObjectMeta {
location: Path::from("test"),
last_modified: DateTime::parse_from_rfc3339("2022-09-27T22:36:00+02:00")
.unwrap()
.into(),
size: 1024,
e_tag: None,
version: None,
};
let cache = DefaultFileStatisticsCache::default();
assert!(cache.get_with_extra(&meta.location, &meta).is_none());
cache.put_with_extra(
&meta.location,
Statistics::new_unknown(&Schema::new(vec![Field::new(
"test_column",
DataType::Timestamp(TimeUnit::Second, None),
false,
)]))
.into(),
&meta,
);
assert!(cache.get_with_extra(&meta.location, &meta).is_some());
// file size changed
let mut meta2 = meta.clone();
meta2.size = 2048;
assert!(cache.get_with_extra(&meta2.location, &meta2).is_none());
// file last_modified changed
let mut meta2 = meta.clone();
meta2.last_modified = DateTime::parse_from_rfc3339("2022-09-27T22:40:00+02:00")
.unwrap()
.into();
assert!(cache.get_with_extra(&meta2.location, &meta2).is_none());
// different file
let mut meta2 = meta;
meta2.location = Path::from("test2");
assert!(cache.get_with_extra(&meta2.location, &meta2).is_none());
}
#[test]
fn test_list_file_cache() {
let meta = ObjectMeta {
location: Path::from("test"),
last_modified: DateTime::parse_from_rfc3339("2022-09-27T22:36:00+02:00")
.unwrap()
.into(),
size: 1024,
e_tag: None,
version: None,
};
let cache = DefaultListFilesCache::default();
assert!(cache.get(&meta.location).is_none());
cache.put(&meta.location, vec![meta.clone()].into());
assert_eq!(
cache.get(&meta.location).unwrap().first().unwrap().clone(),
meta.clone()
);
}
pub struct TestFileMetadata {
metadata: String,
}
impl FileMetadata for TestFileMetadata {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn memory_size(&self) -> usize {
self.metadata.len()
}
fn extra_info(&self) -> HashMap<String, String> {
HashMap::from([("extra_info".to_owned(), "abc".to_owned())])
}
}
#[test]
fn test_default_file_metadata_cache() {
let object_meta = ObjectMeta {
location: Path::from("test"),
last_modified: DateTime::parse_from_rfc3339("2025-07-29T12:12:12+00:00")
.unwrap()
.into(),
size: 1024,
e_tag: None,
version: None,
};
let metadata: Arc<dyn FileMetadata> = Arc::new(TestFileMetadata {
metadata: "retrieved_metadata".to_owned(),
});
let mut cache = DefaultFilesMetadataCache::new(1024 * 1024);
assert!(cache.get(&object_meta).is_none());
// put
cache.put(&object_meta, Arc::clone(&metadata));
// get and contains of a valid entry
assert!(cache.contains_key(&object_meta));
let value = cache.get(&object_meta);
assert!(value.is_some());
let test_file_metadata = Arc::downcast::<TestFileMetadata>(value.unwrap());
assert!(test_file_metadata.is_ok());
assert_eq!(test_file_metadata.unwrap().metadata, "retrieved_metadata");
// file size changed
let mut object_meta2 = object_meta.clone();
object_meta2.size = 2048;
assert!(cache.get(&object_meta2).is_none());
assert!(!cache.contains_key(&object_meta2));
// file last_modified changed
let mut object_meta2 = object_meta.clone();
object_meta2.last_modified =
DateTime::parse_from_rfc3339("2025-07-29T13:13:13+00:00")
.unwrap()
.into();
assert!(cache.get(&object_meta2).is_none());
assert!(!cache.contains_key(&object_meta2));
// different file
let mut object_meta2 = object_meta.clone();
object_meta2.location = Path::from("test2");
assert!(cache.get(&object_meta2).is_none());
assert!(!cache.contains_key(&object_meta2));
// remove
cache.remove(&object_meta);
assert!(cache.get(&object_meta).is_none());
assert!(!cache.contains_key(&object_meta));
// len and clear
cache.put(&object_meta, Arc::clone(&metadata));
cache.put(&object_meta2, metadata);
assert_eq!(cache.len(), 2);
cache.clear();
assert_eq!(cache.len(), 0);
}
fn generate_test_metadata_with_size(
path: &str,
size: usize,
) -> (ObjectMeta, Arc<dyn FileMetadata>) {
let object_meta = ObjectMeta {
location: Path::from(path),
last_modified: chrono::Utc::now(),
size: size as u64,
e_tag: None,
version: None,
};
let metadata: Arc<dyn FileMetadata> = Arc::new(TestFileMetadata {
metadata: "a".repeat(size),
});
(object_meta, metadata)
}
#[test]
fn test_default_file_metadata_cache_with_limit() {
let mut cache = DefaultFilesMetadataCache::new(1000);
let (object_meta1, metadata1) = generate_test_metadata_with_size("1", 100);
let (object_meta2, metadata2) = generate_test_metadata_with_size("2", 500);
let (object_meta3, metadata3) = generate_test_metadata_with_size("3", 300);
cache.put(&object_meta1, metadata1);
cache.put(&object_meta2, metadata2);
cache.put(&object_meta3, metadata3);
// all entries will fit
assert_eq!(cache.len(), 3);
assert_eq!(cache.memory_used(), 900);
assert!(cache.contains_key(&object_meta1));
assert!(cache.contains_key(&object_meta2));
assert!(cache.contains_key(&object_meta3));
// add a new entry which will remove the least recently used ("1")
let (object_meta4, metadata4) = generate_test_metadata_with_size("4", 200);
cache.put(&object_meta4, metadata4);
assert_eq!(cache.len(), 3);
assert_eq!(cache.memory_used(), 1000);
assert!(!cache.contains_key(&object_meta1));
assert!(cache.contains_key(&object_meta4));
// get entry "2", which will move it to the top of the queue, and add a new one which will
// remove the new least recently used ("3")
cache.get(&object_meta2);
let (object_meta5, metadata5) = generate_test_metadata_with_size("5", 100);
cache.put(&object_meta5, metadata5);
assert_eq!(cache.len(), 3);
assert_eq!(cache.memory_used(), 800);
assert!(!cache.contains_key(&object_meta3));
assert!(cache.contains_key(&object_meta5));
// new entry which will not be able to fit in the 1000 bytes allocated
let (object_meta6, metadata6) = generate_test_metadata_with_size("6", 1200);
cache.put(&object_meta6, metadata6);
assert_eq!(cache.len(), 3);
assert_eq!(cache.memory_used(), 800);
assert!(!cache.contains_key(&object_meta6));
// new entry which is able to fit without removing any entry
let (object_meta7, metadata7) = generate_test_metadata_with_size("7", 200);
cache.put(&object_meta7, metadata7);
assert_eq!(cache.len(), 4);
assert_eq!(cache.memory_used(), 1000);
assert!(cache.contains_key(&object_meta7));
// new entry which will remove all other entries
let (object_meta8, metadata8) = generate_test_metadata_with_size("8", 999);
cache.put(&object_meta8, metadata8);
assert_eq!(cache.len(), 1);
assert_eq!(cache.memory_used(), 999);
assert!(cache.contains_key(&object_meta8));
// when updating an entry, the previous ones are not unnecessarily removed
let (object_meta9, metadata9) = generate_test_metadata_with_size("9", 300);
let (object_meta10, metadata10) = generate_test_metadata_with_size("10", 200);
let (object_meta11_v1, metadata11_v1) =
generate_test_metadata_with_size("11", 400);
cache.put(&object_meta9, metadata9);
cache.put(&object_meta10, metadata10);
cache.put(&object_meta11_v1, metadata11_v1);
assert_eq!(cache.memory_used(), 900);
assert_eq!(cache.len(), 3);
let (object_meta11_v2, metadata11_v2) =
generate_test_metadata_with_size("11", 500);
cache.put(&object_meta11_v2, metadata11_v2);
assert_eq!(cache.memory_used(), 1000);
assert_eq!(cache.len(), 3);
assert!(cache.contains_key(&object_meta9));
assert!(cache.contains_key(&object_meta10));
assert!(cache.contains_key(&object_meta11_v2));
assert!(!cache.contains_key(&object_meta11_v1));
// when updating an entry that now exceeds the limit, the LRU ("9") needs to be removed
let (object_meta11_v3, metadata11_v3) =
generate_test_metadata_with_size("11", 501);
cache.put(&object_meta11_v3, metadata11_v3);
assert_eq!(cache.memory_used(), 701);
assert_eq!(cache.len(), 2);
assert!(cache.contains_key(&object_meta10));
assert!(cache.contains_key(&object_meta11_v3));
assert!(!cache.contains_key(&object_meta11_v2));
// manually removing an entry that is not the LRU
cache.remove(&object_meta11_v3);
assert_eq!(cache.len(), 1);
assert_eq!(cache.memory_used(), 200);
assert!(cache.contains_key(&object_meta10));
assert!(!cache.contains_key(&object_meta11_v3));
// clear
cache.clear();
assert_eq!(cache.len(), 0);
assert_eq!(cache.memory_used(), 0);
// resizing the cache should clear the extra entries
let (object_meta12, metadata12) = generate_test_metadata_with_size("12", 300);
let (object_meta13, metadata13) = generate_test_metadata_with_size("13", 200);
let (object_meta14, metadata14) = generate_test_metadata_with_size("14", 500);
cache.put(&object_meta12, metadata12);
cache.put(&object_meta13, metadata13);
cache.put(&object_meta14, metadata14);
assert_eq!(cache.len(), 3);
assert_eq!(cache.memory_used(), 1000);
cache.update_cache_limit(600);
assert_eq!(cache.len(), 1);
assert_eq!(cache.memory_used(), 500);
assert!(!cache.contains_key(&object_meta12));
assert!(!cache.contains_key(&object_meta13));
assert!(cache.contains_key(&object_meta14));
}
#[test]
fn test_default_file_metadata_cache_entries_info() {
let mut cache = DefaultFilesMetadataCache::new(1000);
let (object_meta1, metadata1) = generate_test_metadata_with_size("1", 100);
let (object_meta2, metadata2) = generate_test_metadata_with_size("2", 200);
let (object_meta3, metadata3) = generate_test_metadata_with_size("3", 300);
// initial entries, all will have hits = 0
cache.put(&object_meta1, metadata1);
cache.put(&object_meta2, metadata2);
cache.put(&object_meta3, metadata3);
assert_eq!(
cache.list_entries(),
HashMap::from([
(
Path::from("1"),
FileMetadataCacheEntry {
object_meta: object_meta1.clone(),
size_bytes: 100,
hits: 0,
extra: HashMap::from([(
"extra_info".to_owned(),
"abc".to_owned()
)]),
}
),
(
Path::from("2"),
FileMetadataCacheEntry {
object_meta: object_meta2.clone(),
size_bytes: 200,
hits: 0,
extra: HashMap::from([(
"extra_info".to_owned(),
"abc".to_owned()
)]),
}
),
(
Path::from("3"),
FileMetadataCacheEntry {
object_meta: object_meta3.clone(),
size_bytes: 300,
hits: 0,
extra: HashMap::from([(
"extra_info".to_owned(),
"abc".to_owned()
)]),
}
)
])
);
// new hit on "1"
cache.get(&object_meta1);
assert_eq!(
cache.list_entries(),
HashMap::from([
(
Path::from("1"),
FileMetadataCacheEntry {
object_meta: object_meta1.clone(),
size_bytes: 100,
hits: 1,
extra: HashMap::from([(
"extra_info".to_owned(),
"abc".to_owned()
)]),
}
),
(
Path::from("2"),
FileMetadataCacheEntry {
object_meta: object_meta2.clone(),
size_bytes: 200,
hits: 0,
extra: HashMap::from([(
"extra_info".to_owned(),
"abc".to_owned()
)]),
}
),
(
Path::from("3"),
FileMetadataCacheEntry {
object_meta: object_meta3.clone(),
size_bytes: 300,
hits: 0,
extra: HashMap::from([(
"extra_info".to_owned(),
"abc".to_owned()
)]),
}
)
])
);
// new entry, will evict "2"
let (object_meta4, metadata4) = generate_test_metadata_with_size("4", 600);
cache.put(&object_meta4, metadata4);
assert_eq!(
cache.list_entries(),
HashMap::from([
(
Path::from("1"),
FileMetadataCacheEntry {
object_meta: object_meta1.clone(),
size_bytes: 100,
hits: 1,
extra: HashMap::from([(
"extra_info".to_owned(),
"abc".to_owned()
)]),
}
),
(
Path::from("3"),
FileMetadataCacheEntry {
object_meta: object_meta3.clone(),
size_bytes: 300,
hits: 0,
extra: HashMap::from([(
"extra_info".to_owned(),
"abc".to_owned()
)]),
}
),
(
Path::from("4"),
FileMetadataCacheEntry {
object_meta: object_meta4.clone(),
size_bytes: 600,
hits: 0,
extra: HashMap::from([(
"extra_info".to_owned(),
"abc".to_owned()
)]),
}
)
])
);
// replace entry "1"
let (object_meta1_new, metadata1_new) = generate_test_metadata_with_size("1", 50);
cache.put(&object_meta1_new, metadata1_new);
assert_eq!(
cache.list_entries(),
HashMap::from([
(
Path::from("1"),
FileMetadataCacheEntry {
object_meta: object_meta1_new.clone(),
size_bytes: 50,
hits: 0,
extra: HashMap::from([(
"extra_info".to_owned(),
"abc".to_owned()
)]),
}
),
(
Path::from("3"),
FileMetadataCacheEntry {
object_meta: object_meta3.clone(),
size_bytes: 300,
hits: 0,
extra: HashMap::from([(
"extra_info".to_owned(),
"abc".to_owned()
)]),
}
),
(
Path::from("4"),
FileMetadataCacheEntry {
object_meta: object_meta4.clone(),
size_bytes: 600,
hits: 0,
extra: HashMap::from([(
"extra_info".to_owned(),
"abc".to_owned()
)]),
}
)
])
);
// remove entry "4"
cache.remove(&object_meta4);
assert_eq!(
cache.list_entries(),
HashMap::from([
(
Path::from("1"),
FileMetadataCacheEntry {
object_meta: object_meta1_new.clone(),
size_bytes: 50,
hits: 0,
extra: HashMap::from([(
"extra_info".to_owned(),
"abc".to_owned()
)]),
}
),
(
Path::from("3"),
FileMetadataCacheEntry {
object_meta: object_meta3.clone(),
size_bytes: 300,
hits: 0,
extra: HashMap::from([(
"extra_info".to_owned(),
"abc".to_owned()
)]),
}
)
])
);
// clear
cache.clear();
assert_eq!(cache.list_entries(), HashMap::from([]));
}
}