use std::sync::Arc;
use rudb_common::{Error, Field, LogicalType, Result};
use rudb_functions::open_parquet;
use rudb_kernels::cast;
use rudb_metrics::Counters;
use rudb_parquet::Reader;
use rudb_pipeline::{Progress, Stream};
use rudb_plan::{ExprRef, Plan, Slice};
use rudb_vector::{Chunk, Data};
use crate::prepared::{Prepared, Scratch};
use crate::schema::Schema;
use crate::source::{file_arguments, positions};
#[derive(Debug)]
pub(crate) struct Fetch {
path: String,
wanted: Vec<Field>,
row: Prepared,
schema: Schema,
counters: Option<Arc<Counters>>,
}
#[derive(Debug)]
pub(crate) struct Fetching {
scratch: Scratch,
reader: Option<Reader>,
}
impl Fetch {
pub(crate) fn new(
plan: &Plan,
input: &Schema,
index: u32,
args: Slice,
columns: Slice,
row: ExprRef,
) -> Result<Self> {
let mut paths = file_arguments(plan, args, rudb_functions::TableFunction::ReadParquet)?;
if paths.len() != 1 {
return Err(Error::internal(format!(
"a fetch over {} files, where a row ordinal names no row at all",
paths.len()
)));
}
let wanted = plan.field_list(columns).to_vec();
Ok(Self {
path: paths.remove(0),
schema: Schema::numbered(wanted.clone(), index),
wanted,
row: Prepared::one(plan, row, input)?,
counters: None,
})
}
pub(crate) fn watched(mut self, counters: Arc<Counters>) -> Self {
self.counters = Some(counters);
self
}
pub(crate) fn schema(&self) -> &Schema {
&self.schema
}
fn open(&self) -> Result<Reader> {
let mut reader = open_parquet(&self.path)?;
let held = reader.fields();
reader.project(&positions(
rudb_functions::TableFunction::ReadParquet,
&self.wanted,
&held,
&self.path,
None,
)?)?;
let text: Vec<bool> =
self.wanted.iter().map(|field| field.ty == LogicalType::Varchar).collect();
reader.as_string(&text);
Ok(reader)
}
fn conform(&self, chunk: Chunk) -> Result<Chunk> {
let rows = chunk.len();
let settled = self.wanted.iter().enumerate().all(|(at, field)| {
chunk.column(at).is_ok_and(|column| column.logical_type() == &field.ty)
});
if settled {
return Ok(chunk);
}
let mut columns = Vec::with_capacity(self.wanted.len());
for (at, field) in self.wanted.iter().enumerate() {
let column = chunk.column(at)?;
if column.logical_type() == &field.ty {
columns.push(column.clone());
continue;
}
columns.push(cast(column, &field.ty, false).map_err(|error| {
Error::conversion(format!(
"Error while reading file \"{}\": failed to cast column \"{}\" from type {} \
to {}: {}",
self.path,
field.name,
column.logical_type(),
field.ty,
error.message()
))
})?);
}
Chunk::with_rows(columns, rows)
}
}
impl Stream for Fetch {
type Local = Fetching;
fn local(&self) -> Fetching {
Fetching { scratch: self.row.scratch(), reader: None }
}
fn push(&self, chunk: &mut Chunk, local: &mut Fetching) -> Result<Progress> {
if chunk.is_empty() {
*chunk = Chunk::empty(&self.schema.types());
return Ok(Progress::More);
}
let ordinals = self.row.evaluate_one(chunk, &mut local.scratch)?;
let count = ordinals.len();
let flat = ordinals.flatten()?;
let held: &[i64] = match flat.data() {
Some(Data::Int64(values)) if !flat.validity().has_nulls(count) => values.as_slice(),
_ => {
return Err(Error::internal(
"a fetch was handed something other than a row ordinal in every row",
));
}
};
let mut order: Vec<usize> = (0..count).collect();
order.sort_by_key(|&at| held[at]);
let mut rows: Vec<u64> = Vec::with_capacity(count);
let mut taken = vec![0_u32; count];
for &at in &order {
let row = u64::try_from(held[at]).map_err(|_| {
Error::internal(format!(
"a fetch was handed the row ordinal {}, which is before the file starts",
held[at]
))
})?;
if rows.last() != Some(&row) {
rows.push(row);
}
taken[at] = u32::try_from(rows.len() - 1).unwrap_or(u32::MAX);
}
let reader = match local.reader.as_mut() {
Some(reader) => reader,
None => local.reader.insert(self.open()?),
};
let before = reader.bytes_read();
let fetched = reader.rows_at(&rows)?;
if let Some(counters) = &self.counters {
counters.read(reader.bytes_read().saturating_sub(before));
}
let fetched = self.conform(fetched)?;
let width = fetched.width();
let mut columns = Vec::with_capacity(width);
for at in 0..width {
columns.push(fetched.column(at)?.gather(&taken)?);
}
*chunk = Chunk::with_rows(columns, taken.len())?;
Ok(Progress::More)
}
}