Skip to content

feat(compare): match changed queries by shape across a hash change (#3367)#177

Merged
veksen merged 1 commit into
mainfrom
feat-shape-match-changed-queries
Jul 14, 2026
Merged

feat(compare): match changed queries by shape across a hash change (#3367)#177
veksen merged 1 commit into
mainfrom
feat-shape-match-changed-queries

Conversation

@veksen

@veksen veksen commented Jul 14, 2026

Copy link
Copy Markdown
Member

What

compareRuns matched the previous run only by exact query hash. Adding a column to a SELECT changes the hash but not the query's shape, so the same call site showed up as one removed + one new query instead of one changed query.

This adds a shape-match fallback: when the exact hash misses, pair the current query with a previous one of the same shape and call site.

Why — Nutcracker dogfood (#3367)

findByUser gained two SELECT columns:

  • removed af6f56c6…SELECT id, share_slug, user_id … FROM design_renders WHERE user_id=$1 ORDER BY created_at DESC LIMIT $2 (staging, postgres.ts:1755)
  • new 96698339… — same query + session_id, source_payload (PR, postgres.ts:1761)

Same call site, reported as removed+new — and that churn is also what made find_regressions noisy.

How

  • Shape key = core's normalizedFingerprint, which strips bare column refs from every SELECT list (top-level, CTEs, subqueries, set-ops) while keeping FROM/WHERE/ORDER/LIMIT/aggregates. So a column-add produces the same key; a WHERE/table/aggregate change does not. Computed on the fly from the query text both runs already carry — no payload/API change.
  • Two-pass matching — reserve every exact-hash match first, then shape-match the leftovers, so a new query can't claim a previous one that a later query matches exactly.
  • Call-site guard — when both queries carry a sqlcommenter file tag, require the same origin, so identically-shaped queries from different sites aren't merged.
  • A shape-matched pair flows through the existing cost logic, so it inherits the regression/improvement delta instead of vanishing into new+removed.

Scope

Analyzer-only. Does not need the edit-stable symbol provenance from #3494 — the file tag is stable in this case; only line numbers moved, and the shape key doesn't depend on them. #3494's symbol axis is still what a cross-commit / drifted-worktree join would need; this isn't that.

Tests

  • query-shape.test.ts — column-add keeps the key; WHERE/table change moves it; unparseable → null; origin-compatibility rules.
  • site-api.test.ts — column-add reads as one changed query (not new+removed); carries the cost delta; different call site not merged; widened variant is new when the original still runs; genuinely different query not matched. makeQuery now gives each hash distinct SQL (real queries can't share text without sharing a hash).

Full suite green (299), typecheck clean.

…3367)

Run comparison keyed only on the exact query hash, so adding a column to a
SELECT — which changes the hash but not the query's shape — showed the same
call site as one `removed` + one `new` query instead of one changed query. Real
dogfood: Nutcracker's `findByUser` gained two columns and read as removed+new,
which also made the find_regressions rows noisy.

Fall back to a shape match when the exact hash misses. The shape key is core's
`normalizedFingerprint`, which strips bare column references from every SELECT
list while keeping FROM/WHERE/ORDER/LIMIT/aggregates — so a column-add pairs
with its previous self and inherits the cost delta, while a genuinely different
query does not. Matching is two-pass (all exact-hash matches reserved before any
shape match) so a new query can't steal a previous one a later query matches
exactly, and requires the same sqlcommenter call site when both are tagged so
identically-shaped queries from different sites aren't merged.

Analyzer-only: the key is computed on the fly from the query text both runs
already carry — no payload/API change, and no dependency on the edit-stable
symbol provenance (#3494), since the file tag is stable here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Query Doctor Analysis

3 queries analyzed

0 regressed · 0 improved · 0 new · 0 removed

2 pre-existing issues

Using assumed statistics (10000000 rows/table). For better results, sync production stats.

More detail → get_ci_run({ runId: "019f5eaf-5f2c-72f1-aeb3-ca08884ba7d1" }) · view run · docs

@veksen veksen merged commit f15a34d into main Jul 14, 2026
6 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