use std::fmt::Debug;
use std::pin::Pin;
use std::task::{Context, Poll};
use glaredb_error::{DbError, Result};
use super::glob::is_glob;
use super::{FileOpenContext, FileSystemWithState};
use crate::arrays::datatype::DataType;
use crate::arrays::field::{ColumnSchema, Field};
use crate::arrays::scalar::{BorrowedScalarValue, ScalarValue};
use crate::functions::table::TableFunctionInput;
use crate::functions::table::scan::ScanContext;
use crate::optimizer::expr_rewrite::ExpressionRewriteRule;
use crate::optimizer::expr_rewrite::const_fold::ConstFold;
pub trait FileProvider: Debug + Sync + Send {
fn poll_next(&mut self, cx: &mut Context, out: &mut Vec<String>) -> Poll<Result<usize>>;
}
#[derive(Debug)]
pub struct StaticFileProvider {
paths: Vec<String>,
}
impl StaticFileProvider {
pub fn new<S>(paths: impl IntoIterator<Item = S>) -> Self
where
S: Into<String>,
{
StaticFileProvider {
paths: paths.into_iter().map(|s| s.into()).collect(),
}
}
}
impl FileProvider for StaticFileProvider {
fn poll_next(&mut self, _cx: &mut Context, out: &mut Vec<String>) -> Poll<Result<usize>> {
let n = self.paths.len();
out.append(&mut self.paths);
Poll::Ready(Ok(n))
}
}
#[derive(Debug, Clone)]
pub struct MultiFileData {
expanded: Vec<String>,
}
impl MultiFileData {
pub const fn empty() -> Self {
MultiFileData {
expanded: Vec::new(),
}
}
pub fn expanded_count(&self) -> usize {
self.expanded.len()
}
pub fn get(&self, n: usize) -> Option<&str> {
self.expanded.get(n).map(|s| s.as_str())
}
pub fn expanded(&self) -> &[String] {
&self.expanded
}
}
#[derive(Debug)]
pub struct MultiFileProvider {
provider: Box<dyn FileProvider>,
exhausted: bool,
}
impl MultiFileProvider {
pub const META_PROJECTION_FILENAME: usize = 0;
pub const META_PROJECTION_ROWID: usize = 1;
pub fn meta_schema(&self) -> ColumnSchema {
ColumnSchema::new([
Field::new("_filename", DataType::utf8(), false),
Field::new("_rowid", DataType::int64(), false),
])
}
pub async fn try_new_from_inputs(
scan_context: ScanContext<'_>,
input: &TableFunctionInput,
) -> Result<(Self, FileSystemWithState)> {
let path = ConstFold::rewrite(input.positional[0].clone())?.try_into_scalar()?;
match path {
ScalarValue::Utf8(s) => {
let s = s.into_owned();
let fs = scan_context.dispatch.filesystem_for_path(&s)?;
let context = FileOpenContext::new(scan_context.database_context, &input.named);
let fs = fs.load_state(context).await?;
let provider = if is_glob(&s) {
fs.read_glob(&s).await?
} else {
Box::new(StaticFileProvider::new([s]))
};
let provider = MultiFileProvider {
provider,
exhausted: false,
};
Ok((provider, fs))
}
BorrowedScalarValue::List(list) => {
let paths = list
.into_iter()
.map(|s| s.try_into_string())
.collect::<Result<Vec<_>>>()?;
let fs = match paths.first() {
Some(path) => {
let fs = scan_context.dispatch.filesystem_for_path(path)?;
for path in &paths[1..] {
if !fs.call_can_handle_path(path) {
return Err(DbError::new(format!(
"{} file system cannot handle path '{}'",
fs.name, path
)));
}
}
let context =
FileOpenContext::new(scan_context.database_context, &input.named);
fs.load_state(context).await?
}
None => {
return Err(DbError::new(
"No file paths provided, cannot determine which filesystem to use",
));
}
};
let provider = MultiFileProvider {
provider: Box::new(StaticFileProvider::new(paths)),
exhausted: false,
};
Ok((provider, fs))
}
other => Err(DbError::new(format!(
"Cannot use {} as a file path. Provider either a string or list of strings.",
other,
))),
}
}
pub fn poll_expand_n(
&mut self,
cx: &mut Context,
data: &mut MultiFileData,
n: usize,
) -> Poll<Result<usize>> {
loop {
if n < data.expanded.len() {
return Poll::Ready(Ok(data.expanded.len()));
}
if self.exhausted {
return Poll::Ready(Ok(data.expanded.len()));
}
let appended = match self.provider.poll_next(cx, &mut data.expanded) {
Poll::Ready(Ok(n)) => n,
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
Poll::Pending => return Poll::Pending,
};
if appended == 0 {
self.exhausted = true;
}
}
}
#[must_use]
pub fn expand_n<'a>(&'a mut self, data: &'a mut MultiFileData, n: usize) -> ExpandN<'a> {
ExpandN {
provider: self,
data,
n,
}
}
#[must_use]
pub fn expand_all<'a>(&'a mut self, data: &'a mut MultiFileData) -> ExpandAll<'a> {
ExpandAll {
provider: self,
data,
n: 1, }
}
}
#[derive(Debug)]
pub struct ExpandN<'a> {
provider: &'a mut MultiFileProvider,
data: &'a mut MultiFileData,
n: usize,
}
impl Future for ExpandN<'_> {
type Output = Result<usize>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
this.provider.poll_expand_n(cx, this.data, this.n)
}
}
#[derive(Debug)]
pub struct ExpandAll<'a> {
provider: &'a mut MultiFileProvider,
data: &'a mut MultiFileData,
n: usize,
}
impl Future for ExpandAll<'_> {
type Output = Result<()>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
while !this.provider.exhausted {
let poll = this.provider.poll_expand_n(cx, this.data, this.n)?;
match poll {
Poll::Ready(_) => {
this.n += 10;
}
Poll::Pending => return Poll::Pending,
}
}
Poll::Ready(Ok(()))
}
}