use crate::TableEntry;
use crate::excel::{IngestOptions, register_workbook};
use crate::register::register_path;
use datafusion::prelude::*;
use futures::future::BoxFuture;
use std::path::PathBuf;
pub trait TableSource: Send + Sync {
fn kind(&self) -> &'static str;
fn register<'a>(
&'a self,
ctx: &'a SessionContext,
opts: &'a IngestOptions,
) -> BoxFuture<'a, Result<Vec<TableEntry>, String>>;
}
pub async fn register_sources(
ctx: &SessionContext,
sources: &[Box<dyn TableSource>],
opts: &IngestOptions,
) -> Result<Vec<TableEntry>, String> {
let mut tables = Vec::new();
for source in sources {
tables.extend(
source
.register(ctx, opts)
.await
.map_err(|e| format!("{} source: {e}", source.kind()))?,
);
}
Ok(tables)
}
#[derive(Debug, Clone, Default)]
pub struct FileSource {
items: Vec<FileItem>,
}
#[derive(Debug, Clone)]
enum FileItem {
Path(PathBuf),
CsvNamed { path: PathBuf, table: String },
Workbook(PathBuf),
}
impl FileSource {
pub fn new() -> Self {
Self::default()
}
pub fn from_paths<I, P>(paths: I) -> Self
where
I: IntoIterator<Item = P>,
P: Into<PathBuf>,
{
Self {
items: paths
.into_iter()
.map(|p| FileItem::Path(p.into()))
.collect(),
}
}
pub fn path(mut self, path: impl Into<PathBuf>) -> Self {
self.items.push(FileItem::Path(path.into()));
self
}
pub fn csv_named(mut self, path: impl Into<PathBuf>, table: impl Into<String>) -> Self {
self.items.push(FileItem::CsvNamed {
path: path.into(),
table: table.into(),
});
self
}
pub fn workbook(mut self, path: impl Into<PathBuf>) -> Self {
self.items.push(FileItem::Workbook(path.into()));
self
}
pub fn len(&self) -> usize {
self.items.len()
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
}
impl TableSource for FileSource {
fn kind(&self) -> &'static str {
"files"
}
fn register<'a>(
&'a self,
ctx: &'a SessionContext,
opts: &'a IngestOptions,
) -> BoxFuture<'a, Result<Vec<TableEntry>, String>> {
Box::pin(async move {
let mut tables = Vec::new();
for item in &self.items {
match item {
FileItem::Path(path) => tables.extend(register_path(ctx, path, opts).await?),
FileItem::CsvNamed { path, table } => {
if !path.exists() {
return Err(format!("CSV not found: {}", path.display()));
}
let p = path
.to_str()
.ok_or_else(|| "CSV path must be valid UTF-8".to_string())?;
ctx.register_csv(table, p, CsvReadOptions::new())
.await
.map_err(|e| e.to_string())?;
tables.push(TableEntry::csv(table, p));
}
FileItem::Workbook(path) => {
if !path.exists() {
return Err(format!("workbook not found: {}", path.display()));
}
for info in register_workbook(ctx, path, opts)? {
tables.push(TableEntry::sheet(&info, path.display().to_string()));
}
}
}
}
Ok(tables)
})
}
}