grammar+db: 3g — RETURNING on INSERT/UPDATE/DELETE (ADR-0033 §5)

Shared RETURNING_CLAUSE (reuses Phase-2 PROJECTION_LIST, now
pub(crate)) as an optional tail on all three SQL DML shapes.
`returning: bool` on the Command variants, set by the ast-builders
and threaded to the worker. run_returning collects the returned rows
as a DataResult (RETURNING mutates + yields in one pass), reusing
resolve_select_column_types for bare-column type recovery; computed
projections stay typeless. DeleteResult gains a `data` field rendered
alongside the cascade summary.

Follow-set fix: `returning` is added to the table-source and
projection bare-alias follow-sets so an INSERT … SELECT row source
stops before RETURNING instead of reading it as a table alias.

Auto-fill × RETURNING: build_sql_insert stops row_source before the
RETURNING token (keeping it preparable for shortid materialisation),
and plan_shortid_autofill re-appends the RETURNING tail so generated
shortids surface in RETURNING *.

Tests (+17): grammar accept on all three; INSERT/UPDATE/DELETE
RETURNING incl. *, aliases, multi-row, type recovery + computed-
typeless; auto-fill × RETURNING (single + multi-row distinct ids);
INSERT…SELECT…RETURNING execution; UPDATE…RETURNING zero-match;
DELETE…RETURNING cascade+rows; app-level render of both. Dev
sql_insert/sql_update/sql_delete entry words still removed in 3j.
1562 pass / 0 fail / 1 ignored. Clippy clean.
This commit is contained in:
claude@clouddev1
2026-05-22 20:44:55 +00:00
parent b935090d7b
commit fd8b74ba5e
12 changed files with 637 additions and 46 deletions
+24 -1
View File
@@ -143,6 +143,11 @@ static EMPTY_NOMATCH: Node = Node::Choice(&[]);
const PROJECTION_FOLLOW_SET: &[&str] = &[
"from", "where", "group", "order", "having", "limit",
"union", "intersect", "except",
// `returning` belongs to an enclosing DML statement
// (`INSERT … SELECT … RETURNING …`, ADR-0033 §5), never to a
// projection item's bare alias — so a no-FROM SELECT row source
// (`select id returning *`) stops before it.
"returning",
];
/// Continuation keywords that may legitimately follow a table
@@ -156,6 +161,10 @@ const TABLE_SOURCE_FOLLOW_SET: &[&str] = &[
"where", "group", "order", "having", "limit",
"union", "intersect", "except",
"inner", "left", "right", "full", "cross", "join", "on",
// `returning` belongs to an enclosing DML statement
// (`INSERT … SELECT … FROM t RETURNING …`, ADR-0033 §5), so the
// SELECT row source must not read it as table `t`'s bare alias.
"returning",
];
fn peek_next_ident_lower(source: &str, pos: usize) -> Option<String> {
@@ -325,12 +334,26 @@ fn projection_item_factory(
static PROJECTION_ITEM: Node = Node::Lookahead(projection_item_factory);
static PROJECTION_LIST: Node = Node::Repeated {
pub(crate) static PROJECTION_LIST: Node = Node::Repeated {
inner: &PROJECTION_ITEM,
separator: Some(&COMMA),
min: 1,
};
/// `RETURNING projection_list` — the optional tail shared by the
/// SQL DML statements (ADR-0033 §5, sub-phase 3g). Reuses the
/// Phase-2 projection list unchanged (`*`, bare/qualified column
/// refs, `expr AS alias`, computed expressions), so a RETURNING
/// projection is parsed, completed and highlighted exactly as a
/// SELECT projection. The worker collects the returned rows as a
/// `DataResult`; result-column playground types are recovered via
/// the same column-origin path SELECT uses (ADR-0032 §12).
pub(crate) static RETURNING_CLAUSE: Node = Node::Seq(RETURNING_CLAUSE_NODES);
static RETURNING_CLAUSE_NODES: &[Node] = &[
Node::Word(Word::keyword("returning")),
Node::Subgrammar(&PROJECTION_LIST),
];
// =================================================================
// DISTINCT / ALL prefix
// =================================================================