grammar: admit WITH inside subqueries / CTE bodies (ADR-0032 §10.3)
ADR-0032 §10.3 says cte_bindings lives on the scope frame, with inner subqueries free to declare their own CTEs that shadow outer ones. The grammar didn't actually admit nested WITH inside SQL_SELECT_COMPOUND — a real ADR-vs-implementation gap. Closes the gap by making SQL_SELECT_COMPOUND a Choice between a WITH-prefixed form and a plain form. The naive Optional-prefix approach silently broke the paren-vs-subquery dispatch in sql_expr.rs's PAREN_GROUP: Optional matches 0 bytes, committing the Seq, so SELECT_CORE's NoMatch on `(a + b)` became Failed and the Choice couldn't fall through to or_expr. The Choice-fronted form keeps the fast NoMatch on non-WITH non-SELECT first tokens. Side effect: scalar subquery / IN / EXISTS / derived-table bodies now admit a leading WITH too, which matches standard SQL. Updated two tests that were guarding the old `(WITH …)` rejection behavior. Added one new harvest test exercising nested-WITH inside a CTE body — the harvest's `expand_binding` mechanism already handled the data correctly; the grammar gap was the sole blocker. Test totals: 1414 → 1415 passing (+1 nested-with-in-cte test). Clippy clean.
This commit is contained in:
@@ -2151,6 +2151,36 @@ mod tests {
|
||||
assert_eq!(ctes[0].columns[0].name.as_deref(), Some("id"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cte_harvest_nested_with_in_cte_body() {
|
||||
// Nested WITH inside a CTE body now parses (ADR-0032
|
||||
// §10.3 — inner subqueries may declare their own CTEs).
|
||||
// The outer CTE's body has its own scope and its own
|
||||
// CTE inside it. The outer's `*` projects from its
|
||||
// body's FROM, which references the inner CTE; the
|
||||
// inner CTE's columns flow through `expand_binding`.
|
||||
let schema = schema_users();
|
||||
let ctes = cte_bindings_after_walk_with_schema(
|
||||
"with outer_cte as (with inner_cte as (select id, name from users) select * from inner_cte) select * from outer_cte",
|
||||
&schema,
|
||||
);
|
||||
let outer = ctes
|
||||
.iter()
|
||||
.find(|c| c.name == "outer_cte")
|
||||
.expect("outer_cte binding");
|
||||
assert_eq!(outer.columns.len(), 2);
|
||||
assert_eq!(outer.columns[0].name.as_deref(), Some("id"));
|
||||
assert_eq!(
|
||||
outer.columns[0].type_,
|
||||
Some(crate::dsl::types::Type::Int),
|
||||
);
|
||||
assert_eq!(outer.columns[1].name.as_deref(), Some("name"));
|
||||
assert_eq!(
|
||||
outer.columns[1].type_,
|
||||
Some(crate::dsl::types::Type::Text),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cte_harvest_sibling_b_sees_a_columns() {
|
||||
// Sibling CTEs at the same level. When `b`'s body
|
||||
|
||||
Reference in New Issue
Block a user