use std::collections::HashMap;
use vibesql_ast::Expression;
use super::{
selectivity::order_predicates_by_selectivity,
where_pushdown::{decompose_where_clause, PredicateDecomposition},
};
use crate::schema::CombinedSchema;
#[derive(Debug, Clone)]
pub struct PredicatePlan {
decomposition: PredicateDecomposition,
}
#[allow(dead_code)]
impl PredicatePlan {
pub fn empty() -> Self {
Self { decomposition: PredicateDecomposition::empty() }
}
pub fn from_where_clause(
where_expr: Option<&Expression>,
schema: &CombinedSchema,
) -> Result<Self, String> {
let decomposition = decompose_where_clause(where_expr, schema)?;
Ok(Self { decomposition })
}
pub fn get_table_filters(&self, table_name: &str) -> &[Expression] {
let normalized = table_name.to_lowercase();
self.decomposition
.table_local_predicates
.get(&normalized)
.map(|v| v.as_slice())
.unwrap_or(&[])
}
pub fn get_table_filters_ordered(
&self,
table_name: &str,
stats: Option<&vibesql_storage::statistics::TableStatistics>,
) -> Vec<Expression> {
let predicates = self.get_table_filters(table_name);
if predicates.is_empty() {
return Vec::new();
}
if let Some(table_stats) = stats {
order_predicates_by_selectivity(predicates.to_vec(), table_stats)
} else {
predicates.to_vec()
}
}
pub fn has_table_filters(&self, table_name: &str) -> bool {
let normalized = table_name.to_lowercase();
self.decomposition
.table_local_predicates
.get(&normalized)
.is_some_and(|preds| !preds.is_empty())
}
pub fn get_equijoin_conditions(&self) -> &[(String, String, String, String, Expression)] {
&self.decomposition.equijoin_conditions
}
pub fn get_complex_predicates(&self) -> &[Expression] {
&self.decomposition.complex_predicates
}
pub fn get_all_table_predicates(&self) -> &HashMap<String, Vec<Expression>> {
&self.decomposition.table_local_predicates
}
pub fn is_empty(&self) -> bool {
self.decomposition.is_empty()
}
pub fn is_always_false(&self) -> bool {
self.decomposition.always_false
}
pub fn decomposition(&self) -> &PredicateDecomposition {
&self.decomposition
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_plan() {
let plan = PredicatePlan::empty();
assert!(plan.is_empty());
assert_eq!(plan.get_table_filters("t1").len(), 0);
assert_eq!(plan.get_equijoin_conditions().len(), 0);
assert_eq!(plan.get_complex_predicates().len(), 0);
}
#[test]
fn test_has_table_filters() {
let plan = PredicatePlan::empty();
assert!(!plan.has_table_filters("t1"));
}
}