#![allow(clippy::only_used_in_recursion)]
use std::collections::{HashMap, HashSet};
use vibesql_ast::{Expression, FromClause, JoinType};
#[derive(Debug, Clone)]
pub(super) struct TableRef {
pub(super) name: String,
pub(super) alias: Option<String>,
#[allow(dead_code)]
pub(super) is_cte: bool,
pub(super) is_subquery: bool,
pub(super) subquery: Option<Box<vibesql_ast::SelectStmt>>,
pub(super) column_aliases: Option<Vec<String>>,
}
#[derive(Debug, Clone)]
pub(super) struct JoinConditionWithType {
pub(super) condition: Expression,
pub(super) join_type: JoinType,
}
pub(super) fn flatten_join_tree(from: &FromClause, tables: &mut Vec<TableRef>) {
match from {
FromClause::Table { name, alias, column_aliases, .. } => {
tables.push(TableRef {
name: name.clone(),
alias: alias.clone(),
is_cte: false,
is_subquery: false,
subquery: None,
column_aliases: column_aliases.clone(),
});
}
FromClause::Subquery { query, alias, column_aliases } => {
tables.push(TableRef {
name: alias.clone(),
alias: Some(alias.clone()),
is_cte: false,
is_subquery: true,
subquery: Some(query.clone()),
column_aliases: column_aliases.clone(),
});
}
FromClause::Values { alias, column_aliases, .. } => {
tables.push(TableRef {
name: alias.clone(),
alias: Some(alias.clone()),
is_cte: false,
is_subquery: false,
subquery: None,
column_aliases: column_aliases.clone(),
});
}
FromClause::Join { left, right, .. } => {
flatten_join_tree(left, tables);
flatten_join_tree(right, tables);
}
}
}
pub(super) fn extract_all_conditions(from: &FromClause, conditions: &mut Vec<Expression>) {
match from {
FromClause::Table { .. } | FromClause::Subquery { .. } | FromClause::Values { .. } => {
}
FromClause::Join { left, right, condition, .. } => {
if let Some(cond) = condition {
conditions.push(cond.clone());
}
extract_all_conditions(left, conditions);
extract_all_conditions(right, conditions);
}
}
}
pub(super) fn extract_conditions_with_types(
from: &FromClause,
conditions: &mut Vec<JoinConditionWithType>,
) {
match from {
FromClause::Table { .. } | FromClause::Subquery { .. } | FromClause::Values { .. } => {
}
FromClause::Join { left, right, join_type, condition, .. } => {
if let Some(cond) = condition {
conditions.push(JoinConditionWithType {
condition: cond.clone(),
join_type: join_type.clone(),
});
}
extract_conditions_with_types(left, conditions);
extract_conditions_with_types(right, conditions);
}
}
}
pub(super) const OUTER_REF_MARKER: &str = "__outer_ref__";
pub(super) fn extract_referenced_tables_with_schema(
expr: &Expression,
output: &mut HashSet<String>,
available_tables: &HashSet<String>,
column_to_table: &HashMap<String, String>,
) {
match expr {
Expression::ColumnRef(col_id)
if col_id.schema_canonical().is_none() && col_id.table_canonical().is_some() =>
{
output.insert(col_id.table_canonical().unwrap().to_lowercase());
}
Expression::ColumnRef(col_id)
if col_id.schema_canonical().is_none() && col_id.table_canonical().is_none() =>
{
if let Some(table) = super::utils::resolve_column_with_fallback(
col_id.column_canonical(),
column_to_table,
) {
output.insert(table.to_lowercase());
} else {
output.insert(OUTER_REF_MARKER.to_string());
}
}
Expression::BinaryOp { left, right, .. } => {
extract_referenced_tables_with_schema(left, output, available_tables, column_to_table);
extract_referenced_tables_with_schema(right, output, available_tables, column_to_table);
}
Expression::UnaryOp { expr, .. } => {
extract_referenced_tables_with_schema(expr, output, available_tables, column_to_table);
}
Expression::Function { args, .. } | Expression::AggregateFunction { args, .. } => {
for arg in args {
extract_referenced_tables_with_schema(
arg,
output,
available_tables,
column_to_table,
);
}
}
Expression::InList { expr, values, .. } => {
extract_referenced_tables_with_schema(expr, output, available_tables, column_to_table);
for item in values {
extract_referenced_tables_with_schema(
item,
output,
available_tables,
column_to_table,
);
}
}
Expression::Between { expr, low, high, .. } => {
extract_referenced_tables_with_schema(expr, output, available_tables, column_to_table);
extract_referenced_tables_with_schema(low, output, available_tables, column_to_table);
extract_referenced_tables_with_schema(high, output, available_tables, column_to_table);
}
Expression::Case { operand, when_clauses, else_result } => {
if let Some(op) = operand {
extract_referenced_tables_with_schema(
op,
output,
available_tables,
column_to_table,
);
}
for clause in when_clauses {
for condition in &clause.conditions {
extract_referenced_tables_with_schema(
condition,
output,
available_tables,
column_to_table,
);
}
extract_referenced_tables_with_schema(
&clause.result,
output,
available_tables,
column_to_table,
);
}
if let Some(else_res) = else_result {
extract_referenced_tables_with_schema(
else_res,
output,
available_tables,
column_to_table,
);
}
}
Expression::IsNull { expr, .. } => {
extract_referenced_tables_with_schema(expr, output, available_tables, column_to_table);
}
Expression::Cast { expr, .. } => {
extract_referenced_tables_with_schema(expr, output, available_tables, column_to_table);
}
Expression::In { expr, .. } => {
extract_referenced_tables_with_schema(expr, output, available_tables, column_to_table);
output.insert("__subquery__".to_string());
}
Expression::Position { substring, string, .. } => {
extract_referenced_tables_with_schema(
substring,
output,
available_tables,
column_to_table,
);
extract_referenced_tables_with_schema(
string,
output,
available_tables,
column_to_table,
);
}
Expression::Trim { removal_char, string, .. } => {
if let Some(char_expr) = removal_char {
extract_referenced_tables_with_schema(
char_expr,
output,
available_tables,
column_to_table,
);
}
extract_referenced_tables_with_schema(
string,
output,
available_tables,
column_to_table,
);
}
Expression::Like { expr, pattern, .. } | Expression::Glob { expr, pattern, .. } => {
extract_referenced_tables_with_schema(expr, output, available_tables, column_to_table);
extract_referenced_tables_with_schema(
pattern,
output,
available_tables,
column_to_table,
);
}
Expression::ScalarSubquery(_)
| Expression::Exists { .. }
| Expression::QuantifiedComparison { .. } => {
output.insert("__subquery__".to_string());
}
_ => {}
}
}