use sqlparser::ast::{self, SetExpr};
use crate::error::{Result, SqlError};
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 init_exprs = extract_select_exprs_as_text(left).ok_or_else(|| SqlError::Parse {
detail: "WITH RECURSIVE anchor must be a SELECT".into(),
})?;
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() {
(0..init_exprs.len()).map(|i| format!("col{i}")).collect()
} else {
declared_columns.to_vec()
};
Ok(SqlPlan::RecursiveValue {
cte_name: cte_name.to_owned(),
columns,
init_exprs,
step_exprs,
condition,
max_depth: DEFAULT_MAX_RECURSION_DEPTH,
distinct,
})
}
fn extract_select_exprs_as_text(expr: &SetExpr) -> Option<Vec<String>> {
let select = match expr {
SetExpr::Select(s) => s,
_ => return None,
};
Some(
select
.projection
.iter()
.map(|item| match item {
ast::SelectItem::UnnamedExpr(e) => format!("{e}"),
ast::SelectItem::ExprWithAlias { expr: e, .. } => format!("{e}"),
ast::SelectItem::Wildcard(_) => "*".into(),
ast::SelectItem::QualifiedWildcard(name, _) => format!("{name}.*"),
})
.collect(),
)
}
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, .. } => 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))
}