use super::{table_refs::flatten_conjuncts, *};
#[test]
fn test_flatten_conjuncts_single() {
let expr = vibesql_ast::Expression::Literal(vibesql_types::SqlValue::Boolean(true));
let conjuncts = flatten_conjuncts(&expr);
assert_eq!(conjuncts.len(), 1);
}
#[test]
fn test_flatten_conjuncts_multiple() {
let a = vibesql_ast::Expression::Literal(vibesql_types::SqlValue::Boolean(true));
let b = vibesql_ast::Expression::Literal(vibesql_types::SqlValue::Boolean(true));
let ab = vibesql_ast::Expression::BinaryOp {
op: vibesql_ast::BinaryOperator::And,
left: Box::new(a),
right: Box::new(b),
};
let c = vibesql_ast::Expression::Literal(vibesql_types::SqlValue::Boolean(false));
let abc = vibesql_ast::Expression::BinaryOp {
op: vibesql_ast::BinaryOperator::And,
left: Box::new(ab),
right: Box::new(c),
};
let conjuncts = flatten_conjuncts(&abc);
assert_eq!(conjuncts.len(), 3);
}
#[test]
fn test_decomposition_empty() {
let decomp = PredicateDecomposition::empty();
assert!(decomp.is_empty());
}
#[test]
fn test_rebuild_empty() {
let decomp = PredicateDecomposition::empty();
assert!(decomp.rebuild_where_clause().is_none());
}
#[test]
fn test_combine_with_and() {
assert_eq!(combine_with_and(vec![]), None);
let expr = vibesql_ast::Expression::Literal(vibesql_types::SqlValue::Boolean(true));
assert_eq!(combine_with_and(vec![expr.clone()]), Some(expr));
let exprs = vec![
vibesql_ast::Expression::Literal(vibesql_types::SqlValue::Boolean(true)),
vibesql_ast::Expression::Literal(vibesql_types::SqlValue::Boolean(false)),
];
let result = combine_with_and(exprs);
assert!(result.is_some());
}
#[test]
fn test_or_filter_extraction() {
use vibesql_ast::{BinaryOperator, Expression};
use vibesql_catalog::{ColumnSchema, TableSchema};
use vibesql_types::SqlValue;
let n1_schema = TableSchema::new(
"n1".to_string(),
vec![ColumnSchema::new(
"n_name".to_string(),
vibesql_types::DataType::Varchar { max_length: Some(25) },
false,
)],
);
let n2_schema = TableSchema::new(
"n2".to_string(),
vec![ColumnSchema::new(
"n_name".to_string(),
vibesql_types::DataType::Varchar { max_length: Some(25) },
false,
)],
);
let schema = CombinedSchema::combine(
CombinedSchema::from_table("n1".to_string(), n1_schema),
"n2".to_string(),
n2_schema,
);
let n1_france = Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
"n1", false, "n_name", false,
))),
right: Box::new(Expression::Literal(SqlValue::Varchar(arcstr::ArcStr::from("FRANCE")))),
};
let n2_germany = Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
"n2", false, "n_name", false,
))),
right: Box::new(Expression::Literal(SqlValue::Varchar(arcstr::ArcStr::from("GERMANY")))),
};
let n1_germany = Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
"n1", false, "n_name", false,
))),
right: Box::new(Expression::Literal(SqlValue::Varchar(arcstr::ArcStr::from("GERMANY")))),
};
let n2_france = Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
"n2", false, "n_name", false,
))),
right: Box::new(Expression::Literal(SqlValue::Varchar(arcstr::ArcStr::from("FRANCE")))),
};
let left_branch = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(n1_france),
right: Box::new(n2_germany),
};
let right_branch = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(n1_germany),
right: Box::new(n2_france),
};
let or_predicate = Expression::BinaryOp {
op: BinaryOperator::Or,
left: Box::new(left_branch),
right: Box::new(right_branch),
};
let filters = or_conditions::extract_table_filters_from_or(&or_predicate, &schema);
assert!(filters.is_some(), "Should extract filters from OR predicate");
let filters = filters.unwrap();
assert_eq!(filters.len(), 2, "Should extract 2 table filters (n1 and n2)");
let table_names: HashSet<_> = filters.iter().map(|(t, _)| t.as_str()).collect();
assert!(table_names.contains("n1"), "Should have filter for n1");
assert!(table_names.contains("n2"), "Should have filter for n2");
}
#[test]
fn test_or_filter_extraction_multi_branch() {
use vibesql_ast::{BinaryOperator, Expression};
use vibesql_catalog::{ColumnSchema, TableSchema};
use vibesql_types::SqlValue;
let t1_schema = TableSchema::new(
"t1".to_string(),
vec![ColumnSchema::new("a".to_string(), vibesql_types::DataType::Integer, false)],
);
let t2_schema = TableSchema::new(
"t2".to_string(),
vec![ColumnSchema::new("b".to_string(), vibesql_types::DataType::Integer, false)],
);
let t3_schema = TableSchema::new(
"t3".to_string(),
vec![ColumnSchema::new("c".to_string(), vibesql_types::DataType::Integer, false)],
);
let schema = CombinedSchema::combine(
CombinedSchema::combine(
CombinedSchema::from_table("t1".to_string(), t1_schema),
"t2".to_string(),
t2_schema,
),
"t3".to_string(),
t3_schema,
);
let branch1 = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
"t1", false, "a", 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::qualified(
"t2", false, "b", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(2))),
}),
};
let branch2 = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
"t1", false, "a", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(3))),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
"t2", false, "b", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(4))),
}),
};
let branch3 = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
"t1", false, "a", 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::qualified(
"t2", false, "b", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(6))),
}),
};
let inner_or = Expression::BinaryOp {
op: BinaryOperator::Or,
left: Box::new(branch1),
right: Box::new(branch2),
};
let outer_or = Expression::BinaryOp {
op: BinaryOperator::Or,
left: Box::new(inner_or.clone()),
right: Box::new(branch3),
};
let inner_filters = or_conditions::extract_table_filters_from_or(&inner_or, &schema);
assert!(inner_filters.is_some(), "Should extract filters from inner OR");
let inner_filters = inner_filters.unwrap();
assert_eq!(inner_filters.len(), 2, "Inner OR should extract 2 table filters (t1 and t2)");
let outer_filters = or_conditions::extract_table_filters_from_or(&outer_or, &schema);
assert!(outer_filters.is_none(),
"Outer OR should return None because left branch is a complex OR predicate, not simple AND predicates");
}
#[test]
fn test_or_filter_extraction_nested_or() {
use vibesql_ast::{BinaryOperator, Expression};
use vibesql_catalog::{ColumnSchema, TableSchema};
use vibesql_types::SqlValue;
let t1_schema = TableSchema::new(
"t1".to_string(),
vec![
ColumnSchema::new("a".to_string(), vibesql_types::DataType::Integer, false),
ColumnSchema::new("b".to_string(), vibesql_types::DataType::Integer, false),
],
);
let schema = CombinedSchema::from_table("t1".to_string(), t1_schema);
let left_or = Expression::BinaryOp {
op: BinaryOperator::Or,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
"t1", false, "a", 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::qualified(
"t1", false, "a", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(2))),
}),
};
let left_branch = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(left_or),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
"t1", false, "b", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(10))),
}),
};
let right_or = Expression::BinaryOp {
op: BinaryOperator::Or,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
"t1", false, "a", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(3))),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
"t1", false, "a", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(4))),
}),
};
let right_branch = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(right_or),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
"t1", false, "b", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(20))),
}),
};
let nested_predicate = Expression::BinaryOp {
op: BinaryOperator::Or,
left: Box::new(left_branch),
right: Box::new(right_branch),
};
let filters = or_conditions::extract_table_filters_from_or(&nested_predicate, &schema);
assert!(filters.is_some(), "Should extract some filters from nested OR");
let filters = filters.unwrap();
assert_eq!(filters.len(), 1, "Should extract filter for t1.b");
assert_eq!(filters[0].0, "t1", "Filter should be for table t1");
}
#[test]
fn test_or_filter_extraction_asymmetric() {
use vibesql_ast::{BinaryOperator, Expression};
use vibesql_catalog::{ColumnSchema, TableSchema};
use vibesql_types::SqlValue;
let t1_schema = TableSchema::new(
"t1".to_string(),
vec![ColumnSchema::new("a".to_string(), vibesql_types::DataType::Integer, false)],
);
let t2_schema = TableSchema::new(
"t2".to_string(),
vec![ColumnSchema::new("b".to_string(), vibesql_types::DataType::Integer, false)],
);
let schema = CombinedSchema::combine(
CombinedSchema::from_table("t1".to_string(), t1_schema),
"t2".to_string(),
t2_schema,
);
let left_branch = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
"t1", false, "a", 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::qualified(
"t2", false, "b", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(2))),
}),
};
let right_branch = Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
"t1", false, "a", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(3))),
};
let asymmetric_or = Expression::BinaryOp {
op: BinaryOperator::Or,
left: Box::new(left_branch),
right: Box::new(right_branch),
};
let filters = or_conditions::extract_table_filters_from_or(&asymmetric_or, &schema);
assert!(filters.is_some(), "Should extract filters from asymmetric OR");
let filters = filters.unwrap();
assert_eq!(filters.len(), 1, "Should extract only 1 table filter (for t1)");
assert_eq!(filters[0].0, "t1", "Filter should be for table t1");
let table_names: HashSet<_> = filters.iter().map(|(t, _)| t.as_str()).collect();
assert!(!table_names.contains("t2"), "Should NOT have filter for t2");
}
#[test]
fn test_or_filter_extraction_single_table() {
use vibesql_ast::{BinaryOperator, Expression};
use vibesql_catalog::{ColumnSchema, TableSchema};
use vibesql_types::SqlValue;
let t1_schema = TableSchema::new(
"t1".to_string(),
vec![ColumnSchema::new("a".to_string(), vibesql_types::DataType::Integer, false)],
);
let schema = CombinedSchema::from_table("t1".to_string(), t1_schema);
let single_table_or = Expression::BinaryOp {
op: BinaryOperator::Or,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
"t1", false, "a", 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::qualified(
"t1", false, "a", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(2))),
}),
};
let filters = or_conditions::extract_table_filters_from_or(&single_table_or, &schema);
assert!(filters.is_some(), "Should extract filter from single-table OR");
let filters = filters.unwrap();
assert_eq!(filters.len(), 1, "Should extract 1 table filter");
assert_eq!(filters[0].0, "t1", "Filter should be for table t1");
}
#[test]
fn test_or_filter_extraction_no_common_tables() {
use vibesql_ast::{BinaryOperator, Expression};
use vibesql_catalog::{ColumnSchema, TableSchema};
use vibesql_types::SqlValue;
let t1_schema = TableSchema::new(
"t1".to_string(),
vec![ColumnSchema::new("a".to_string(), vibesql_types::DataType::Integer, false)],
);
let t2_schema = TableSchema::new(
"t2".to_string(),
vec![ColumnSchema::new("b".to_string(), vibesql_types::DataType::Integer, false)],
);
let t3_schema = TableSchema::new(
"t3".to_string(),
vec![ColumnSchema::new("c".to_string(), vibesql_types::DataType::Integer, false)],
);
let t4_schema = TableSchema::new(
"t4".to_string(),
vec![ColumnSchema::new("d".to_string(), vibesql_types::DataType::Integer, false)],
);
let schema = CombinedSchema::combine(
CombinedSchema::combine(
CombinedSchema::combine(
CombinedSchema::from_table("t1".to_string(), t1_schema),
"t2".to_string(),
t2_schema,
),
"t3".to_string(),
t3_schema,
),
"t4".to_string(),
t4_schema,
);
let left_branch = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
"t1", false, "a", 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::qualified(
"t2", false, "b", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(2))),
}),
};
let right_branch = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
"t3", false, "c", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(3))),
}),
right: Box::new(Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
"t4", false, "d", false,
))),
right: Box::new(Expression::Literal(SqlValue::Integer(4))),
}),
};
let no_common_or = Expression::BinaryOp {
op: BinaryOperator::Or,
left: Box::new(left_branch),
right: Box::new(right_branch),
};
let filters = or_conditions::extract_table_filters_from_or(&no_common_or, &schema);
assert!(filters.is_none(), "Should return None when no tables appear in both branches");
}
#[test]
fn test_or_filter_extraction_empty_branches() {
use vibesql_ast::{BinaryOperator, Expression};
use vibesql_catalog::{ColumnSchema, TableSchema};
use vibesql_types::SqlValue;
let t1_schema = TableSchema::new(
"t1".to_string(),
vec![ColumnSchema::new("a".to_string(), vibesql_types::DataType::Integer, false)],
);
let schema = CombinedSchema::from_table("t1".to_string(), t1_schema);
let empty_or = Expression::BinaryOp {
op: BinaryOperator::Or,
left: Box::new(Expression::Literal(SqlValue::Boolean(true))),
right: Box::new(Expression::Literal(SqlValue::Boolean(false))),
};
let filters = or_conditions::extract_table_filters_from_or(&empty_or, &schema);
assert!(filters.is_none(), "Should return None for empty branches with no table references");
}