1use sqlparser::ast::{self, Query, SetExpr};
7
8use crate::error::{Result, SqlError};
9use crate::functions::registry::FunctionRegistry;
10use crate::reserved::check_ast_identifier;
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 = check_ast_identifier(&cte.alias.name)?;
39 let declared_columns: Vec<String> = cte
40 .alias
41 .columns
42 .iter()
43 .map(|column| check_ast_identifier(&column.name))
44 .collect::<Result<_>>()?;
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_relation_identifiers(left)?;
73 validate_relation_identifiers(right)?;
74
75 validate_self_ref_count(right, &cte_name)?;
77
78 let distinct = !matches!(set_quantifier, ast::SetQuantifier::All);
79
80 let SetExpr::Select(anchor_select) = left.as_ref() else {
84 return plan_recursive_value(left, right, &cte_name, &declared_columns, distinct);
85 };
86 if anchor_select.from.is_empty() {
87 return plan_recursive_value(left, right, &cte_name, &declared_columns, distinct);
88 }
89
90 let base = plan_cte_branch(left, catalog, functions, temporal)?;
91 let collection = extract_collection(&base);
92 if collection.is_empty() {
93 return Err(SqlError::Unsupported {
94 detail: "collection-backed recursive CTE anchor did not produce a relation".into(),
95 });
96 }
97 plan_recursive_scan_from_parts(
98 &cte_name,
99 &base,
100 &RecursiveParts {
101 left,
102 right,
103 declared_columns: &declared_columns,
104 distinct,
105 },
106 catalog,
107 functions,
108 temporal,
109 )
110}
111
112fn validate_relation_identifiers(expr: &SetExpr) -> Result<()> {
113 match expr {
114 SetExpr::Select(select) => {
115 for from in &select.from {
116 validate_table_factor(&from.relation)?;
117 for join in &from.joins {
118 validate_table_factor(&join.relation)?;
119 }
120 }
121 Ok(())
122 }
123 SetExpr::SetOperation { left, right, .. } => {
124 validate_relation_identifiers(left)?;
125 validate_relation_identifiers(right)
126 }
127 SetExpr::Query(query) => validate_query_identifiers(query),
128 _ => Ok(()),
129 }
130}
131
132fn validate_query_identifiers(query: &Query) -> Result<()> {
133 if let Some(with) = &query.with {
134 for cte in &with.cte_tables {
135 check_ast_identifier(&cte.alias.name)?;
136 for column in &cte.alias.columns {
137 check_ast_identifier(&column.name)?;
138 }
139 validate_query_identifiers(&cte.query)?;
140 }
141 }
142 validate_relation_identifiers(&query.body)
143}
144
145fn validate_table_factor(factor: &ast::TableFactor) -> Result<()> {
146 match factor {
147 ast::TableFactor::Table { .. } => {
148 crate::parser::normalize::table_name_from_factor(factor)?;
149 Ok(())
150 }
151 ast::TableFactor::Derived {
152 subquery, alias, ..
153 } => {
154 if let Some(alias) = alias {
155 crate::reserved::check_ast_identifier(&alias.name)?;
156 }
157 validate_query_identifiers(subquery)
158 }
159 _ => Ok(()),
160 }
161}
162
163struct RecursiveParts<'a> {
166 left: &'a SetExpr,
167 right: &'a SetExpr,
168 declared_columns: &'a [String],
169 distinct: bool,
170}
171
172fn plan_recursive_scan_from_parts(
173 cte_name: &str,
174 base: &SqlPlan,
175 parts: &RecursiveParts<'_>,
176 catalog: &dyn SqlCatalog,
177 functions: &FunctionRegistry,
178 temporal: crate::TemporalScope,
179) -> Result<SqlPlan> {
180 let RecursiveParts {
181 left,
182 right,
183 declared_columns,
184 distinct,
185 } = parts;
186 let collection = extract_collection(base);
187
188 if !declared_columns.is_empty() {
190 let anchor_cols = count_select_cols(left);
191 if anchor_cols != 0 && anchor_cols != declared_columns.len() {
192 return Err(SqlError::RecursiveColumnMismatch {
193 cte_name: cte_name.to_owned(),
194 anchor_cols,
195 declared_cols: declared_columns.len(),
196 });
197 }
198 }
199
200 let (recursive_filters, join_link) =
205 super::join_link::extract_recursive_info(right, cte_name, catalog, functions, temporal)?;
206
207 let projection = match base {
210 SqlPlan::Scan { projection, .. } | SqlPlan::Join { projection, .. } => projection.clone(),
211 _ => Vec::new(),
212 };
213
214 Ok(SqlPlan::RecursiveScan(RecursiveScanPlan {
215 collection,
216 base_filters: extract_filters(base),
217 recursive_filters,
218 join_link,
219 max_iterations: DEFAULT_MAX_RECURSION_DEPTH,
220 distinct: *distinct,
221 limit: usize::MAX,
225 projection,
226 }))
227}
228
229pub(super) fn plan_cte_branch(
230 expr: &SetExpr,
231 catalog: &dyn SqlCatalog,
232 functions: &FunctionRegistry,
233 temporal: crate::TemporalScope,
234) -> Result<SqlPlan> {
235 match expr {
236 SetExpr::Select(select) => {
237 let query = Query {
238 with: None,
239 body: Box::new(SetExpr::Select(select.clone())),
240 order_by: None,
241 limit_clause: None,
242 fetch: None,
243 locks: Vec::new(),
244 for_clause: None,
245 settings: None,
246 format_clause: None,
247 pipe_operators: Vec::new(),
248 };
249 crate::planner::select::plan_query(&query, catalog, functions, temporal)
250 }
251 _ => Err(SqlError::Unsupported {
252 detail: "CTE branch must be SELECT".into(),
253 }),
254 }
255}
256
257pub(super) fn extract_collection(plan: &SqlPlan) -> String {
258 match plan {
259 SqlPlan::Scan { collection, .. } => collection.clone(),
260 _ => String::new(),
261 }
262}
263
264pub(super) fn extract_filters(plan: &SqlPlan) -> Vec<Filter> {
265 match plan {
266 SqlPlan::Scan { filters, .. } => filters.clone(),
267 _ => Vec::new(),
268 }
269}