use sqlparser::ast::{self, SetExpr};
use crate::error::{Result, SqlError};
use crate::parser::normalize::normalize_ident;
use crate::types::*;
use super::recursive_scan::DEFAULT_MAX_RECURSION_DEPTH;
pub(super) fn plan_recursive_value(
left: &SetExpr,
right: &SetExpr,
cte_name: &str,
declared_columns: &[String],
distinct: bool,
) -> Result<SqlPlan> {
let anchor_items = extract_anchor_items(left).ok_or_else(|| SqlError::Parse {
detail: "WITH RECURSIVE anchor must be a SELECT".into(),
})?;
let init_exprs: Vec<String> = anchor_items.iter().map(|i| i.text.clone()).collect();
if !declared_columns.is_empty() && init_exprs.len() != declared_columns.len() {
return Err(SqlError::RecursiveColumnMismatch {
cte_name: cte_name.to_owned(),
anchor_cols: init_exprs.len(),
declared_cols: declared_columns.len(),
});
}
let (step_exprs, condition) =
extract_step_exprs_and_condition(right).ok_or_else(|| SqlError::Parse {
detail: "WITH RECURSIVE step must be a SELECT".into(),
})?;
let columns = if declared_columns.is_empty() {
anchor_items
.iter()
.enumerate()
.map(|(i, item)| item.name.clone().unwrap_or_else(|| format!("col{i}")))
.collect()
} else {
declared_columns.to_vec()
};
let mut seen: std::collections::HashSet<&str> = std::collections::HashSet::new();
for column in &columns {
if !seen.insert(column.as_str()) {
return Err(SqlError::DuplicateRecursiveColumn {
cte_name: cte_name.to_owned(),
column: column.clone(),
});
}
}
Ok(SqlPlan::RecursiveValue(RecursiveValuePlan {
cte_name: cte_name.to_owned(),
columns,
init_exprs,
step_exprs,
condition,
max_depth: DEFAULT_MAX_RECURSION_DEPTH,
distinct,
}))
}
struct AnchorItem {
text: String,
name: Option<String>,
}
fn extract_anchor_items(expr: &SetExpr) -> Option<Vec<AnchorItem>> {
let select = match expr {
SetExpr::Select(s) => s,
_ => return None,
};
Some(
select
.projection
.iter()
.map(|item| match item {
ast::SelectItem::UnnamedExpr(e) => AnchorItem {
text: format!("{e}"),
name: expr_output_name(e),
},
ast::SelectItem::ExprWithAlias { expr: e, alias } => AnchorItem {
text: format!("{e}"),
name: Some(normalize_ident(alias)),
},
ast::SelectItem::ExprWithAliases { expr: e, .. } => AnchorItem {
text: format!("{e}"),
name: None,
},
ast::SelectItem::Wildcard(_) => AnchorItem {
text: "*".into(),
name: None,
},
ast::SelectItem::QualifiedWildcard(name, _) => AnchorItem {
text: format!("{name}.*"),
name: None,
},
})
.collect(),
)
}
fn expr_output_name(expr: &ast::Expr) -> Option<String> {
match expr {
ast::Expr::Identifier(ident) => Some(normalize_ident(ident)),
ast::Expr::CompoundIdentifier(parts) => parts.last().map(normalize_ident),
_ => None,
}
}
fn extract_step_exprs_and_condition(expr: &SetExpr) -> Option<(Vec<String>, Option<String>)> {
let select = match expr {
SetExpr::Select(s) => s,
_ => return None,
};
let step_exprs = select
.projection
.iter()
.map(|item| match item {
ast::SelectItem::UnnamedExpr(e) => format!("{e}"),
ast::SelectItem::ExprWithAlias { expr: e, .. }
| ast::SelectItem::ExprWithAliases { expr: e, .. } => format!("{e}"),
ast::SelectItem::Wildcard(_) => "*".into(),
ast::SelectItem::QualifiedWildcard(name, _) => format!("{name}.*"),
})
.collect();
let condition = select.selection.as_ref().map(|e| format!("{e}"));
Some((step_exprs, condition))
}