Plan a value-generating WITH RECURSIVE CTE (no collection reference). Produces a `SqlPlan::RecursiveValue` that carries the anchor and step expressions as raw SQL text for evaluation in the Data Plane.
(
left: &SetExpr,
right: &SetExpr,
cte_name: &str,
declared_columns: &[String],
distinct: bool,
)
| 158 | /// Produces a `SqlPlan::RecursiveValue` that carries the anchor and step |
| 159 | /// expressions as raw SQL text for evaluation in the Data Plane. |
| 160 | fn plan_recursive_value( |
| 161 | left: &SetExpr, |
| 162 | right: &SetExpr, |
| 163 | cte_name: &str, |
| 164 | declared_columns: &[String], |
| 165 | distinct: bool, |
| 166 | ) -> Result<SqlPlan> { |
| 167 | let init_exprs = extract_select_exprs_as_text(left).ok_or_else(|| SqlError::Parse { |
| 168 | detail: "WITH RECURSIVE anchor must be a SELECT".into(), |
| 169 | })?; |
| 170 | |
| 171 | // Validate column count against declared columns list. |
| 172 | if !declared_columns.is_empty() && init_exprs.len() != declared_columns.len() { |
| 173 | return Err(SqlError::RecursiveColumnMismatch { |
| 174 | cte_name: cte_name.to_owned(), |
| 175 | anchor_cols: init_exprs.len(), |
| 176 | declared_cols: declared_columns.len(), |
| 177 | }); |
| 178 | } |
| 179 | |
| 180 | let (step_exprs, condition) = |
| 181 | extract_step_exprs_and_condition(right).ok_or_else(|| SqlError::Parse { |
| 182 | detail: "WITH RECURSIVE step must be a SELECT".into(), |
| 183 | })?; |
| 184 | |
| 185 | // Infer column names from anchor if not declared. |
| 186 | let columns = if declared_columns.is_empty() { |
| 187 | // Default column names: col0, col1, ... |
| 188 | (0..init_exprs.len()).map(|i| format!("col{i}")).collect() |
| 189 | } else { |
| 190 | declared_columns.to_vec() |
| 191 | }; |
| 192 | |
| 193 | Ok(SqlPlan::RecursiveValue { |
| 194 | cte_name: cte_name.to_owned(), |
| 195 | columns, |
| 196 | init_exprs, |
| 197 | step_exprs, |
| 198 | condition, |
| 199 | max_depth: DEFAULT_MAX_RECURSION_DEPTH, |
| 200 | distinct, |
| 201 | }) |
| 202 | } |
| 203 | |
| 204 | /// Extract SELECT projection items as raw SQL text strings. |
| 205 | fn extract_select_exprs_as_text(expr: &SetExpr) -> Option<Vec<String>> { |
no test coverage detected