use std::collections::HashSet;
use crate::optimizer::where_pushdown::flatten_conjuncts;
use super::{INL_BASE_THRESHOLD, INL_MAX_THRESHOLD, INL_SIZE_RATIO_THRESHOLD};
pub(super) fn should_use_inl_for_semi_join(
left_row_count: usize,
right_from: &vibesql_ast::FromClause,
_condition: &Option<vibesql_ast::Expression>,
database: &vibesql_storage::Database,
) -> bool {
let debug = std::env::var("INL_DECISION_DEBUG").is_ok();
if left_row_count < INL_BASE_THRESHOLD {
if debug {
eprintln!(
"[INL_DECISION] left_rows={} < base_threshold={}, using INL",
left_row_count, INL_BASE_THRESHOLD
);
}
return true;
}
if left_row_count > INL_MAX_THRESHOLD {
if debug {
eprintln!(
"[INL_DECISION] left_rows={} > max_threshold={}, using hash join",
left_row_count, INL_MAX_THRESHOLD
);
}
return false;
}
let right_row_count = match right_from {
vibesql_ast::FromClause::Table { name, .. } => {
database.get_table(name).map(|t| t.row_count()).unwrap_or(0)
}
_ => {
if debug {
eprintln!("[INL_DECISION] Complex right side, using hash join");
}
return false;
}
};
let ratio = if left_row_count > 0 { right_row_count / left_row_count } else { 0 };
let use_inl = ratio >= INL_SIZE_RATIO_THRESHOLD;
if debug {
eprintln!(
"[INL_DECISION] left_rows={}, right_rows={}, ratio={}, threshold={}, decision={}",
left_row_count,
right_row_count,
ratio,
INL_SIZE_RATIO_THRESHOLD,
if use_inl { "INL" } else { "hash" }
);
}
use_inl
}
pub(super) struct EquiJoinInfo {
pub(super) left_col: String,
pub(super) right_col: String,
}
pub(super) fn parse_semi_join_condition(
cond: &vibesql_ast::Expression,
left_result: &super::super::FromResult,
right_table_name: &str,
) -> Option<(EquiJoinInfo, Option<vibesql_ast::Expression>)> {
let conjuncts = flatten_conjuncts(cond);
let left_tables: HashSet<String> =
left_result.schema.table_schemas.keys().map(|s| s.canonical().to_string()).collect();
let right_table_lower = right_table_name.to_lowercase();
let mut equi_join: Option<EquiJoinInfo> = None;
let mut right_only_preds: Vec<vibesql_ast::Expression> = Vec::new();
for pred in conjuncts {
if let vibesql_ast::Expression::BinaryOp {
left,
op: vibesql_ast::BinaryOperator::Equal,
right,
} = &pred
{
if let (
vibesql_ast::Expression::ColumnRef(left_col_id),
vibesql_ast::Expression::ColumnRef(right_col_id),
) = (left.as_ref(), right.as_ref())
{
if left_col_id.schema_canonical().is_some()
|| right_col_id.schema_canonical().is_some()
{
right_only_preds.push(pred);
continue;
}
let left_tbl = left_col_id.table_canonical();
let right_tbl = right_col_id.table_canonical();
let left_col = left_col_id.column_canonical();
let right_col = right_col_id.column_canonical();
let left_tbl_lower = left_tbl.map(|s| s.to_lowercase());
let right_tbl_lower = right_tbl.map(|s| s.to_lowercase());
let left_col_lower = left_col.to_lowercase();
let left_is_left =
left_tbl_lower.as_ref().map(|t| left_tables.contains(t)).unwrap_or(false)
|| left_result.schema.table_schemas.values().any(|(_, schema)| {
schema.columns.iter().any(|c| c.name.to_lowercase() == left_col_lower)
});
let right_is_right =
right_tbl_lower.as_ref().map(|t| t == &right_table_lower).unwrap_or(true);
if left_is_left && right_is_right && equi_join.is_none() {
equi_join = Some(EquiJoinInfo {
left_col: left_col.to_string(),
right_col: right_col.to_string(),
});
continue;
}
let right_is_left =
right_tbl_lower.as_ref().map(|t| left_tables.contains(t)).unwrap_or(false);
let left_is_right =
left_tbl_lower.as_ref().map(|t| t == &right_table_lower).unwrap_or(true);
if right_is_left && left_is_right && equi_join.is_none() {
equi_join = Some(EquiJoinInfo {
left_col: right_col.to_string(),
right_col: left_col.to_string(),
});
continue;
}
}
}
right_only_preds.push(pred);
}
equi_join.map(|ej| {
let filter = crate::optimizer::combine_with_and(right_only_preds);
(ej, filter)
})
}