use std::collections::HashMap;
use vibesql_ast::{Expression, SelectItem};
use vibesql_storage::{QueryBufferPool, Row};
use super::window::WindowFunctionKey;
use crate::{
errors::ExecutorError, evaluator::CombinedExpressionEvaluator, schema::CombinedSchema,
};
const BATCH_PROJECTION_THRESHOLD: usize = 100;
#[allow(unused_variables)]
pub fn try_batch_project_simd(
rows: &[Row],
columns: &[SelectItem],
evaluator: &CombinedExpressionEvaluator,
schema: &CombinedSchema,
window_mapping: &Option<HashMap<WindowFunctionKey, usize>>,
buffer_pool: &QueryBufferPool,
) -> Result<Option<Vec<Row>>, ExecutorError> {
if rows.len() < BATCH_PROJECTION_THRESHOLD || window_mapping.is_some() {
return Ok(None);
}
let column_indices = match extract_simple_column_indices(columns, schema) {
Some(indices) => indices,
None => return Ok(None), };
let projected_rows = batch_project_by_indices(rows, &column_indices, buffer_pool);
Ok(Some(projected_rows))
}
fn extract_simple_column_indices(
columns: &[SelectItem],
schema: &CombinedSchema,
) -> Option<Vec<usize>> {
let mut indices = Vec::with_capacity(columns.len());
for item in columns {
match item {
SelectItem::Expression { expr, alias: _, .. } => {
if let Expression::ColumnRef(col_id) = expr {
if let Some(idx) = resolve_column_index(
col_id.table_canonical(),
col_id.column_canonical(),
schema,
) {
indices.push(idx);
} else {
return None;
}
} else {
return None;
}
}
SelectItem::Wildcard { .. } | SelectItem::QualifiedWildcard { .. } => {
return None;
}
}
}
Some(indices)
}
fn resolve_column_index(
table: Option<&str>,
column: &str,
schema: &CombinedSchema,
) -> Option<usize> {
if let Some(table_name) = table {
let table_id = vibesql_catalog::TableIdentifier::unquoted(table_name);
if let Some((start_idx, table_schema)) = schema.table_schemas.get(&table_id) {
for (col_idx, col) in table_schema.columns.iter().enumerate() {
if col.name.eq_ignore_ascii_case(column) {
return Some(start_idx + col_idx);
}
}
}
for (tbl_id, (start_idx, table_schema)) in &schema.table_schemas {
if tbl_id.canonical() == table_name.to_ascii_lowercase() {
for (col_idx, col) in table_schema.columns.iter().enumerate() {
if col.name.eq_ignore_ascii_case(column) {
return Some(start_idx + col_idx);
}
}
}
}
return None;
}
for (start_idx, table_schema) in schema.table_schemas.values() {
for (col_idx, col) in table_schema.columns.iter().enumerate() {
if col.name.eq_ignore_ascii_case(column) {
return Some(start_idx + col_idx);
}
}
}
None
}
fn batch_project_by_indices(
rows: &[Row],
column_indices: &[usize],
buffer_pool: &QueryBufferPool,
) -> Vec<Row> {
let num_output_columns = column_indices.len();
let mut result = buffer_pool.get_row_buffer(rows.len());
for row in rows {
let mut values = Vec::with_capacity(num_output_columns);
for &idx in column_indices {
if let Some(value) = row.values.get(idx) {
values.push(value.clone());
} else {
values.push(vibesql_types::SqlValue::Null);
}
}
result.push(Row::new(values));
}
result
}
#[cfg(test)]
mod tests {
use vibesql_ast::SelectItem;
use vibesql_storage::{QueryBufferPool, Row};
use vibesql_types::{DataType, SqlValue};
use super::*;
use crate::{evaluator::CombinedExpressionEvaluator, schema::CombinedSchema};
fn create_test_evaluator() -> CombinedExpressionEvaluator<'static> {
use vibesql_catalog::{ColumnSchema, TableSchema};
let columns = vec![
ColumnSchema::new("a".to_string(), DataType::Bigint, false),
ColumnSchema::new("b".to_string(), DataType::Bigint, false),
];
let table_schema = TableSchema::new("test".to_string(), columns);
let schema =
Box::leak(Box::new(CombinedSchema::from_table("test".to_string(), table_schema)));
CombinedExpressionEvaluator::new(schema)
}
fn create_test_schema() -> CombinedSchema {
use vibesql_catalog::{ColumnSchema, TableSchema};
let columns = vec![
ColumnSchema::new("a".to_string(), DataType::Bigint, false),
ColumnSchema::new("b".to_string(), DataType::Bigint, false),
];
let table_schema = TableSchema::new("test".to_string(), columns);
CombinedSchema::from_table("test".to_string(), table_schema)
}
#[test]
fn test_try_batch_project_simd_returns_none_for_literals() {
let rows: Vec<Row> = (0..200)
.map(|i| Row::new(vec![SqlValue::Bigint(i as i64), SqlValue::Bigint(i as i64 * 2)]))
.collect();
let columns = vec![SelectItem::Expression {
expr: vibesql_ast::Expression::Literal(SqlValue::Integer(42)),
alias: None,
source_text: None,
}];
let evaluator = create_test_evaluator();
let schema = create_test_schema();
let buffer_pool = QueryBufferPool::new();
let result =
try_batch_project_simd(&rows, &columns, &evaluator, &schema, &None, &buffer_pool);
assert!(result.is_ok());
assert_eq!(result.unwrap(), None);
}
#[test]
fn test_try_batch_project_simd_returns_none_for_small_rowset() {
let rows: Vec<Row> = (0..50)
.map(|i| Row::new(vec![SqlValue::Bigint(i as i64), SqlValue::Bigint(i as i64 * 2)]))
.collect();
let columns = vec![SelectItem::Expression {
expr: vibesql_ast::Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"a", false,
)),
alias: None,
source_text: None,
}];
let evaluator = create_test_evaluator();
let schema = create_test_schema();
let buffer_pool = QueryBufferPool::new();
let result =
try_batch_project_simd(&rows, &columns, &evaluator, &schema, &None, &buffer_pool);
assert!(result.is_ok());
assert_eq!(result.unwrap(), None);
}
#[test]
fn test_try_batch_project_simd_works_for_simple_columns() {
let rows: Vec<Row> = (0..200)
.map(|i| Row::new(vec![SqlValue::Bigint(i as i64), SqlValue::Bigint(i as i64 * 2)]))
.collect();
let columns = vec![SelectItem::Expression {
expr: vibesql_ast::Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"b", false,
)),
alias: None,
source_text: None,
}];
let evaluator = create_test_evaluator();
let schema = create_test_schema();
let buffer_pool = QueryBufferPool::new();
let result =
try_batch_project_simd(&rows, &columns, &evaluator, &schema, &None, &buffer_pool);
assert!(result.is_ok());
let projected = result.unwrap();
assert!(projected.is_some());
let projected_rows = projected.unwrap();
assert_eq!(projected_rows.len(), 200);
for (i, row) in projected_rows.iter().enumerate() {
assert_eq!(row.values.len(), 1);
assert_eq!(row.values[0], SqlValue::Bigint(i as i64 * 2));
}
}
#[test]
fn test_try_batch_project_simd_multiple_columns() {
let rows: Vec<Row> = (0..200)
.map(|i| Row::new(vec![SqlValue::Bigint(i as i64), SqlValue::Bigint(i as i64 * 10)]))
.collect();
let columns = vec![
SelectItem::Expression {
expr: vibesql_ast::Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"b", false,
)),
alias: None,
source_text: None,
},
SelectItem::Expression {
expr: vibesql_ast::Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"a", false,
)),
alias: None,
source_text: None,
},
];
let evaluator = create_test_evaluator();
let schema = create_test_schema();
let buffer_pool = QueryBufferPool::new();
let result =
try_batch_project_simd(&rows, &columns, &evaluator, &schema, &None, &buffer_pool);
assert!(result.is_ok());
let projected = result.unwrap();
assert!(projected.is_some());
let projected_rows = projected.unwrap();
assert_eq!(projected_rows.len(), 200);
for (i, row) in projected_rows.iter().enumerate() {
assert_eq!(row.values.len(), 2);
assert_eq!(row.values[0], SqlValue::Bigint(i as i64 * 10)); assert_eq!(row.values[1], SqlValue::Bigint(i as i64)); }
}
}