use std::collections::HashSet;
use std::path::{Path, PathBuf};
use arrow::ipc::reader::FileReader;
use arrow::ipc::writer::FileWriter;
use arrow::record_batch::RecordBatch;
pub struct ArrowShadow {
dir: PathBuf,
}
impl ArrowShadow {
pub fn new(table_dir: &Path) -> Self {
let dir = table_dir.join("_shadow");
let _ = std::fs::create_dir_all(&dir);
Self { dir }
}
fn shadow_path(&self, run_id: u128) -> PathBuf {
self.dir.join(format!("r-{run_id}.arrow"))
}
pub fn try_read(&self, run_id: u128) -> Option<RecordBatch> {
let path = self.shadow_path(run_id);
let file = std::fs::File::open(&path).ok()?;
let reader = FileReader::try_new(file, None).ok()?;
let batches: Vec<RecordBatch> = reader
.into_iter()
.collect::<arrow::error::Result<Vec<_>>>()
.ok()?;
if batches.is_empty() {
return None;
}
if batches.len() == 1 {
return Some(batches.into_iter().next().unwrap());
}
let schema = batches[0].schema();
arrow::compute::concat_batches(&schema, &batches).ok()
}
pub fn write(&self, run_id: u128, batch: &RecordBatch) {
let path = self.shadow_path(run_id);
let tmp = path.with_extension("tmp");
let write = || -> std::io::Result<()> {
let file = std::fs::File::create(&tmp)?;
let mut writer = FileWriter::try_new(file, batch.schema().as_ref())
.map_err(|e| std::io::Error::other(e.to_string()))?;
writer
.write(batch)
.map_err(|e| std::io::Error::other(e.to_string()))?;
writer
.finish()
.map_err(|e| std::io::Error::other(e.to_string()))?;
Ok(())
};
if write().is_ok() {
let _ = std::fs::rename(&tmp, &path);
} else {
let _ = std::fs::remove_file(&tmp);
}
}
pub fn sweep(&self, live_run_ids: &HashSet<u128>) {
let Ok(entries) = std::fs::read_dir(&self.dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) == Some("tmp") {
let _ = std::fs::remove_file(&path);
continue;
}
if let Some(id_str) = path
.file_name()
.and_then(|n| n.to_str())
.and_then(|s| s.strip_prefix("r-"))
.and_then(|s| s.strip_suffix(".arrow"))
{
if let Ok(id) = id_str.parse::<u128>() {
if !live_run_ids.contains(&id) {
let _ = std::fs::remove_file(&path);
}
}
}
}
}
}