Skip to content

fix(parity): close P11 — SELECT alias on the LHS of an IN-subquery#39

Merged
TimelordUK merged 1 commit into
mainfrom
fix/p11-alias-in-in-subquery
Jul 25, 2026
Merged

fix(parity): close P11 — SELECT alias on the LHS of an IN-subquery#39
TimelordUK merged 1 commit into
mainfrom
fix/p11-alias-in-in-subquery

Conversation

@TimelordUK

Copy link
Copy Markdown
Owner

Summary

Closes P11: SELECT symbol, price * 2 AS dbl FROM trades WHERE dbl IN (SELECT price * 2 FROM trades WHERE price > 185) errored with Column 'dbl' not found. Parity board moves 76 → 77 AGREE, 6 → 5 GAP; the corpus case select_alias_in_in_subquery now AGREEs and the --check gate locks it.

Fixing it took two layers — the second was a pre-existing bug the alias case merely exposed.

1. R2 migration of WhereAliasExpander

The transformer was a hand-rolled recursion ending in a _ => (clone, false) catch-all (an R3 pattern), so it silently dropped InSubquery / NotInSubquery / the tuple forms — the same-scope LHS operand was skipped along with the subquery body.

Rewrote expand_expression to intercept only the two nodes that carry a real rule (a bare column that names an alias; a method-call string receiver the walker can't reach) and delegate all structural recursion to walk::map_children. That:

  • retires ~230 lines of hand-rolled arms and the catch-all;
  • picks up the four subquery-LHS variants for free — the walker visits their same-scope operands while treating the nested SelectStatement as an opaque scope boundary, so an alias name reused inside the subquery body is correctly left untouched.

Regression: where_alias_expander::test_expands_alias_on_in_subquery_lhs_not_body.

2. Expression LHS in evaluate_in_list / evaluate_between

Once dbl expands to price * 2, the LHS is a compound expression — and price * 2 IN (SELECT ...) fails even with no alias. The InOperatorLifter normally lifts an expression-LHS IN-list into a computed CTE column, but it only matches InList/NotInList; an IN-subquery's InList is synthesized later by SubqueryExecutor, after the lifter has run, so evaluate_in_list received a raw expression LHS and its extract_column_name errored.

Added RecursiveWhereEvaluator::evaluate_operand_value, which looks up a plain column but evaluates any other expression through the ArithmeticEvaluator — the same delegation evaluate_binary_op already does for its LHS. IN and BETWEEN now both accept a compound LHS. This stands on its own (it also fixes alias-free expr IN (subquery)); the long-term home is the R7 per-row subquery evaluation.

Testing

  • Parity harness: 77 AGREE / 1 DIFFER / 5 GAP, contract holds (83 cases).
  • cargo test --lib: 687 passed, 0 failed (incl. the new expander test).
  • cargo test --test integration: 407 passed, 0 failed.
  • cargo fmt clean.

🤖 Generated with Claude Code

`WHERE dbl IN (SELECT ...)` where `dbl` aliases `price * 2` errored with
"Column 'dbl' not found". Fixing it took two layers; the second was a
pre-existing bug the alias case merely exposed.

1. R2 migration of WhereAliasExpander. Rewrote expand_expression to intercept
   only the two nodes that carry a real rule (a bare column naming an alias; a
   method-call string receiver the walker can't reach) and delegate all
   structural recursion to walk::map_children. That retires the hand-rolled arms
   and the `_ => (clone, false)` catch-all (R3), and picks up the four
   subquery-LHS variants for free: the walker visits their same-scope operands
   while treating the nested SelectStatement as an opaque scope boundary, so an
   alias name reused inside the subquery body is correctly left alone.

2. Expression LHS in evaluate_in_list / evaluate_between. Once the alias expands,
   the LHS is `price * 2` — and `price * 2 IN (SELECT ...)` fails even with no
   alias, because the InOperatorLifter (which lifts an expression-LHS IN-list
   into a computed CTE column) only matches InList/NotInList, while an
   IN-subquery's InList is synthesized later by SubqueryExecutor, after the
   lifter ran. Added RecursiveWhereEvaluator::evaluate_operand_value, which looks
   up a plain column but evaluates any other expression via ArithmeticEvaluator
   — the same delegation evaluate_binary_op already does for its LHS.

Corpus select_alias_in_in_subquery now AGREEs (77 AGREE / 1 DIFFER / 5 GAP).
Regression test: where_alias_expander::test_expands_alias_on_in_subquery_lhs_not_body.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@TimelordUK
TimelordUK merged commit 3400cef into main Jul 25, 2026
8 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

Development

Successfully merging this pull request may close these issues.

1 participant