use std::io::{BufReader, Read as _};
use std::path::{Path, PathBuf};
use super::grace_partitioner::partition_hash_seeded;
use super::spill::SpillPartitionWriter;
use crate::data::io::uring_seq_reader::UringSeqReader;
enum FrameBackend {
Uring(Box<UringSeqReader>),
Std(BufReader<std::fs::File>),
}
pub(in crate::data::executor) struct FrameStreamReader {
backend: FrameBackend,
}
impl FrameStreamReader {
pub(in crate::data::executor) fn open(path: &Path) -> crate::Result<Self> {
let backend = match UringSeqReader::open_default(path) {
Some(r) => FrameBackend::Uring(Box::new(r)),
None => FrameBackend::Std(BufReader::new(std::fs::File::open(path).map_err(|e| {
crate::Error::Storage {
engine: "grace-repartition".into(),
detail: format!("spill frame reader open {}: {e}", path.display()),
}
})?)),
};
Ok(Self { backend })
}
fn read_full(backend: &mut FrameBackend, dst: &mut [u8]) -> crate::Result<bool> {
match backend {
FrameBackend::Uring(r) => r.read_exact(dst),
FrameBackend::Std(r) => match r.read_exact(dst) {
Ok(()) => Ok(true),
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => Ok(false),
Err(e) => Err(crate::Error::Io(e)),
},
}
}
pub(in crate::data::executor) fn next_row(&mut self) -> crate::Result<Option<Vec<u8>>> {
let mut len_buf = [0u8; 4];
if !Self::read_full(&mut self.backend, &mut len_buf)? {
return Ok(None);
}
let row_len = u32::from_le_bytes(len_buf) as usize;
let mut body = vec![0u8; row_len];
if !Self::read_full(&mut self.backend, &mut body)? {
return Err(crate::Error::Storage {
engine: "grace-repartition".into(),
detail: "spill partition truncated: frame body shorter than declared length".into(),
});
}
Ok(Some(body))
}
}
pub(super) enum PartitionSource {
InMemory(Vec<(String, Vec<u8>)>),
Spilled(PathBuf),
}
impl PartitionSource {
pub(super) fn size_bytes(&self) -> crate::Result<usize> {
match self {
PartitionSource::InMemory(rows) => Ok(rows.iter().map(|(_, v)| v.len()).sum()),
PartitionSource::Spilled(path) => {
let meta = std::fs::metadata(path).map_err(|e| crate::Error::Storage {
engine: "grace-repartition".into(),
detail: format!("stat spilled partition {}: {e}", path.display()),
})?;
Ok(meta.len() as usize)
}
}
}
pub(super) fn materialize(self) -> crate::Result<Vec<(String, Vec<u8>)>> {
match self {
PartitionSource::InMemory(rows) => Ok(rows),
PartitionSource::Spilled(path) => {
let mut reader = FrameStreamReader::open(&path)?;
let mut out = Vec::new();
while let Some(row) = reader.next_row()? {
out.push((String::new(), row));
}
Ok(out)
}
}
}
}
pub(super) fn repartition_side<S: AsRef<str>>(
src: PartitionSource,
keys: &[S],
seed: u64,
sub_p: usize,
sub_dir: &Path,
side_tag: &str,
) -> crate::Result<Vec<PathBuf>> {
std::fs::create_dir_all(sub_dir).map_err(|e| crate::Error::Storage {
engine: "grace-repartition".into(),
detail: format!(
"failed to create re-partition sub-dir {}: {e}",
sub_dir.display()
),
})?;
let mut writers: Vec<SpillPartitionWriter> = Vec::with_capacity(sub_p);
for sp in 0..sub_p {
let path = sub_dir.join(format!("sp{sp}.{side_tag}.spill"));
let w = SpillPartitionWriter::create(&path).ok_or_else(|| crate::Error::Storage {
engine: "grace-repartition".into(),
detail: format!(
"failed to create sub-partition spill writer {}",
path.display()
),
})?;
writers.push(w);
}
match src {
PartitionSource::InMemory(rows) => {
for (_, value) in rows {
let sp = (partition_hash_seeded(&value, keys, seed) % sub_p as u64) as usize;
let w = writers
.get_mut(sp)
.ok_or_else(|| sub_index_error(sp, sub_p))?;
w.append_row(&value)?;
}
}
PartitionSource::Spilled(path) => {
let mut reader = FrameStreamReader::open(&path)?;
while let Some(value) = reader.next_row()? {
let sp = (partition_hash_seeded(&value, keys, seed) % sub_p as u64) as usize;
let w = writers
.get_mut(sp)
.ok_or_else(|| sub_index_error(sp, sub_p))?;
w.append_row(&value)?;
}
}
}
let mut paths = Vec::with_capacity(sub_p);
for w in writers {
paths.push(w.finish()?);
}
Ok(paths)
}
fn sub_index_error(sp: usize, sub_p: usize) -> crate::Error {
crate::Error::Storage {
engine: "grace-repartition".into(),
detail: format!("sub-partition index {sp} out of range (sub_p={sub_p})"),
}
}