use std::sync::Arc;
use vibesql_ast::Expression;
use vibesql_types::SqlValue;
use crate::{
errors::ExecutorError,
schema::CombinedSchema,
select::columnar::{
aggregate::AggregateOp,
batch::{ColumnArray, ColumnarBatch},
},
};
pub fn compute_batch_expression_aggregate(
batch: &ColumnarBatch,
expr: &Expression,
op: AggregateOp,
schema: &CombinedSchema,
) -> Result<SqlValue, ExecutorError> {
log::debug!(
"[BatchExpr] compute_batch_expression_aggregate: {} rows, op={:?}",
batch.row_count(),
op
);
if batch.row_count() == 0 {
log::debug!("[BatchExpr] Empty batch, returning default");
return Ok(match op {
AggregateOp::Count => SqlValue::Integer(0),
_ => SqlValue::Null,
});
}
log::debug!("[BatchExpr] Evaluating expression on batch columns (SIMD-enabled)");
let result_array = evaluate_batch_expression(batch, expr, schema)?;
log::debug!("[BatchExpr] Aggregating result array with SIMD");
aggregate_column_array(&result_array, op)
}
fn evaluate_batch_expression(
batch: &ColumnarBatch,
expr: &Expression,
schema: &CombinedSchema,
) -> Result<ColumnArray, ExecutorError> {
match expr {
Expression::ColumnRef(col_id) => {
let column = col_id.column_canonical();
let col_idx =
schema.get_column_index(col_id.table_canonical(), column).ok_or_else(|| {
ExecutorError::UnsupportedExpression(format!("Column not found: {}", column))
})?;
batch.column(col_idx).cloned().ok_or_else(|| ExecutorError::ColumnarColumnNotFound {
column_index: col_idx,
batch_columns: batch.column_count(),
})
}
Expression::Literal(val) => {
create_literal_column_array(val, batch.row_count())
}
Expression::BinaryOp { left, op, right } => {
let left_array = evaluate_batch_expression(batch, left, schema)?;
let right_array = evaluate_batch_expression(batch, right, schema)?;
apply_binary_op_to_columns(&left_array, &right_array, op)
}
_ => Err(ExecutorError::UnsupportedExpression(
"Complex expressions not supported in batch-native columnar aggregates".to_string(),
)),
}
}
fn create_literal_column_array(value: &SqlValue, len: usize) -> Result<ColumnArray, ExecutorError> {
match value {
SqlValue::Integer(i) | SqlValue::Bigint(i) => {
Ok(ColumnArray::Int64(Arc::new(vec![*i; len]), None))
}
SqlValue::Smallint(i) => Ok(ColumnArray::Int64(Arc::new(vec![*i as i64; len]), None)),
SqlValue::Float(f) => Ok(ColumnArray::Float64(Arc::new(vec![*f as f64; len]), None)),
SqlValue::Double(f) | SqlValue::Numeric(f) | SqlValue::Real(f) => {
Ok(ColumnArray::Float64(Arc::new(vec![*f; len]), None))
}
SqlValue::Null => {
Ok(ColumnArray::Float64(Arc::new(vec![0.0; len]), Some(Arc::new(vec![true; len]))))
}
_ => Err(ExecutorError::UnsupportedExpression(format!(
"Cannot create literal column array for {:?}",
value
))),
}
}
fn apply_binary_op_to_columns(
left: &ColumnArray,
right: &ColumnArray,
op: &vibesql_ast::BinaryOperator,
) -> Result<ColumnArray, ExecutorError> {
use vibesql_ast::BinaryOperator::*;
let (left_typed, right_typed) = match (left, right) {
(ColumnArray::Mixed(left_vals), ColumnArray::Mixed(right_vals)) => {
let left_f64 = try_extract_f64_from_mixed(left_vals)?;
let right_f64 = try_extract_f64_from_mixed(right_vals)?;
(
ColumnArray::Float64(Arc::new(left_f64), None),
ColumnArray::Float64(Arc::new(right_f64), None),
)
}
(ColumnArray::Mixed(left_vals), other) => {
let left_f64 = try_extract_f64_from_mixed(left_vals)?;
(ColumnArray::Float64(Arc::new(left_f64), None), other.clone())
}
(other, ColumnArray::Mixed(right_vals)) => {
let right_f64 = try_extract_f64_from_mixed(right_vals)?;
(other.clone(), ColumnArray::Float64(Arc::new(right_f64), None))
}
_ => (left.clone(), right.clone()),
};
match (&left_typed, &right_typed) {
(
ColumnArray::Float64(left_vals, left_nulls),
ColumnArray::Float64(right_vals, right_nulls),
) => {
let result = apply_float64_binary_op(left_vals, right_vals, op)?;
let nulls = merge_null_bitmaps(
left_nulls.as_ref().map(|a| a.as_slice()),
right_nulls.as_ref().map(|a| a.as_slice()),
left_vals.len(),
);
Ok(ColumnArray::Float64(Arc::new(result), nulls.map(Arc::new)))
}
(
ColumnArray::Int64(left_vals, left_nulls),
ColumnArray::Int64(right_vals, right_nulls),
) => {
match op {
Plus | Minus | Multiply => {
let result = apply_int64_binary_op(left_vals, right_vals, op)?;
let nulls = merge_null_bitmaps(
left_nulls.as_ref().map(|a| a.as_slice()),
right_nulls.as_ref().map(|a| a.as_slice()),
left_vals.len(),
);
Ok(ColumnArray::Int64(Arc::new(result), nulls.map(Arc::new)))
}
Divide => {
let left_f64: Vec<f64> = left_vals.iter().map(|&v| v as f64).collect();
let right_f64: Vec<f64> = right_vals.iter().map(|&v| v as f64).collect();
let result = apply_float64_binary_op(&left_f64, &right_f64, op)?;
let nulls = merge_null_bitmaps(
left_nulls.as_ref().map(|a| a.as_slice()),
right_nulls.as_ref().map(|a| a.as_slice()),
left_vals.len(),
);
Ok(ColumnArray::Float64(Arc::new(result), nulls.map(Arc::new)))
}
_ => Err(ExecutorError::UnsupportedExpression(format!(
"Unsupported binary operator for Int64: {:?}",
op
))),
}
}
(
ColumnArray::Int64(left_vals, left_nulls),
ColumnArray::Float64(right_vals, right_nulls),
) => {
let left_f64: Vec<f64> = left_vals.iter().map(|&v| v as f64).collect();
let result = apply_float64_binary_op(&left_f64, right_vals, op)?;
let nulls = merge_null_bitmaps(
left_nulls.as_ref().map(|a| a.as_slice()),
right_nulls.as_ref().map(|a| a.as_slice()),
left_vals.len(),
);
Ok(ColumnArray::Float64(Arc::new(result), nulls.map(Arc::new)))
}
(
ColumnArray::Float64(left_vals, left_nulls),
ColumnArray::Int64(right_vals, right_nulls),
) => {
let right_f64: Vec<f64> = right_vals.iter().map(|&v| v as f64).collect();
let result = apply_float64_binary_op(left_vals, &right_f64, op)?;
let nulls = merge_null_bitmaps(
left_nulls.as_ref().map(|a| a.as_slice()),
right_nulls.as_ref().map(|a| a.as_slice()),
left_vals.len(),
);
Ok(ColumnArray::Float64(Arc::new(result), nulls.map(Arc::new)))
}
_ => Err(ExecutorError::UnsupportedExpression(
"Non-numeric columns not supported in batch arithmetic".to_string(),
)),
}
}
fn try_extract_f64_from_mixed(values: &[SqlValue]) -> Result<Vec<f64>, ExecutorError> {
values
.iter()
.map(|v| match v {
SqlValue::Integer(i) | SqlValue::Bigint(i) => Ok(*i as f64),
SqlValue::Smallint(i) => Ok(*i as f64),
SqlValue::Float(f) => Ok(*f as f64),
SqlValue::Double(f) | SqlValue::Numeric(f) | SqlValue::Real(f) => Ok(*f), SqlValue::Null => Ok(f64::NAN), _ => Err(ExecutorError::UnsupportedExpression(
"Non-numeric columns not supported in batch arithmetic".to_string(),
)),
})
.collect()
}
fn apply_float64_binary_op(
left: &[f64],
right: &[f64],
op: &vibesql_ast::BinaryOperator,
) -> Result<Vec<f64>, ExecutorError> {
use vibesql_ast::BinaryOperator::*;
if left.len() != right.len() {
return Err(ExecutorError::ColumnarLengthMismatch {
context: "binary_op".to_string(),
expected: left.len(),
actual: right.len(),
});
}
let result: Vec<f64> = match op {
Plus => left.iter().zip(right.iter()).map(|(l, r)| l + r).collect(),
Minus => left.iter().zip(right.iter()).map(|(l, r)| l - r).collect(),
Multiply => left.iter().zip(right.iter()).map(|(l, r)| l * r).collect(),
Divide => left.iter().zip(right.iter()).map(|(l, r)| l / r).collect(),
_ => {
return Err(ExecutorError::UnsupportedExpression(format!(
"Unsupported binary operator for Float64: {:?}",
op
)))
}
};
Ok(result)
}
fn apply_int64_binary_op(
left: &[i64],
right: &[i64],
op: &vibesql_ast::BinaryOperator,
) -> Result<Vec<i64>, ExecutorError> {
use vibesql_ast::BinaryOperator::*;
if left.len() != right.len() {
return Err(ExecutorError::ColumnarLengthMismatch {
context: "binary_op".to_string(),
expected: left.len(),
actual: right.len(),
});
}
let result: Vec<i64> = match op {
Plus => left.iter().zip(right.iter()).map(|(l, r)| l + r).collect(),
Minus => left.iter().zip(right.iter()).map(|(l, r)| l - r).collect(),
Multiply => left.iter().zip(right.iter()).map(|(l, r)| l * r).collect(),
_ => {
return Err(ExecutorError::UnsupportedExpression(format!(
"Unsupported binary operator for Int64: {:?}",
op
)))
}
};
Ok(result)
}
fn merge_null_bitmaps(
left: Option<&[bool]>,
right: Option<&[bool]>,
_len: usize,
) -> Option<Vec<bool>> {
match (left, right) {
(None, None) => None,
(Some(l), None) => Some(l.to_vec()),
(None, Some(r)) => Some(r.to_vec()),
(Some(l), Some(r)) => {
let merged: Vec<bool> =
l.iter().zip(r.iter()).map(|(&l_null, &r_null)| l_null || r_null).collect();
if merged.iter().any(|&is_null| is_null) {
Some(merged)
} else {
None
}
}
}
}
fn aggregate_column_array(array: &ColumnArray, op: AggregateOp) -> Result<SqlValue, ExecutorError> {
match array {
ColumnArray::Float64(values, nulls) => {
aggregate_f64_array(values, nulls.as_ref().map(|n| n.as_slice()), op)
}
ColumnArray::Int64(values, nulls) => {
aggregate_i64_array(values, nulls.as_ref().map(|n| n.as_slice()), op)
}
_ => Err(ExecutorError::UnsupportedExpression(
"Non-numeric columns not supported for aggregation".to_string(),
)),
}
}
fn aggregate_f64_array(
values: &[f64],
nulls: Option<&[bool]>,
op: AggregateOp,
) -> Result<SqlValue, ExecutorError> {
let non_null_values: Vec<f64> = if let Some(null_bitmap) = nulls {
values
.iter()
.zip(null_bitmap.iter())
.filter(|(_, &is_null)| !is_null)
.map(|(&v, _)| v)
.collect()
} else {
values.to_vec()
};
if non_null_values.is_empty() {
return Ok(match op {
AggregateOp::Count => SqlValue::Integer(0),
_ => SqlValue::Null,
});
}
match op {
AggregateOp::Sum => {
let sum: f64 = non_null_values.iter().sum();
Ok(SqlValue::Double(sum))
}
AggregateOp::Count => Ok(SqlValue::Integer(non_null_values.len() as i64)),
AggregateOp::Avg => {
let sum: f64 = non_null_values.iter().sum();
let count = non_null_values.len() as f64;
Ok(SqlValue::Double(sum / count))
}
AggregateOp::Min => {
let min = non_null_values.iter().cloned().fold(f64::INFINITY, f64::min);
Ok(SqlValue::Double(min))
}
AggregateOp::Max => {
let max = non_null_values.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
Ok(SqlValue::Double(max))
}
}
}
fn aggregate_i64_array(
values: &[i64],
nulls: Option<&[bool]>,
op: AggregateOp,
) -> Result<SqlValue, ExecutorError> {
let non_null_values: Vec<i64> = if let Some(null_bitmap) = nulls {
values
.iter()
.zip(null_bitmap.iter())
.filter(|(_, &is_null)| !is_null)
.map(|(&v, _)| v)
.collect()
} else {
values.to_vec()
};
if non_null_values.is_empty() {
return Ok(match op {
AggregateOp::Count => SqlValue::Integer(0),
_ => SqlValue::Null,
});
}
match op {
AggregateOp::Sum => {
let sum: i64 = non_null_values.iter().sum();
Ok(SqlValue::Integer(sum))
}
AggregateOp::Count => Ok(SqlValue::Integer(non_null_values.len() as i64)),
AggregateOp::Avg => {
let sum: i64 = non_null_values.iter().sum();
let count = non_null_values.len() as f64;
Ok(SqlValue::Double(sum as f64 / count))
}
AggregateOp::Min => {
let min = *non_null_values.iter().min().unwrap();
Ok(SqlValue::Integer(min))
}
AggregateOp::Max => {
let max = *non_null_values.iter().max().unwrap();
Ok(SqlValue::Integer(max))
}
}
}
pub fn evaluate_expression_to_column(
batch: &ColumnarBatch,
expr: &Expression,
schema: &CombinedSchema,
) -> Result<ColumnArray, ExecutorError> {
evaluate_batch_expression(batch, expr, schema)
}
pub fn evaluate_expression_with_cached_column(
batch: &ColumnarBatch,
expr: &Expression,
cached_col_idx: usize,
schema: &CombinedSchema,
) -> Result<ColumnArray, ExecutorError> {
let cached_col =
batch.column(cached_col_idx).ok_or_else(|| ExecutorError::ColumnarColumnNotFound {
column_index: cached_col_idx,
batch_columns: batch.column_count(),
})?;
let expr_col = evaluate_batch_expression(batch, expr, schema)?;
apply_binary_op_to_columns(cached_col, &expr_col, &vibesql_ast::BinaryOperator::Multiply)
}