use vibesql_ast::{BinaryOperator, Expression};
use vibesql_types::SqlValue;
use super::range::extract_range_predicate;
use super::*;
#[test]
fn test_extract_range_predicate_greater_than() {
let expr = Expression::BinaryOp {
op: BinaryOperator::GreaterThan,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple("col0", false))),
right: Box::new(Expression::Literal(SqlValue::Integer(60))),
};
let range = extract_range_predicate(&expr, "col0").unwrap();
assert_eq!(range.start, Some(SqlValue::Integer(60)));
assert_eq!(range.end, None);
assert!(!range.inclusive_start);
}
#[test]
fn test_extract_range_predicate_less_than_or_equal() {
let expr = Expression::BinaryOp {
op: BinaryOperator::LessThanOrEqual,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple("col0", false))),
right: Box::new(Expression::Literal(SqlValue::Integer(43))),
};
let range = extract_range_predicate(&expr, "col0").unwrap();
assert_eq!(range.start, None);
assert_eq!(range.end, Some(SqlValue::Integer(43)));
assert!(range.inclusive_end);
}
#[test]
fn test_extract_range_predicate_between() {
let expr = Expression::Between {
expr: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple("col0", false))),
low: Box::new(Expression::Literal(SqlValue::Integer(10))),
high: Box::new(Expression::Literal(SqlValue::Integer(20))),
negated: false,
symmetric: false,
};
let range = extract_range_predicate(&expr, "col0").unwrap();
assert_eq!(range.start, Some(SqlValue::Integer(10)));
assert_eq!(range.end, Some(SqlValue::Integer(20)));
assert!(range.inclusive_start);
assert!(range.inclusive_end);
}
#[test]
fn test_extract_range_predicate_combined_and() {
let expr = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::GreaterThan,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"col0", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(10))),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::LessThan,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"col0", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(20))),
}),
};
let range = extract_range_predicate(&expr, "col0").unwrap();
assert_eq!(range.start, Some(SqlValue::Integer(10)));
assert_eq!(range.end, Some(SqlValue::Integer(20)));
assert!(!range.inclusive_start);
assert!(!range.inclusive_end);
}
#[test]
fn test_extract_range_predicate_flipped_comparison() {
let expr = Expression::BinaryOp {
op: BinaryOperator::LessThan,
left: Box::new(Expression::Literal(SqlValue::Integer(60))),
right: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"col0", false,
))),
};
let range = extract_range_predicate(&expr, "col0").unwrap();
assert_eq!(range.start, Some(SqlValue::Integer(60)));
assert_eq!(range.end, None);
assert!(!range.inclusive_start);
}
#[test]
fn test_where_clause_fully_satisfied_simple_equal() {
let expr = Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple("col0", false))),
right: Box::new(Expression::Literal(SqlValue::Integer(5))),
};
assert!(where_clause_fully_satisfied_by_index(&expr, "col0"));
}
#[test]
fn test_where_clause_fully_satisfied_between() {
let expr = Expression::Between {
expr: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple("col0", false))),
low: Box::new(Expression::Literal(SqlValue::Integer(10))),
high: Box::new(Expression::Literal(SqlValue::Integer(20))),
negated: false,
symmetric: false,
};
assert!(where_clause_fully_satisfied_by_index(&expr, "col0"));
}
#[test]
fn test_where_clause_fully_satisfied_in_list() {
let expr = Expression::InList {
expr: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple("col0", false))),
values: vec![
Expression::Literal(SqlValue::Integer(1)),
Expression::Literal(SqlValue::Integer(2)),
Expression::Literal(SqlValue::Integer(3)),
],
negated: false,
};
assert!(where_clause_fully_satisfied_by_index(&expr, "col0"));
}
#[test]
fn test_where_clause_fully_satisfied_combined_range() {
let expr = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::GreaterThan,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"col0", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(10))),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::LessThan,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"col0", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(20))),
}),
};
assert!(where_clause_fully_satisfied_by_index(&expr, "col0"));
}
#[test]
fn test_where_clause_not_fully_satisfied_multiple_columns() {
let expr = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"col0", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(5))),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"col1", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(10))),
}),
};
assert!(!where_clause_fully_satisfied_by_index(&expr, "col0"));
}
#[test]
fn test_where_clause_not_fully_satisfied_or() {
let expr = Expression::BinaryOp {
op: BinaryOperator::Or,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"col0", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(5))),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"col0", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(10))),
}),
};
assert!(!where_clause_fully_satisfied_by_index(&expr, "col0"));
}
#[test]
fn test_extract_composite_predicates_full_match() {
let expr = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_w_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(1))),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_d_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(2))),
}),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(3))),
}),
};
let columns = vec!["c_w_id", "c_d_id", "c_id"];
let result = extract_composite_equality_predicates(&expr, &columns);
assert!(result.is_some());
let key = result.unwrap();
assert_eq!(key.len(), 3);
assert_eq!(key[0], SqlValue::Integer(1));
assert_eq!(key[1], SqlValue::Integer(2));
assert_eq!(key[2], SqlValue::Integer(3));
}
#[test]
fn test_extract_composite_predicates_partial_match() {
let expr = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_w_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(1))),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_d_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(2))),
}),
};
let columns = vec!["c_w_id", "c_d_id", "c_id"];
let result = extract_composite_equality_predicates(&expr, &columns);
assert!(result.is_none());
}
#[test]
fn test_extract_composite_predicates_case_insensitive() {
let expr = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"C_W_ID", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(1))),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"C_D_ID", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(2))),
}),
};
let columns = vec!["c_w_id", "c_d_id"]; let result = extract_composite_equality_predicates(&expr, &columns);
assert!(result.is_some());
let key = result.unwrap();
assert_eq!(key.len(), 2);
assert_eq!(key[0], SqlValue::Integer(1));
assert_eq!(key[1], SqlValue::Integer(2));
}
#[test]
fn test_extract_composite_predicates_with_string_values() {
let expr = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"department",
false,
))),
right: Box::new(Expression::Literal(SqlValue::Varchar(arcstr::ArcStr::from(
"Engineering",
)))),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"name", false,
))),
right: Box::new(Expression::Literal(SqlValue::Varchar(arcstr::ArcStr::from("Alice")))),
}),
};
let columns = vec!["department", "name"];
let result = extract_composite_equality_predicates(&expr, &columns);
assert!(result.is_some());
let key = result.unwrap();
assert_eq!(key.len(), 2);
assert_eq!(key[0], SqlValue::Varchar(arcstr::ArcStr::from("Engineering")));
assert_eq!(key[1], SqlValue::Varchar(arcstr::ArcStr::from("Alice")));
}
#[test]
fn test_extract_composite_predicates_empty_columns() {
let expr = Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple("col", false))),
right: Box::new(Expression::Literal(SqlValue::Integer(1))),
};
let columns: Vec<&str> = vec![];
let result = extract_composite_equality_predicates(&expr, &columns);
assert!(result.is_none());
}
#[test]
fn test_extract_prefix_predicates_partial_match() {
let expr = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_w_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(1))),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_d_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(2))),
}),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::GreaterThan,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_balance",
false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(100))),
}),
};
let columns = vec!["c_w_id", "c_d_id", "c_id"];
let result = extract_prefix_equality_predicates(&expr, &columns);
assert!(result.is_some());
let prefix = result.unwrap();
assert_eq!(prefix.prefix_key.len(), 2);
assert_eq!(prefix.prefix_key[0], SqlValue::Integer(1));
assert_eq!(prefix.prefix_key[1], SqlValue::Integer(2));
assert!(prefix.covered_columns.contains("C_W_ID"));
assert!(prefix.covered_columns.contains("C_D_ID"));
assert!(!prefix.covered_columns.contains("C_ID"));
}
#[test]
fn test_extract_prefix_predicates_single_column() {
let expr = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_w_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(1))),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::GreaterThan,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_balance",
false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(100))),
}),
};
let columns = vec!["c_w_id", "c_d_id", "c_id"];
let result = extract_prefix_equality_predicates(&expr, &columns);
assert!(result.is_some());
let prefix = result.unwrap();
assert_eq!(prefix.prefix_key.len(), 1);
assert_eq!(prefix.prefix_key[0], SqlValue::Integer(1));
assert!(prefix.covered_columns.contains("C_W_ID"));
}
#[test]
fn test_extract_prefix_predicates_gap_in_columns() {
let expr = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_w_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(1))),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(3))),
}),
};
let columns = vec!["c_w_id", "c_d_id", "c_id"];
let result = extract_prefix_equality_predicates(&expr, &columns);
assert!(result.is_some());
let prefix = result.unwrap();
assert_eq!(prefix.prefix_key.len(), 1); assert_eq!(prefix.prefix_key[0], SqlValue::Integer(1));
}
#[test]
fn test_extract_prefix_predicates_no_first_column() {
let expr = Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_d_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(2))),
};
let columns = vec!["c_w_id", "c_d_id", "c_id"];
let result = extract_prefix_equality_predicates(&expr, &columns);
assert!(result.is_none());
}
#[test]
fn test_extract_prefix_predicates_full_match() {
let expr = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_w_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(1))),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_d_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(2))),
}),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(3))),
}),
};
let columns = vec!["c_w_id", "c_d_id", "c_id"];
let result = extract_prefix_equality_predicates(&expr, &columns);
assert!(result.is_some());
let prefix = result.unwrap();
assert_eq!(prefix.prefix_key.len(), 3);
}
#[test]
fn test_build_residual_where_clause_partial_covered() {
let expr = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_w_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(1))),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_d_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(2))),
}),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::GreaterThan,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_balance",
false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(100))),
}),
};
let mut covered = std::collections::HashSet::new();
covered.insert("C_W_ID".to_string());
covered.insert("C_D_ID".to_string());
let residual = build_residual_where_clause(&expr, &covered);
assert!(residual.is_some());
match residual.unwrap() {
Expression::BinaryOp { left, op: BinaryOperator::GreaterThan, right } => {
match left.as_ref() {
Expression::ColumnRef(col_id) => {
assert_eq!(col_id.column_canonical(), "c_balance");
}
_ => panic!("Expected column reference"),
}
match right.as_ref() {
Expression::Literal(SqlValue::Integer(100)) => {}
_ => panic!("Expected literal 100"),
}
}
_ => panic!("Expected binary op"),
}
}
#[test]
fn test_build_residual_where_clause_all_covered() {
let expr = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_w_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(1))),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_d_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(2))),
}),
};
let mut covered = std::collections::HashSet::new();
covered.insert("C_W_ID".to_string());
covered.insert("C_D_ID".to_string());
let residual = build_residual_where_clause(&expr, &covered);
assert!(residual.is_none()); }
#[test]
fn test_build_residual_where_clause_none_covered() {
let expr = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::GreaterThan,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_balance",
false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(100))),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_credit", false,
))),
right: Box::new(Expression::Literal(SqlValue::Varchar(arcstr::ArcStr::from("BC")))),
}),
};
let covered = std::collections::HashSet::new();
let residual = build_residual_where_clause(&expr, &covered);
assert!(residual.is_some()); }
#[test]
fn test_build_residual_where_clause_multiple_uncovered() {
let expr = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_w_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(1))),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::GreaterThan,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_balance",
false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(100))),
}),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_credit", false,
))),
right: Box::new(Expression::Literal(SqlValue::Varchar(arcstr::ArcStr::from("BC")))),
}),
};
let mut covered = std::collections::HashSet::new();
covered.insert("C_W_ID".to_string());
let residual = build_residual_where_clause(&expr, &covered);
assert!(residual.is_some());
match residual.unwrap() {
Expression::BinaryOp { op: BinaryOperator::And, .. } => {}
_ => panic!("Expected AND expression"),
}
}
#[test]
fn test_extract_composite_predicates_with_in_basic() {
let expr = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::InList {
expr: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_w_id", false,
))),
values: vec![
Expression::Literal(SqlValue::Integer(1)),
Expression::Literal(SqlValue::Integer(2)),
Expression::Literal(SqlValue::Integer(3)),
],
negated: false,
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_d_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(5))),
}),
};
let columns = vec!["c_w_id", "c_d_id"];
let result = extract_composite_predicates_with_in(&expr, &columns);
assert!(result.is_some());
let predicates = result.unwrap();
assert_eq!(predicates.len(), 2);
match &predicates[0] {
CompositePredicateType::In(values) => {
assert_eq!(values.len(), 3);
assert_eq!(values[0], SqlValue::Integer(1));
assert_eq!(values[1], SqlValue::Integer(2));
assert_eq!(values[2], SqlValue::Integer(3));
}
_ => panic!("Expected IN predicate"),
}
match &predicates[1] {
CompositePredicateType::Equality(value) => {
assert_eq!(*value, SqlValue::Integer(5));
}
_ => panic!("Expected Equality predicate"),
}
}
#[test]
fn test_extract_composite_predicates_with_in_reversed() {
let expr = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_d_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(5))),
}),
right: Box::new(Expression::InList {
expr: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_w_id", false,
))),
values: vec![
Expression::Literal(SqlValue::Integer(1)),
Expression::Literal(SqlValue::Integer(2)),
],
negated: false,
}),
};
let columns = vec!["c_w_id", "c_d_id"];
let result = extract_composite_predicates_with_in(&expr, &columns);
assert!(result.is_some());
let predicates = result.unwrap();
assert_eq!(predicates.len(), 2);
match &predicates[0] {
CompositePredicateType::In(values) => {
assert_eq!(values.len(), 2);
}
_ => panic!("Expected IN predicate for c_w_id"),
}
match &predicates[1] {
CompositePredicateType::Equality(value) => {
assert_eq!(*value, SqlValue::Integer(5));
}
_ => panic!("Expected Equality predicate for c_d_id"),
}
}
#[test]
fn test_generate_composite_keys_all_equality() {
let predicates = vec![
CompositePredicateType::Equality(SqlValue::Integer(1)),
CompositePredicateType::Equality(SqlValue::Integer(2)),
CompositePredicateType::Equality(SqlValue::Integer(3)),
];
let keys = generate_composite_keys(&predicates);
assert_eq!(keys.len(), 1);
assert_eq!(keys[0], vec![SqlValue::Integer(1), SqlValue::Integer(2), SqlValue::Integer(3)]);
}
#[test]
fn test_generate_composite_keys_single_in() {
let predicates = vec![
CompositePredicateType::In(vec![
SqlValue::Integer(1),
SqlValue::Integer(2),
SqlValue::Integer(3),
]),
CompositePredicateType::Equality(SqlValue::Integer(5)),
];
let keys = generate_composite_keys(&predicates);
assert_eq!(keys.len(), 3);
assert_eq!(keys[0], vec![SqlValue::Integer(1), SqlValue::Integer(5)]);
assert_eq!(keys[1], vec![SqlValue::Integer(2), SqlValue::Integer(5)]);
assert_eq!(keys[2], vec![SqlValue::Integer(3), SqlValue::Integer(5)]);
}
#[test]
fn test_generate_composite_keys_in_on_second_column() {
let predicates = vec![
CompositePredicateType::Equality(SqlValue::Integer(1)),
CompositePredicateType::In(vec![SqlValue::Integer(2), SqlValue::Integer(3)]),
];
let keys = generate_composite_keys(&predicates);
assert_eq!(keys.len(), 2);
assert_eq!(keys[0], vec![SqlValue::Integer(1), SqlValue::Integer(2)]);
assert_eq!(keys[1], vec![SqlValue::Integer(1), SqlValue::Integer(3)]);
}
#[test]
fn test_generate_composite_keys_multiple_in() {
let predicates = vec![
CompositePredicateType::In(vec![SqlValue::Integer(1), SqlValue::Integer(2)]),
CompositePredicateType::In(vec![SqlValue::Integer(3), SqlValue::Integer(4)]),
];
let keys = generate_composite_keys(&predicates);
assert_eq!(keys.len(), 4);
assert_eq!(keys[0], vec![SqlValue::Integer(1), SqlValue::Integer(3)]);
assert_eq!(keys[1], vec![SqlValue::Integer(1), SqlValue::Integer(4)]);
assert_eq!(keys[2], vec![SqlValue::Integer(2), SqlValue::Integer(3)]);
assert_eq!(keys[3], vec![SqlValue::Integer(2), SqlValue::Integer(4)]);
}
#[test]
fn test_where_clause_satisfied_by_composite_key_equality_only() {
let expr = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_w_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(1))),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_d_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(2))),
}),
};
let columns = vec!["c_w_id", "c_d_id"];
assert!(where_clause_fully_satisfied_by_composite_key(&expr, &columns));
}
#[test]
fn test_where_clause_satisfied_by_composite_key_with_in() {
let expr = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::InList {
expr: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_w_id", false,
))),
values: vec![
Expression::Literal(SqlValue::Integer(1)),
Expression::Literal(SqlValue::Integer(2)),
Expression::Literal(SqlValue::Integer(3)),
],
negated: false,
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_d_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(5))),
}),
};
let columns = vec!["c_w_id", "c_d_id"];
assert!(where_clause_fully_satisfied_by_composite_key(&expr, &columns));
}
#[test]
fn test_where_clause_not_satisfied_extra_predicate() {
let expr = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_w_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(1))),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_d_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(2))),
}),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"extra_col",
false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(3))),
}),
};
let columns = vec!["c_w_id", "c_d_id"];
assert!(!where_clause_fully_satisfied_by_composite_key(&expr, &columns));
}
#[test]
fn test_where_clause_not_satisfied_negated_in() {
let expr = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::InList {
expr: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_w_id", false,
))),
values: vec![
Expression::Literal(SqlValue::Integer(1)),
Expression::Literal(SqlValue::Integer(2)),
Expression::Literal(SqlValue::Integer(3)),
],
negated: true, }),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_d_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(5))),
}),
};
let columns = vec!["c_w_id", "c_d_id"];
assert!(!where_clause_fully_satisfied_by_composite_key(&expr, &columns));
}
#[test]
fn test_composite_key_satisfaction_full_match() {
let expr = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_w_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(1))),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_d_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(2))),
}),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(3))),
}),
};
let columns = vec!["c_w_id", "c_d_id", "c_id"];
assert!(where_clause_fully_satisfied_by_composite_key(&expr, &columns));
}
#[test]
fn test_composite_key_satisfaction_missing_predicate() {
let expr = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_w_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(1))),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_d_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(2))),
}),
};
let columns = vec!["c_w_id", "c_d_id", "c_id"];
assert!(!where_clause_fully_satisfied_by_composite_key(&expr, &columns));
}
#[test]
fn test_composite_key_satisfaction_range_predicate() {
let expr = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_w_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(1))),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::GreaterThan,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_d_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(2))),
}),
};
let columns = vec!["c_w_id", "c_d_id"];
assert!(!where_clause_fully_satisfied_by_composite_key(&expr, &columns));
}
#[test]
fn test_composite_key_satisfaction_or_predicate() {
let expr = Expression::BinaryOp {
op: BinaryOperator::Or,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_w_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(1))),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"c_d_id", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(2))),
}),
};
let columns = vec!["c_w_id", "c_d_id"];
assert!(!where_clause_fully_satisfied_by_composite_key(&expr, &columns));
}
#[test]
fn test_composite_key_satisfaction_single_column() {
let expr = Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple("c_id", false))),
right: Box::new(Expression::Literal(SqlValue::Integer(42))),
};
let columns = vec!["c_id"];
assert!(where_clause_fully_satisfied_by_composite_key(&expr, &columns));
}
#[test]
fn test_composite_key_satisfaction_case_insensitive() {
let expr = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"C_W_ID", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(1))),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"C_D_ID", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(2))),
}),
};
let columns = vec!["c_w_id", "c_d_id"];
assert!(where_clause_fully_satisfied_by_composite_key(&expr, &columns));
}
#[test]
fn test_composite_key_satisfaction_null_value() {
let expr = Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple("c_id", false))),
right: Box::new(Expression::Literal(SqlValue::Null)),
};
let columns = vec!["c_id"];
assert!(!where_clause_fully_satisfied_by_composite_key(&expr, &columns));
}
#[test]
fn test_range_contradiction_nested_with_real_types() {
let inner_contradiction = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::GreaterThanOrEqual,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"col4", false,
))),
right: Box::new(Expression::Literal(SqlValue::Real(20.80))),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::LessThanOrEqual,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"col4", false,
))),
right: Box::new(Expression::Literal(SqlValue::Real(6.25))),
}),
};
let expr = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::LessThan,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"col4", false,
))),
right: Box::new(Expression::Literal(SqlValue::Real(21.13))),
}),
right: Box::new(inner_contradiction),
};
let range = extract_range_predicate(&expr, "col4").unwrap();
assert_eq!(range.start, Some(SqlValue::Real(20.80)));
assert_eq!(range.end, Some(SqlValue::Real(6.25)));
}
#[test]
fn test_range_contradiction_simple() {
let expr = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::GreaterThan,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"col0", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(100))),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::LessThan,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"col0", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(50))),
}),
};
let range = extract_range_predicate(&expr, "col0").unwrap();
assert_eq!(range.start, Some(SqlValue::Integer(100)));
assert_eq!(range.end, Some(SqlValue::Integer(50)));
assert!(!range.inclusive_start); assert!(!range.inclusive_end); }