1use sqlparser::ast::{self, Query, SetExpr};
7
8use crate::error::{Result, SqlError};
9use crate::functions::registry::FunctionRegistry;
10use crate::parser::normalize::normalize_ident;
11use crate::types::*;
12
13use super::recursive_value::plan_recursive_value;
14use super::validate::{count_select_cols, validate_self_ref_count};
15
16pub const DEFAULT_MAX_RECURSION_DEPTH: usize = 1000;
18
19pub fn plan_recursive_cte(
25 query: &Query,
26 catalog: &dyn SqlCatalog,
27 functions: &FunctionRegistry,
28 temporal: crate::TemporalScope,
29) -> Result<SqlPlan> {
30 let with = query.with.as_ref().ok_or_else(|| SqlError::Parse {
31 detail: "expected WITH clause".into(),
32 })?;
33
34 let cte = with.cte_tables.first().ok_or_else(|| SqlError::Parse {
35 detail: "empty WITH clause".into(),
36 })?;
37
38 let cte_name = normalize_ident(&cte.alias.name);
39 let declared_columns: Vec<String> = cte
40 .alias
41 .columns
42 .iter()
43 .map(|c| normalize_ident(&c.name))
44 .collect();
45
46 let cte_query = &cte.query;
47
48 let (left, right, set_quantifier) = match &*cte_query.body {
50 SetExpr::SetOperation {
51 op: ast::SetOperator::Union,
52 left,
53 right,
54 set_quantifier,
55 } => (left, right, set_quantifier),
56 SetExpr::SetOperation { op, .. } => {
57 return Err(SqlError::InvalidRecursiveSetOp {
58 op: format!("{op}"),
59 });
60 }
61 _ => {
62 return Err(SqlError::InvalidRecursiveSetOp {
63 op: "non-set-operation".into(),
64 });
65 }
66 };
67
68 validate_self_ref_count(right, &cte_name)?;
70
71 let distinct = !matches!(set_quantifier, ast::SetQuantifier::All);
72
73 match plan_cte_branch(left, catalog, functions, temporal) {
76 Ok(base) => {
77 let collection = extract_collection(&base);
78 if collection.is_empty() {
79 plan_recursive_value(left, right, &cte_name, &declared_columns, distinct)
81 } else {
82 plan_recursive_scan_from_parts(
83 &cte_name,
84 &base,
85 &RecursiveParts {
86 left,
87 right,
88 declared_columns: &declared_columns,
89 distinct,
90 },
91 catalog,
92 functions,
93 temporal,
94 )
95 }
96 }
97 Err(_) => {
98 plan_recursive_value(left, right, &cte_name, &declared_columns, distinct)
100 }
101 }
102}
103
104struct RecursiveParts<'a> {
107 left: &'a SetExpr,
108 right: &'a SetExpr,
109 declared_columns: &'a [String],
110 distinct: bool,
111}
112
113fn plan_recursive_scan_from_parts(
114 cte_name: &str,
115 base: &SqlPlan,
116 parts: &RecursiveParts<'_>,
117 catalog: &dyn SqlCatalog,
118 functions: &FunctionRegistry,
119 temporal: crate::TemporalScope,
120) -> Result<SqlPlan> {
121 let RecursiveParts {
122 left,
123 right,
124 declared_columns,
125 distinct,
126 } = parts;
127 let collection = extract_collection(base);
128
129 if !declared_columns.is_empty() {
131 let anchor_cols = count_select_cols(left);
132 if anchor_cols != 0 && anchor_cols != declared_columns.len() {
133 return Err(SqlError::RecursiveColumnMismatch {
134 cte_name: cte_name.to_owned(),
135 anchor_cols,
136 declared_cols: declared_columns.len(),
137 });
138 }
139 }
140
141 let (recursive_filters, join_link) = match plan_cte_branch(right, catalog, functions, temporal)
142 {
143 Ok(plan) => (extract_filters(&plan), None),
144 Err(_) => super::join_link::extract_recursive_info(right, cte_name)?,
145 };
146
147 let projection = match base {
150 SqlPlan::Scan { projection, .. } | SqlPlan::Join { projection, .. } => projection.clone(),
151 _ => Vec::new(),
152 };
153
154 Ok(SqlPlan::RecursiveScan {
155 collection,
156 base_filters: extract_filters(base),
157 recursive_filters,
158 join_link,
159 max_iterations: DEFAULT_MAX_RECURSION_DEPTH,
160 distinct: *distinct,
161 limit: 10000,
162 projection,
163 })
164}
165
166pub(super) fn plan_cte_branch(
167 expr: &SetExpr,
168 catalog: &dyn SqlCatalog,
169 functions: &FunctionRegistry,
170 temporal: crate::TemporalScope,
171) -> Result<SqlPlan> {
172 match expr {
173 SetExpr::Select(select) => {
174 let query = Query {
175 with: None,
176 body: Box::new(SetExpr::Select(select.clone())),
177 order_by: None,
178 limit_clause: None,
179 fetch: None,
180 locks: Vec::new(),
181 for_clause: None,
182 settings: None,
183 format_clause: None,
184 pipe_operators: Vec::new(),
185 };
186 crate::planner::select::plan_query(&query, catalog, functions, temporal)
187 }
188 _ => Err(SqlError::Unsupported {
189 detail: "CTE branch must be SELECT".into(),
190 }),
191 }
192}
193
194pub(super) fn extract_collection(plan: &SqlPlan) -> String {
195 match plan {
196 SqlPlan::Scan { collection, .. } => collection.clone(),
197 _ => String::new(),
198 }
199}
200
201pub(super) fn extract_filters(plan: &SqlPlan) -> Vec<Filter> {
202 match plan {
203 SqlPlan::Scan { filters, .. } => filters.clone(),
204 _ => Vec::new(),
205 }
206}