use super::super::input::InputRegistry;
use super::super::table::RawCsv;
use crate::datatypes::values::ColumnType;
use indexmap::IndexMap;
use std::collections::HashMap;
#[derive(Default)]
pub(super) struct CsvCache {
inner: std::sync::Mutex<HashMap<String, Result<std::sync::Arc<RawCsv>, String>>>,
}
impl CsvCache {
pub(super) fn get(
&self,
registry: &InputRegistry,
name: &str,
) -> Result<std::sync::Arc<RawCsv>, String> {
{
let guard = self.inner.lock().unwrap();
if let Some(hit) = guard.get(name) {
return hit.clone();
}
}
let result = registry
.get(name)
.and_then(|source| source.read_all())
.map(std::sync::Arc::new);
self.inner
.lock()
.unwrap()
.insert(name.to_string(), result.clone());
result
}
}
pub(super) fn parse_in_parallel(names: &[String], registry: &InputRegistry, cache: &CsvCache) {
use rayon::prelude::*;
names.par_iter().for_each(|name| {
let _ = cache.get(registry, name);
});
}
#[cfg(test)]
mod cache_tests {
use super::super::super::input::csv::CsvFile;
use super::super::super::input::test_double::CountingSource;
use super::*;
use std::path::PathBuf;
use std::sync::atomic::Ordering;
#[test]
fn a_failed_read_is_cached_and_not_retried() {
let (counting, opens) = CountingSource::new(Box::new(CsvFile::new(
PathBuf::from("/nonexistent/definitely-not-here.csv"),
"missing.csv".to_string(),
)));
let mut registry = InputRegistry::default();
registry.insert("missing.csv", Box::new(counting));
let cache = CsvCache::default();
parse_in_parallel(&["missing.csv".to_string()], ®istry, &cache);
let err = cache
.get(®istry, "missing.csv")
.err()
.expect("a file that is not there fails to read");
assert!(err.starts_with("CSV open missing.csv: "), "{err}");
assert_eq!(
opens.load(Ordering::SeqCst),
1,
"the pre-pass read is the only one"
);
}
}
#[derive(Default)]
pub(super) struct IdTypeCache {
inner: std::sync::Mutex<HashMap<String, IndexMap<String, ColumnType>>>,
}
impl IdTypeCache {
pub(super) fn insert(&self, input: &str, types: &IndexMap<String, ColumnType>) {
if types.is_empty() {
return;
}
self.inner
.lock()
.unwrap()
.insert(input.to_string(), types.clone());
}
pub(super) fn get(
&self,
input: &str,
columns: &[String],
) -> Option<IndexMap<String, ColumnType>> {
let guard = self.inner.lock().unwrap();
let known = guard.get(input)?;
columns
.iter()
.map(|c| known.get(c).map(|t| (c.clone(), t.clone())))
.collect()
}
}