use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use minarrow::{Consolidate, SuperTable, Table};
pub trait ChunkedTableReader: Iterator<Item = Result<Table, Self::Error>> + Sized {
type Error;
type Options;
fn open<P: AsRef<Path>>(
dir: P,
base: &str,
options: Self::Options,
) -> Result<Self, Self::Error>;
fn list_paths<P: AsRef<Path>>(dir: P, base: &str) -> Result<Vec<PathBuf>, Self::Error>;
fn paths(&self) -> &[PathBuf];
fn read_chunk(&self, path: &Path) -> Result<Table, Self::Error>;
fn read_chunk_cols(&self, path: &Path, columns: &[&str]) -> Result<Table, Self::Error>;
fn load_batched(self) -> Result<SuperTable, Self::Error> {
let mut batches: Vec<Arc<Table>> = Vec::new();
let mut name: Option<String> = None;
for chunk in self {
let chunk = chunk?;
if name.is_none() {
name = Some(chunk.name.clone());
}
batches.push(Arc::new(chunk));
}
Ok(SuperTable::from_batches(
batches,
name.or(Some("chunked".into())),
))
}
fn load_batched_cols(self, columns: &[&str]) -> Result<SuperTable, Self::Error>;
fn load_table(self) -> Result<Table, Self::Error> {
Ok(self.load_batched()?.consolidate())
}
fn load_table_cols(self, columns: &[&str]) -> Result<Table, Self::Error> {
Ok(self.load_batched_cols(columns)?.consolidate())
}
fn par_load_batched<P: AsRef<Path>>(
dir: P,
base: &str,
options: Self::Options,
threads: Option<usize>,
) -> Result<SuperTable, Self::Error>
where
Self: Sync,
Self::Error: Send,
{
let reader = Self::open(dir, base, options)?;
let paths = reader.paths();
if paths.is_empty() {
return Ok(SuperTable::from_batches(Vec::new(), Some("chunked".into())));
}
let default_threads = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4);
let n_threads = threads.unwrap_or(default_threads).max(1).min(paths.len());
let n_files = paths.len();
let next_index = AtomicUsize::new(0);
let per_worker_cap = n_files.div_ceil(n_threads);
let collected: Result<Vec<(usize, Table)>, Self::Error> = std::thread::scope(|s| {
let mut handles = Vec::with_capacity(n_threads);
for _ in 0..n_threads {
let next_index = &next_index;
let this = &reader;
handles.push(
s.spawn(move || -> Result<Vec<(usize, Table)>, Self::Error> {
let mut local: Vec<(usize, Table)> = Vec::with_capacity(per_worker_cap);
let paths = this.paths();
loop {
let idx = next_index.fetch_add(1, Ordering::Relaxed);
if idx >= n_files {
break;
}
let table = this.read_chunk(&paths[idx])?;
local.push((idx, table));
}
Ok(local)
}),
);
}
let mut all: Vec<(usize, Table)> = Vec::with_capacity(n_files);
for h in handles {
let part = h.join().expect("chunked reader worker panicked")?;
all.extend(part);
}
Ok(all)
});
let mut all = collected?;
all.sort_by_key(|(i, _)| *i);
let batches: Vec<Arc<Table>> = all.into_iter().map(|(_, t)| Arc::new(t)).collect();
Ok(SuperTable::from_batches(batches, Some("chunked".into())))
}
fn par_load_batched_cols<P: AsRef<Path>>(
dir: P,
base: &str,
options: Self::Options,
threads: Option<usize>,
columns: &[&str],
) -> Result<SuperTable, Self::Error>
where
Self: Sync,
Self::Error: Send,
{
let reader = Self::open(dir, base, options)?;
let paths = reader.paths();
if paths.is_empty() {
return Ok(SuperTable::from_batches(Vec::new(), Some("chunked".into())));
}
let default_threads = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4);
let n_threads = threads.unwrap_or(default_threads).max(1).min(paths.len());
let n_files = paths.len();
let next_index = AtomicUsize::new(0);
let per_worker_cap = n_files.div_ceil(n_threads);
let collected: Result<Vec<(usize, Table)>, Self::Error> = std::thread::scope(|s| {
let mut handles = Vec::with_capacity(n_threads);
for _ in 0..n_threads {
let next_index = &next_index;
let this = &reader;
handles.push(
s.spawn(move || -> Result<Vec<(usize, Table)>, Self::Error> {
let mut local: Vec<(usize, Table)> = Vec::with_capacity(per_worker_cap);
let paths = this.paths();
loop {
let idx = next_index.fetch_add(1, Ordering::Relaxed);
if idx >= n_files {
break;
}
let table = this.read_chunk_cols(&paths[idx], columns)?;
local.push((idx, table));
}
Ok(local)
}),
);
}
let mut all: Vec<(usize, Table)> = Vec::with_capacity(n_files);
for h in handles {
let part = h.join().expect("chunked reader worker panicked")?;
all.extend(part);
}
Ok(all)
});
let mut all = collected?;
all.sort_by_key(|(i, _)| *i);
let batches: Vec<Arc<Table>> = all.into_iter().map(|(_, t)| Arc::new(t)).collect();
Ok(SuperTable::from_batches(batches, Some("chunked".into())))
}
}