use vibesql_catalog::{ColumnSchema, TableSchema};
use vibesql_types::{DataType, SqlValue};
use crate::{errors::ExecutorError, evaluator::ExpressionEvaluator};
#[test]
fn test_cse_repeated_arithmetic() -> Result<(), ExecutorError> {
let schema = TableSchema::new(
"test".to_string(),
vec![
ColumnSchema::new("a".to_string(), DataType::Integer, false),
ColumnSchema::new("b".to_string(), DataType::Integer, false),
],
);
let row = vibesql_storage::Row::new(vec![SqlValue::Integer(10), SqlValue::Integer(5)]);
let evaluator = ExpressionEvaluator::new(&schema);
let a_plus_b = vibesql_ast::Expression::BinaryOp {
left: Box::new(vibesql_ast::Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"a", false,
))),
op: vibesql_ast::BinaryOperator::Plus,
right: Box::new(vibesql_ast::Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"b", false,
))),
};
let result1 = evaluator.eval(&a_plus_b, &row)?;
let result2 = evaluator.eval(&a_plus_b, &row)?;
let result3 = evaluator.eval(&a_plus_b, &row)?;
assert_eq!(result1, SqlValue::Integer(15));
assert_eq!(result2, SqlValue::Integer(15));
assert_eq!(result3, SqlValue::Integer(15));
Ok(())
}
#[test]
fn test_cse_nested_expressions() -> Result<(), ExecutorError> {
let schema = TableSchema::new(
"test".to_string(),
vec![
ColumnSchema::new("x".to_string(), DataType::Integer, false),
ColumnSchema::new("y".to_string(), DataType::Integer, false),
],
);
let row = vibesql_storage::Row::new(vec![SqlValue::Integer(3), SqlValue::Integer(4)]);
let evaluator = ExpressionEvaluator::new(&schema);
let x_times_y = vibesql_ast::Expression::BinaryOp {
left: Box::new(vibesql_ast::Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"x", false,
))),
op: vibesql_ast::BinaryOperator::Multiply,
right: Box::new(vibesql_ast::Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"y", false,
))),
};
let result1 = evaluator.eval(&x_times_y, &row)?;
let result2 = evaluator.eval(&x_times_y, &row)?;
assert_eq!(result1, SqlValue::Integer(12));
assert_eq!(result2, SqlValue::Integer(12));
Ok(())
}
#[test]
fn test_cse_case_expression() -> Result<(), ExecutorError> {
let schema = TableSchema::new(
"test".to_string(),
vec![
ColumnSchema::new("x".to_string(), DataType::Integer, false),
ColumnSchema::new("y".to_string(), DataType::Integer, false),
],
);
let row = vibesql_storage::Row::new(vec![SqlValue::Integer(3), SqlValue::Integer(4)]);
let evaluator = ExpressionEvaluator::new(&schema);
let x_times_y = || vibesql_ast::Expression::BinaryOp {
left: Box::new(vibesql_ast::Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"x", false,
))),
op: vibesql_ast::BinaryOperator::Multiply,
right: Box::new(vibesql_ast::Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"y", false,
))),
};
let case_expr = vibesql_ast::Expression::Case {
operand: None,
when_clauses: vec![vibesql_ast::CaseWhen {
conditions: vec![vibesql_ast::Expression::BinaryOp {
left: Box::new(x_times_y()),
op: vibesql_ast::BinaryOperator::GreaterThan,
right: Box::new(vibesql_ast::Expression::Literal(SqlValue::Integer(10))),
}],
result: vibesql_ast::Expression::BinaryOp {
left: Box::new(x_times_y()),
op: vibesql_ast::BinaryOperator::Multiply,
right: Box::new(vibesql_ast::Expression::Literal(SqlValue::Integer(2))),
},
}],
else_result: Some(Box::new(x_times_y())),
};
let result = evaluator.eval(&case_expr, &row)?;
assert_eq!(result, SqlValue::Integer(24));
Ok(())
}
#[test]
fn test_cse_disabled_via_env() -> Result<(), ExecutorError> {
std::env::set_var("CSE_ENABLED", "false");
let schema = TableSchema::new(
"test".to_string(),
vec![ColumnSchema::new("a".to_string(), DataType::Integer, false)],
);
let row = vibesql_storage::Row::new(vec![SqlValue::Integer(42)]);
let evaluator = ExpressionEvaluator::new(&schema);
let expr =
vibesql_ast::Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple("a", false));
let result = evaluator.eval(&expr, &row)?;
assert_eq!(result, SqlValue::Integer(42));
std::env::remove_var("CSE_ENABLED");
Ok(())
}
#[test]
fn test_cse_non_deterministic_not_cached() -> Result<(), ExecutorError> {
let schema = TableSchema::new("test".to_string(), vec![]);
let row = vibesql_storage::Row::new(vec![]);
let evaluator = ExpressionEvaluator::new(&schema);
let expr = vibesql_ast::Expression::CurrentTimestamp { precision: None };
let result1 = evaluator.eval(&expr, &row)?;
let result2 = evaluator.eval(&expr, &row)?;
assert!(matches!(result1, SqlValue::Timestamp(_)));
assert!(matches!(result2, SqlValue::Timestamp(_)));
Ok(())
}
#[test]
fn test_cse_complex_expression() -> Result<(), ExecutorError> {
let schema = TableSchema::new(
"test".to_string(),
vec![
ColumnSchema::new("a".to_string(), DataType::Integer, false),
ColumnSchema::new("b".to_string(), DataType::Integer, false),
ColumnSchema::new("c".to_string(), DataType::Integer, false),
],
);
let row = vibesql_storage::Row::new(vec![
SqlValue::Integer(2),
SqlValue::Integer(3),
SqlValue::Integer(5),
]);
let evaluator = ExpressionEvaluator::new(&schema);
let a_plus_b = || vibesql_ast::Expression::BinaryOp {
left: Box::new(vibesql_ast::Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"a", false,
))),
op: vibesql_ast::BinaryOperator::Plus,
right: Box::new(vibesql_ast::Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"b", false,
))),
};
let complex_expr = vibesql_ast::Expression::BinaryOp {
left: Box::new(vibesql_ast::Expression::BinaryOp {
left: Box::new(a_plus_b()),
op: vibesql_ast::BinaryOperator::Multiply,
right: Box::new(vibesql_ast::Expression::ColumnRef(
vibesql_ast::ColumnIdentifier::simple("c", false),
)),
}),
op: vibesql_ast::BinaryOperator::Plus,
right: Box::new(vibesql_ast::Expression::BinaryOp {
left: Box::new(a_plus_b()),
op: vibesql_ast::BinaryOperator::Multiply,
right: Box::new(vibesql_ast::Expression::Literal(SqlValue::Integer(2))),
}),
};
let result = evaluator.eval(&complex_expr, &row)?;
assert_eq!(result, SqlValue::Integer(35));
Ok(())
}
#[test]
fn test_cse_with_nulls() -> Result<(), ExecutorError> {
let schema = TableSchema::new(
"test".to_string(),
vec![
ColumnSchema::new("a".to_string(), DataType::Integer, true),
ColumnSchema::new("b".to_string(), DataType::Integer, true),
],
);
let row = vibesql_storage::Row::new(vec![SqlValue::Null, SqlValue::Integer(5)]);
let evaluator = ExpressionEvaluator::new(&schema);
let expr = vibesql_ast::Expression::BinaryOp {
left: Box::new(vibesql_ast::Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"a", false,
))),
op: vibesql_ast::BinaryOperator::Plus,
right: Box::new(vibesql_ast::Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"b", false,
))),
};
let result1 = evaluator.eval(&expr, &row)?;
let result2 = evaluator.eval(&expr, &row)?;
assert_eq!(result1, SqlValue::Null);
assert_eq!(result2, SqlValue::Null);
Ok(())
}
#[test]
fn test_cse_string_expressions() -> Result<(), ExecutorError> {
let schema = TableSchema::new(
"test".to_string(),
vec![
ColumnSchema::new(
"first_name".to_string(),
DataType::Varchar { max_length: Some(50) },
false,
),
ColumnSchema::new(
"last_name".to_string(),
DataType::Varchar { max_length: Some(50) },
false,
),
],
);
let row = vibesql_storage::Row::new(vec![
SqlValue::Varchar(arcstr::ArcStr::from("John")),
SqlValue::Varchar(arcstr::ArcStr::from("Doe")),
]);
let evaluator = ExpressionEvaluator::new(&schema);
let full_name = || vibesql_ast::Expression::BinaryOp {
left: Box::new(vibesql_ast::Expression::BinaryOp {
left: Box::new(vibesql_ast::Expression::ColumnRef(
vibesql_ast::ColumnIdentifier::simple("first_name", false),
)),
op: vibesql_ast::BinaryOperator::Concat,
right: Box::new(vibesql_ast::Expression::Literal(SqlValue::Varchar(
arcstr::ArcStr::from(" "),
))),
}),
op: vibesql_ast::BinaryOperator::Concat,
right: Box::new(vibesql_ast::Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"last_name",
false,
))),
};
let result1 = evaluator.eval(&full_name(), &row)?;
let result2 = evaluator.eval(&full_name(), &row)?;
assert_eq!(result1, SqlValue::Varchar(arcstr::ArcStr::from("John Doe")));
assert_eq!(result2, SqlValue::Varchar(arcstr::ArcStr::from("John Doe")));
Ok(())
}