use vibesql_ast::{BinaryOperator, Expression};
use vibesql_types::SqlValue;
use crate::select::scan::index_scan::selection::expressions_match;
use super::in_list::{extract_index_predicate, where_clause_fully_satisfied_by_index};
use super::{IndexPredicate, RangePredicate};
fn is_expression_match(expr: &Expression, index_expr: &Expression) -> bool {
expressions_match(expr, index_expr)
}
fn extract_range_predicate_for_expression(
expr: &Expression,
index_expr: &Expression,
) -> Option<RangePredicate> {
match expr {
Expression::BinaryOp { left, op, right } => {
match op {
BinaryOperator::Equal => {
if is_expression_match(left, index_expr) {
if let Expression::Literal(value) = right.as_ref() {
if matches!(value, SqlValue::Null) {
return None;
}
return Some(RangePredicate {
start: Some(value.clone()),
end: Some(value.clone()),
inclusive_start: true,
inclusive_end: true,
exclude_nulls: false,
});
}
}
if is_expression_match(right, index_expr) {
if let Expression::Literal(value) = left.as_ref() {
if matches!(value, SqlValue::Null) {
return None;
}
return Some(RangePredicate {
start: Some(value.clone()),
end: Some(value.clone()),
inclusive_start: true,
inclusive_end: true,
exclude_nulls: false,
});
}
}
}
BinaryOperator::GreaterThan
| BinaryOperator::GreaterThanOrEqual
| BinaryOperator::LessThan
| BinaryOperator::LessThanOrEqual => {
if is_expression_match(left, index_expr) {
if let Expression::Literal(value) = right.as_ref() {
if matches!(value, SqlValue::Null) {
return None;
}
return Some(match op {
BinaryOperator::GreaterThan => RangePredicate {
start: Some(value.clone()),
end: None,
inclusive_start: false,
inclusive_end: false,
exclude_nulls: true,
},
BinaryOperator::GreaterThanOrEqual => RangePredicate {
start: Some(value.clone()),
end: None,
inclusive_start: true,
inclusive_end: false,
exclude_nulls: true,
},
BinaryOperator::LessThan => RangePredicate {
start: None,
end: Some(value.clone()),
inclusive_start: false,
inclusive_end: false,
exclude_nulls: true,
},
BinaryOperator::LessThanOrEqual => RangePredicate {
start: None,
end: Some(value.clone()),
inclusive_start: false,
inclusive_end: true,
exclude_nulls: true,
},
_ => unreachable!(),
});
}
}
else if is_expression_match(right, index_expr) {
if let Expression::Literal(value) = left.as_ref() {
if matches!(value, SqlValue::Null) {
return None;
}
return Some(match op {
BinaryOperator::GreaterThan => RangePredicate {
start: None,
end: Some(value.clone()),
inclusive_start: false,
inclusive_end: false,
exclude_nulls: true,
},
BinaryOperator::GreaterThanOrEqual => RangePredicate {
start: None,
end: Some(value.clone()),
inclusive_start: false,
inclusive_end: true,
exclude_nulls: true,
},
BinaryOperator::LessThan => RangePredicate {
start: Some(value.clone()),
end: None,
inclusive_start: false,
inclusive_end: false,
exclude_nulls: true,
},
BinaryOperator::LessThanOrEqual => RangePredicate {
start: Some(value.clone()),
end: None,
inclusive_start: true,
inclusive_end: false,
exclude_nulls: true,
},
_ => unreachable!(),
});
}
}
}
BinaryOperator::And => {
let left_range = extract_range_predicate_for_expression(left, index_expr);
let right_range = extract_range_predicate_for_expression(right, index_expr);
match (left_range, right_range) {
(Some(l), Some(r)) => {
let (merged_start, merged_inclusive_start) = match (&l.start, &r.start)
{
(None, None) => (None, false),
(Some(v), None) => (Some(v.clone()), l.inclusive_start),
(None, Some(v)) => (Some(v.clone()), r.inclusive_start),
(Some(lv), Some(rv)) => {
if lv > rv {
(Some(lv.clone()), l.inclusive_start)
} else if rv > lv {
(Some(rv.clone()), r.inclusive_start)
} else {
(Some(lv.clone()), l.inclusive_start && r.inclusive_start)
}
}
};
let (merged_end, merged_inclusive_end) = match (&l.end, &r.end) {
(None, None) => (None, false),
(Some(v), None) => (Some(v.clone()), l.inclusive_end),
(None, Some(v)) => (Some(v.clone()), r.inclusive_end),
(Some(lv), Some(rv)) => {
if lv < rv {
(Some(lv.clone()), l.inclusive_end)
} else if rv < lv {
(Some(rv.clone()), r.inclusive_end)
} else {
(Some(lv.clone()), l.inclusive_end && r.inclusive_end)
}
}
};
return Some(RangePredicate {
start: merged_start,
end: merged_end,
inclusive_start: merged_inclusive_start,
inclusive_end: merged_inclusive_end,
exclude_nulls: l.exclude_nulls || r.exclude_nulls,
});
}
(Some(l), None) => return Some(l),
(None, Some(r)) => return Some(r),
(None, None) => {}
}
}
_ => {}
}
}
Expression::Between { expr: col_expr, low, high, negated, symmetric } => {
if !negated && is_expression_match(col_expr, index_expr) {
if let (Expression::Literal(low_val), Expression::Literal(high_val)) =
(low.as_ref(), high.as_ref())
{
if matches!(low_val, SqlValue::Null) || matches!(high_val, SqlValue::Null) {
return None;
}
let (effective_low, effective_high) = if *symmetric && low_val > high_val {
(high_val.clone(), low_val.clone())
} else {
(low_val.clone(), high_val.clone())
};
return Some(RangePredicate {
start: Some(effective_low),
end: Some(effective_high),
inclusive_start: true,
inclusive_end: true,
exclude_nulls: true,
});
}
}
}
_ => {}
}
None
}
pub(crate) fn extract_index_predicate_for_expression(
expr: &Expression,
index_expr: &Expression,
) -> Option<IndexPredicate> {
if let Some(range) = extract_range_predicate_for_expression(expr, index_expr) {
return Some(IndexPredicate::Range(range));
}
match expr {
Expression::InList { expr: in_expr, values: value_list, negated } => {
if !negated && is_expression_match(in_expr, index_expr) {
let mut values = Vec::new();
let mut has_null = false;
for item in value_list {
if let Expression::Literal(value) = item {
if matches!(value, SqlValue::Null) {
has_null = true;
}
values.push(value.clone());
} else {
return None;
}
}
if has_null {
return None;
}
if !values.is_empty() {
return Some(IndexPredicate::In(values));
}
}
}
Expression::BinaryOp { left, op: BinaryOperator::And, right } => {
if let Some(pred) = extract_index_predicate_for_expression(left, index_expr) {
return Some(pred);
}
if let Some(pred) = extract_index_predicate_for_expression(right, index_expr) {
return Some(pred);
}
}
_ => {}
}
None
}
pub(crate) fn extract_index_predicate_for_indexed_column(
expr: &Expression,
index_col: &vibesql_ast::IndexColumn,
) -> Option<IndexPredicate> {
match index_col {
vibesql_ast::IndexColumn::Column { column_name, .. } => {
extract_index_predicate(expr, column_name)
}
vibesql_ast::IndexColumn::Expression { expr: index_expr, .. } => {
extract_index_predicate_for_expression(expr, index_expr)
}
}
}
pub(crate) fn where_clause_fully_satisfied_by_expression_index(
where_expr: &Expression,
index_expr: &Expression,
) -> bool {
match where_expr {
Expression::BinaryOp { left, op, right } => match op {
BinaryOperator::Equal
| BinaryOperator::GreaterThan
| BinaryOperator::GreaterThanOrEqual
| BinaryOperator::LessThan
| BinaryOperator::LessThanOrEqual => {
let left_is_expr = is_expression_match(left, index_expr);
let right_is_expr = is_expression_match(right, index_expr);
let left_is_literal = matches!(left.as_ref(), Expression::Literal(_));
let right_is_literal = matches!(right.as_ref(), Expression::Literal(_));
(left_is_expr && right_is_literal) || (left_is_literal && right_is_expr)
}
BinaryOperator::And => {
let left_satisfied =
where_clause_fully_satisfied_by_expression_index(left, index_expr);
let right_satisfied =
where_clause_fully_satisfied_by_expression_index(right, index_expr);
left_satisfied && right_satisfied
}
_ => false,
},
Expression::Between { expr: col_expr, low, high, negated, symmetric } => {
!negated
&& !symmetric
&& is_expression_match(col_expr, index_expr)
&& matches!(low.as_ref(), Expression::Literal(_))
&& matches!(high.as_ref(), Expression::Literal(_))
}
Expression::InList { expr: in_expr, values, negated } => {
!negated
&& is_expression_match(in_expr, index_expr)
&& values.iter().all(|v| matches!(v, Expression::Literal(_)))
}
_ => false,
}
}
pub(crate) fn where_clause_fully_satisfied_by_indexed_column(
where_expr: &Expression,
index_col: &vibesql_ast::IndexColumn,
index_predicate: &Option<IndexPredicate>,
) -> bool {
match index_col {
vibesql_ast::IndexColumn::Column { column_name, .. } => {
if index_predicate.is_some() {
where_clause_fully_satisfied_by_index(where_expr, column_name)
} else {
false
}
}
vibesql_ast::IndexColumn::Expression { expr: index_expr, .. } => {
where_clause_fully_satisfied_by_expression_index(where_expr, index_expr)
}
}
}