Skip to content

fix: REPL JOIN hardening — qualifier round-trip, loud no-extension failure, bare-alias sort rejection (#157, #158, #159) - #160

Merged
fupelaqu merged 5 commits into
mainfrom
fix/158-fieldsort-order-by-qualifier
Jul 29, 2026
Merged

fix: REPL JOIN hardening — qualifier round-trip, loud no-extension failure, bare-alias sort rejection (#157, #158, #159)#160
fupelaqu merged 5 commits into
mainfrom
fix/158-fieldsort-order-by-qualifier

Conversation

@fupelaqu

Copy link
Copy Markdown
Contributor

Fixes #157, fixes #158, fixes #159 — the three REPL/JOIN bugs found during the R1 installer E2E verification and the #158 code review, targeted at the 0.20.1 patch line.

#158 — preserve table-alias qualifier in FieldSort SQL rendering

FieldSort.sql was the only clause renderer going through Identifier.identifierName (qualifier-dropping); the REPL JOIN path re-renders the parsed statement via .sql and re-parses it in the arrow JoinPlanner, which rejected the manufactured unqualified column as ambiguous. Now renders via field.sql. FieldSort.name / identifierName / aliasOrName are byte-for-byte untouched (they key the ES-side sort maps).

  • 6 ParserSpec round-trip tests (JOIN qualifier, mixed qualification, UNNEST alias, DISTINCT aggregate, qualifier+NULLS composition, unaliased ranking-window derived name) — each re-parses the rendered SQL
  • Cross-repo integration: arrow JoinExtensionIntegrationSpec template gains a JOIN + ORDER BY <alias>.<field> DESC ordered-rows test; Es6/Es7/Es8/Es9 runners 13/13 green against live ES (arrow-side change ships separately)
  • Accepted (reviewed) behavior change: unaliased window columns over a qualified sort derive a qualified name (row_number_over_order_by_e_salary_desc), consistent with PARTITION BY

#157 — fail loudly on cross-index JOIN without a join-capable extension

CoreDqlExtension (inherited by EnforcedDqlExtension) translates only the first FROM table: without softclient4es-arrow-extensions on the classpath a SELECT … JOIN silently executed as a single-index search (joined columns NULL, orphans kept) — and INSERT … SELECT / CREATE TABLE … AS SELECT wrote that wrong data. Any statement whose (embedded) SELECT has from.enrichmentRequired now fails with a clear 400 pointing at the extensions jar; canHandle additionally claims write-with-JOIN statements (mirroring the arrow JoinExtension) so they cannot fall through to the core DML/DDL executors. UNNEST joins are excluded by construction. With the arrow JoinExtension registered at priority 40 (arrow#125), join statements never reach the guard.

  • 6 new CoreDqlExtensionSpec tests: SELECT JOIN, UNION leg, UNNEST carve-out, INSERT…SELECT, CTAS, canHandle claims

#159 — reject ORDER BY on a bare table alias

ORDER BY e where e aliases a FROM table (or ORDER BY oi on an UNNEST alias) left the alias-split with an empty column name, silently rendering malformed SQL (ORDER BY e. after #158; ORDER BY ASC before it). FieldSort.validate now rejects it with Column name expected after table alias '<alias>', surfaced at Parser.apply.

  • 2 new ParserSpec rejection tests (table alias, UNNEST alias)

Verification

🤖 Generated with Claude Code

fupelaqu and others added 5 commits July 29, 2026 07:36
…158)

FieldSort.sql rendered via identifierName, which drops the tableAlias
re-attached by Identifier.sql — so a parsed JOIN query re-rendered via
SingleSearch.sql lost the ORDER BY qualifier (e.salary -> salary) and the
join planner's re-parse failed with 400 'Ambiguous column'. Render via
field.sql instead; FieldSort.name / identifierName / aliasOrName are
untouched (they key the ES-side sort maps).

Story: _bmad-output/implementation-artifacts/bug-158-repl-join-order-by-qualifier.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- re-parse assertions on all 4 qualifier round-trip tests (pin the
  join-planner re-parse failure mode at unit level)
- qualified sort + NULLS LAST composition round-trip test
- pin unaliased ranking-window derived column name with qualified
  OVER-sort (row_number_over_order_by_e_salary_desc) per review decision

Story: bug-158-repl-join-order-by-qualifier (code review)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ension (#157)

CoreDqlExtension (inherited by EnforcedDqlExtension) translates only the
first FROM table — a cross-index JOIN reaching it silently executed as a
single-index search (joined columns NULL, orphans kept), or worse wrote
that wrong data via INSERT ... SELECT / CREATE TABLE ... AS SELECT.

- guard in execute: any statement whose (embedded) SELECT has
  from.enrichmentRequired now fails with a clear 400 pointing at the
  softclient4es-arrow-extensions jar (Java 11+)
- canHandle additionally claims write-with-JOIN statements (mirroring the
  arrow JoinExtension) so they cannot fall through to the core DML/DDL
  executors; join-less writes keep their existing routing
- UNNEST joins excluded by construction (joinedTables = StandardJoin only)
- 6 new CoreDqlExtensionSpec tests (SELECT, UNION leg, UNNEST carve-out,
  INSERT...SELECT, CTAS, canHandle claims)

With the arrow JoinExtension registered at priority 40 join statements
never reach this guard; without it users get a clear error instead of
wrong results.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… qualifier (#159)

A sort identifier colliding with a FROM alias (ORDER BY e where e aliases
a table, or ORDER BY oi on an UNNEST alias) left GenericIdentifier.update's
alias-split with an empty column name, silently rendering malformed SQL
(ORDER BY e. after #158; ORDER BY  ASC before it).

FieldSort.validate now rejects the empty/dangling column name with
"Column name expected after table alias '<alias>'" — surfaced through
OrderBy.validate at Parser.apply, chaining to the FunctionChain
aggregation-chain validation otherwise. 2 new ParserSpec tests (table
alias, UNNEST alias).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@fupelaqu
fupelaqu merged commit 12e60dd into main Jul 29, 2026
2 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant