use rudb_catalog::Table;
use rudb_common::{Error, Field, LogicalType, Result};
use rudb_csv::Reader as CsvReader;
use rudb_functions::{TableFunction, open_csv, open_parquet, series_length};
use rudb_kernels::cast;
use rudb_parquet::Reader;
use rudb_plan::{ExprRef, Plan, Slice};
use rudb_vector::{Chunk, Data, VECTOR_SIZE, Vector};
use crate::expr::evaluate_all;
use crate::operator::Operator;
use crate::schema::Schema;
#[derive(Debug)]
pub(crate) struct Scan<'a> {
table: &'a Table,
columns: Vec<usize>,
schema: Schema,
at: usize,
}
impl<'a> Scan<'a> {
pub(crate) fn new(
plan: &Plan,
table: &'a Table,
index: u32,
projection: Slice,
) -> Result<Self> {
let fields = plan.field_list(projection).to_vec();
let mut columns = Vec::with_capacity(fields.len());
for field in &fields {
let position = table.column_index(&field.name).ok_or_else(|| {
Error::catalog(format!(
"Table \"{}\" does not have a column named \"{}\"",
table.name().table,
field.name
))
})?;
columns.push(position);
}
let schema = Schema::numbered(fields, index);
Ok(Self { table, columns, schema, at: 0 })
}
}
impl Operator for Scan<'_> {
fn schema(&self) -> &Schema {
&self.schema
}
fn next(&mut self) -> Result<Option<Chunk>> {
if self.at >= self.table.rows().chunk_count() {
return Ok(None);
}
let chunk = self.table.rows().read(self.at, &self.columns)?;
self.at += 1;
Ok(Some(chunk))
}
}
#[derive(Debug)]
pub(crate) struct Dummy {
schema: Schema,
done: bool,
}
impl Dummy {
pub(crate) fn new() -> Self {
Self { schema: Schema::empty(), done: false }
}
}
impl Operator for Dummy {
fn schema(&self) -> &Schema {
&self.schema
}
fn next(&mut self) -> Result<Option<Chunk>> {
if self.done {
return Ok(None);
}
self.done = true;
Ok(Some(Chunk::with_rows(Vec::new(), 1)?))
}
}
#[derive(Debug)]
pub(crate) struct Values {
schema: Schema,
chunks: Vec<Chunk>,
at: usize,
}
impl Values {
pub(crate) fn new(plan: &Plan, index: u32, columns: Slice, rows: Slice) -> Result<Self> {
let fields = plan.field_list(columns).to_vec();
let schema = Schema::numbered(fields, index);
let types = schema.types();
let source = Schema::empty();
let one = Chunk::with_rows(Vec::new(), 1)?;
let mut down: Vec<Vec<rudb_common::Value>> = vec![Vec::new(); types.len()];
for row in plan.row_list(rows) {
let exprs: Vec<ExprRef> = plan.expr_list(*row).to_vec();
if exprs.len() != types.len() {
return Err(Error::internal(format!(
"a VALUES row of {} expressions in a {} column list",
exprs.len(),
types.len()
)));
}
let evaluated = evaluate_all(plan, &exprs, &source, &one)?;
for (position, vector) in evaluated.iter().enumerate() {
down[position].push(vector.value_at(0));
}
}
let total = down.first().map_or(0, Vec::len);
let mut chunks = Vec::new();
let mut start = 0;
while start < total {
let end = (start + VECTOR_SIZE).min(total);
let mut built = Vec::with_capacity(types.len());
for (position, ty) in types.iter().enumerate() {
built.push(Vector::from_values(ty.clone(), &down[position][start..end])?);
}
chunks.push(Chunk::with_rows(built, end - start)?);
start = end;
}
Ok(Self { schema, chunks, at: 0 })
}
}
#[derive(Debug)]
pub(crate) struct Series {
schema: Schema,
at: i64,
step: i64,
left: usize,
}
impl Series {
pub(crate) fn new(plan: &Plan, index: u32, function: &str, args: Slice) -> Result<Self> {
let Some(function) = TableFunction::lookup(function) else {
return Err(Error::internal(format!("a plan with a table function called {function}")));
};
let fields = vec![Field::new(function.name(), LogicalType::BigInt)];
let schema = Schema::numbered(fields, index);
let exprs: Vec<ExprRef> = plan.expr_list(args).to_vec();
let source = Schema::empty();
let one = Chunk::with_rows(Vec::new(), 1)?;
let evaluated = evaluate_all(plan, &exprs, &source, &one)?;
let mut given = Vec::with_capacity(evaluated.len());
for vector in &evaluated {
match vector.value_at(0) {
rudb_common::Value::Null => return Ok(Self::empty(schema)),
rudb_common::Value::BigInt(n) => given.push(n),
other => {
return Err(Error::internal(format!(
"a table function argument bound as BIGINT arrived as {other}"
)));
}
}
}
let (start, stop, step) = match given.as_slice() {
[stop] => (0, *stop, 1),
[start, stop] => (*start, *stop, 1),
[start, stop, step] => (*start, *stop, *step),
_ => {
return Err(Error::internal(format!(
"{}() bound with {} arguments",
function.name(),
given.len()
)));
}
};
let left = series_length(function, start, stop, step)?;
Ok(Self { schema, at: start, step, left })
}
fn empty(schema: Schema) -> Self {
Self { schema, at: 0, step: 1, left: 0 }
}
}
impl Operator for Series {
fn schema(&self) -> &Schema {
&self.schema
}
fn next(&mut self) -> Result<Option<Chunk>> {
if self.left == 0 {
return Ok(None);
}
let count = self.left.min(VECTOR_SIZE);
let mut counted = Vec::with_capacity(count);
for _ in 0..count {
counted.push(self.at);
self.at = self.at.saturating_add(self.step);
}
self.left -= count;
let vector = Vector::flat(LogicalType::BigInt, Data::Int64(counted.into()))?;
Ok(Some(Chunk::with_rows(vec![vector], count)?))
}
}
#[derive(Debug)]
pub(crate) struct FileScan {
function: TableFunction,
paths: Vec<String>,
at: usize,
reader: Option<FileReader>,
wanted: Vec<Field>,
schema: Schema,
}
impl FileScan {
pub(crate) fn new(
plan: &Plan,
index: u32,
function: TableFunction,
args: Slice,
columns: Slice,
) -> Result<Self> {
let paths = file_arguments(plan, args, function)?;
let wanted = plan.field_list(columns).to_vec();
let mut scan = Self {
function,
paths,
at: 0,
reader: None,
wanted: wanted.clone(),
schema: Schema::numbered(wanted, index),
};
scan.advance()?;
Ok(scan)
}
fn advance(&mut self) -> Result<()> {
self.reader = None;
let Some(path) = self.paths.get(self.at) else { return Ok(()) };
let mut reader = FileReader::open(self.function, path)?;
let first = if self.at == 0 { None } else { self.paths.first().map(String::as_str) };
reader.project(&positions(self.function, &self.wanted, &reader.fields(), path, first)?)?;
reader.settle(&self.wanted)?;
self.reader = Some(reader);
self.at += 1;
Ok(())
}
}
impl Operator for FileScan {
fn schema(&self) -> &Schema {
&self.schema
}
fn next(&mut self) -> Result<Option<Chunk>> {
loop {
let Some(reader) = self.reader.as_mut() else { return Ok(None) };
if let Some(chunk) = reader.next_chunk()? {
return Ok(Some(self.conform(chunk)?));
}
self.advance()?;
}
}
}
impl FileScan {
fn conform(&self, chunk: Chunk) -> Result<Chunk> {
let rows = chunk.len();
let mut columns = Vec::with_capacity(self.wanted.len());
let mut changed = false;
for (at, field) in self.wanted.iter().enumerate() {
let column = chunk.column(at)?;
if column.logical_type() == &field.ty {
columns.push(column.clone());
continue;
}
changed = true;
columns.push(cast(column, &field.ty, false).map_err(|error| {
let path = self.paths.get(self.at.saturating_sub(1)).map_or("", String::as_str);
Error::conversion(format!(
"Error while reading file \"{path}\": failed to cast column \"{}\" from type \
{} to {}: {}",
field.name,
column.logical_type(),
field.ty,
error.message()
))
})?);
}
if !changed {
return Ok(chunk);
}
Chunk::with_rows(columns, rows)
}
}
#[derive(Debug)]
enum FileReader {
Parquet(Reader),
Csv(CsvReader),
}
impl FileReader {
fn open(function: TableFunction, path: &str) -> Result<Self> {
match function {
TableFunction::ReadCsv => Ok(Self::Csv(open_csv(path)?)),
_ => Ok(Self::Parquet(open_parquet(path)?)),
}
}
fn fields(&self) -> Vec<Field> {
match self {
Self::Parquet(reader) => reader.fields(),
Self::Csv(reader) => reader.fields(),
}
}
fn project(&mut self, columns: &[usize]) -> Result<()> {
match self {
Self::Parquet(reader) => reader.project(columns),
Self::Csv(reader) => reader.project(columns),
}
}
fn settle(&mut self, wanted: &[Field]) -> Result<()> {
match self {
Self::Parquet(_) => Ok(()),
Self::Csv(reader) => {
let types: Vec<LogicalType> = wanted.iter().map(|field| field.ty.clone()).collect();
reader.retype(&types)
}
}
}
fn next_chunk(&mut self) -> Result<Option<Chunk>> {
match self {
Self::Parquet(reader) => reader.next_chunk(),
Self::Csv(reader) => reader.next_chunk(),
}
}
}
fn file_arguments(plan: &Plan, args: Slice, function: TableFunction) -> Result<Vec<String>> {
let exprs: Vec<ExprRef> = plan.expr_list(args).to_vec();
let source = Schema::empty();
let one = Chunk::with_rows(Vec::new(), 1)?;
let evaluated = evaluate_all(plan, &exprs, &source, &one)?;
let mut paths = Vec::with_capacity(evaluated.len());
for vector in &evaluated {
match vector.value_at(0) {
rudb_common::Value::Varchar(path) => paths.push(path),
other => {
return Err(Error::internal(format!(
"{}() bound with {other:?} rather than constant file names",
function.name()
)));
}
}
}
Ok(paths)
}
fn positions(
function: TableFunction,
wanted: &[Field],
held: &[Field],
path: &str,
first: Option<&str>,
) -> Result<Vec<usize>> {
let mut positions = Vec::with_capacity(wanted.len());
for field in wanted {
let at = held.iter().position(|column| column.name == field.name).ok_or_else(|| {
let Some(first) = first else {
return Error::io(format!(
"File \"{path}\" does not have a column named \"{}\"",
field.name
));
};
if matches!(function, TableFunction::ReadCsv) {
return rudb_csv::mismatch(first, path, &field.name);
}
let candidates: Vec<&str> = held.iter().map(|column| column.name.as_str()).collect();
Error::invalid_input(format!(
"Failed to read file \"{path}\": schema mismatch in glob: column \"{}\" was read \
from the original file \"{first}\", but could not be found in file \
\"{path}\".\nCandidate names: {}\nIf you are trying to read files with different \
schemas, try setting union_by_name=True",
field.name,
candidates.join(", ")
))
})?;
positions.push(at);
}
Ok(positions)
}
impl Operator for Values {
fn schema(&self) -> &Schema {
&self.schema
}
fn next(&mut self) -> Result<Option<Chunk>> {
if self.at >= self.chunks.len() {
return Ok(None);
}
let chunk = self.chunks[self.at].clone();
self.at += 1;
Ok(Some(chunk))
}
}