use std::collections::HashMap;
use vibesql_ast::{Expression, SelectItem};
use vibesql_storage::Row;
use super::{ExecutionContext, ExecutionPipeline, PipelineInput, PipelineOutput};
use crate::{
errors::ExecutorError,
select::{
filter::apply_where_filter_combined_auto, projection::project_row_combined,
window::WindowFunctionKey,
},
SelectExecutor,
};
pub struct RowOrientedPipeline<'a> {
executor: &'a SelectExecutor<'a>,
window_mapping: Option<HashMap<WindowFunctionKey, usize>>,
}
impl<'a> RowOrientedPipeline<'a> {
#[inline]
pub fn new(executor: &'a SelectExecutor<'a>) -> Self {
Self { executor, window_mapping: None }
}
#[inline]
pub fn with_window_mapping(
executor: &'a SelectExecutor<'a>,
window_mapping: HashMap<WindowFunctionKey, usize>,
) -> Self {
Self { executor, window_mapping: Some(window_mapping) }
}
}
impl ExecutionPipeline for RowOrientedPipeline<'_> {
#[inline]
fn create_evaluator<'a>(
&self,
ctx: &'a ExecutionContext<'a>,
) -> crate::evaluator::CombinedExpressionEvaluator<'a> {
ctx.create_evaluator()
}
#[inline]
fn apply_filter(
&self,
input: PipelineInput<'_>,
predicate: Option<&Expression>,
ctx: &ExecutionContext<'_>,
) -> Result<PipelineOutput, ExecutorError> {
let rows = input.into_rows();
if predicate.is_none() {
return Ok(PipelineOutput::from_rows(rows));
}
let evaluator = ctx.create_evaluator();
let filtered =
apply_where_filter_combined_auto(rows, predicate, &evaluator, self.executor)?;
Ok(PipelineOutput::from_rows(filtered))
}
#[inline]
fn apply_projection(
&self,
input: PipelineInput<'_>,
select_items: &[SelectItem],
ctx: &ExecutionContext<'_>,
) -> Result<PipelineOutput, ExecutorError> {
let rows = input.into_rows();
if rows.is_empty() {
return Ok(PipelineOutput::Empty);
}
let evaluator = ctx.create_evaluator();
let buffer_pool = self.executor.query_buffer_pool();
let mut projected_rows = Vec::with_capacity(rows.len());
for row in rows {
self.executor.check_timeout()?;
let projected = project_row_combined(
&row,
select_items,
&evaluator,
ctx.schema,
&self.window_mapping,
buffer_pool,
)?;
projected_rows.push(projected);
}
Ok(PipelineOutput::from_rows(projected_rows))
}
fn apply_aggregation(
&self,
input: PipelineInput<'_>,
select_items: &[SelectItem],
group_by: Option<&[Expression]>,
_having: Option<&Expression>,
ctx: &ExecutionContext<'_>,
) -> Result<PipelineOutput, ExecutorError> {
if let Some(exprs) = group_by {
if !exprs.is_empty() {
return Err(ExecutorError::UnsupportedFeature(
"GROUP BY aggregation should use SelectExecutor.execute_with_aggregation()"
.to_string(),
));
}
}
let rows = input.into_rows();
if rows.is_empty() {
let evaluator = ctx.create_evaluator();
let empty_row = Row::new(vec![]);
let mut values = Vec::new();
for item in select_items {
if let SelectItem::Expression { expr, .. } = item {
let value = evaluator.eval(expr, &empty_row)?;
values.push(value);
}
}
return Ok(PipelineOutput::from_rows(vec![Row::new(values)]));
}
let evaluator = ctx.create_evaluator();
let first_row = &rows[0];
let mut values = Vec::new();
for item in select_items {
if let SelectItem::Expression { expr, .. } = item {
let value = evaluator.eval(expr, first_row)?;
values.push(value);
}
}
Ok(PipelineOutput::from_rows(vec![Row::new(values)]))
}
#[inline]
fn supports_query_pattern(
&self,
has_aggregation: bool,
has_group_by: bool,
_has_joins: bool,
) -> bool {
!has_aggregation || !has_group_by
}
#[inline]
fn name(&self) -> &'static str {
"RowOrientedPipeline"
}
}
#[cfg(test)]
mod tests {
use vibesql_catalog::TableSchema;
use vibesql_types::SqlValue;
use super::*;
use crate::schema::CombinedSchema;
fn create_test_setup() -> (vibesql_storage::Database, CombinedSchema) {
let database = vibesql_storage::Database::new();
let table_schema = TableSchema::new("test".to_string(), vec![]);
let schema = CombinedSchema::from_table("test".to_string(), table_schema);
(database, schema)
}
fn make_test_row(values: Vec<i64>) -> Row {
Row::new(values.into_iter().map(SqlValue::Integer).collect::<Vec<_>>())
}
#[test]
fn test_row_pipeline_name() {
let database = vibesql_storage::Database::new();
let executor = SelectExecutor::new(&database);
let pipeline = RowOrientedPipeline::new(&executor);
assert_eq!(pipeline.name(), "RowOrientedPipeline");
}
#[test]
fn test_row_pipeline_supports_patterns() {
let database = vibesql_storage::Database::new();
let executor = SelectExecutor::new(&database);
let pipeline = RowOrientedPipeline::new(&executor);
assert!(pipeline.supports_query_pattern(false, false, false));
assert!(pipeline.supports_query_pattern(false, false, true));
assert!(pipeline.supports_query_pattern(true, false, false));
assert!(!pipeline.supports_query_pattern(true, true, false));
}
#[test]
fn test_row_pipeline_filter_no_predicate() {
let (database, schema) = create_test_setup();
let executor = SelectExecutor::new(&database);
let pipeline = RowOrientedPipeline::new(&executor);
let rows = vec![make_test_row(vec![1, 2]), make_test_row(vec![3, 4])];
let input = PipelineInput::from_rows_owned(rows);
let ctx = ExecutionContext::new(&schema, &database);
let result = pipeline.apply_filter(input, None, &ctx).unwrap();
assert_eq!(result.row_count(), 2);
}
#[test]
fn test_row_pipeline_limit_offset() {
let (database, _schema) = create_test_setup();
let executor = SelectExecutor::new(&database);
let pipeline = RowOrientedPipeline::new(&executor);
let rows = vec![
make_test_row(vec![1]),
make_test_row(vec![2]),
make_test_row(vec![3]),
make_test_row(vec![4]),
make_test_row(vec![5]),
];
let output = PipelineOutput::from_rows(rows);
let result = pipeline.apply_limit_offset(output, Some(3), None).unwrap();
assert_eq!(result.len(), 3);
}
}