refact(remote): normalize identifiers through PgIdentifier, fold the baseline - #234
Merged
Conversation
…baseline The column check hand-rolled its identifier unquoting: String(name), then a fixed-point loop undoing however many levels of quote escaping that had applied. PgIdentifier.unquoted() is the accessor for reading a name rather than emitting one, and its doc says so. Core already pairs it with a single un-doubling in sql/foreign-keys.ts, for this exact asymmetry. Undoing exactly one level per side is also more correct than the fixed point, which folded `we""ird` and `we"ird` into the same key and reported the second as covered. Test added for the distinction. StatsBaseline held three structures keyed by the same table key, whose key sets were identical by construction but not by type. One Map of a record deletes a field rather than adding a third. The three Shape Drift checks built the same verdict literal three times, differing in a noun phrase. They now share one builder, and the check order reads as one chain. The module doc claimed every comparison runs in one direction, which the dropped-table check contradicts. Scoped to columns, with the asymmetry named. The call-site comment in remote.ts claimed Shape Drift reads indexes, which it does not. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.

Query Doctor — 6 successful checks
More details via MCP → get_ci_run({ runId: "019ffe0b-cb4c-74f6-a140-e2ca38fe1cab" }) · view run · docs
3 queries read against main on assumed statistics of 10,000,000 rows per table. Sync production stats for costs measured against your real data.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Goal
Keep the statistics-refresh trigger cheap to read, so the next person changing it can see what each comparison is for.
Follow-up cleanup on #233, which added the column check. Quality only, no new capability.
What
Nothing observable changes for ordinary schemas. A snapshot that covers a table's columns still reports no drift, and a migration that adds one still earns a re-dump.
One case changes. Two columns whose names differ only in how many double quotes they hold, such as
we"irdandwe""ird, keyed to the same entry, so the second read as covered when it was not. They are now distinct.How
Read
src/remote/stats-drift.ts.remote.tsis two lines, a signature and a comment.Identifiers normalize through
PgIdentifier.unquotetookString(name), which is the emit path and re-escapes, then undid however many levels of escaping it found.PgIdentifier.unquoted()is the accessor for reading a name rather than emitting one, and its doc comment says to use it instead of unpickingtoString(). It leaves exactly one level ofquote_identdoubling, so it pairs with a singlereplaceAll. Core does the same, for the same asymmetry, insql/foreign-keys.ts. Undoing one level per side is also what keepswe"irdandwe""irdapart, which collapsing to a fixed point cannot.StatsBaselinefolds into one map. It heldtables,reltuplesandcolumns, all keyed by the same table key, all written in the same loop, with identical key sets by construction and nothing enforcing it. It is now oneMap<TableKey, {reltuples, columns}>. Both new?.reads already had theirundefinedguard on the next line.The three Shape Drift checks share a verdict builder. They built the same object three times, differing in a noun phrase.
uncoveredColumnsreturns the list rather than a formatted sentence, which gives it the same shape as the two table scans, and the order reads as one chain.Two comments contradicted the code. The module doc said every comparison runs in one direction, which the dropped-table check does not; it is now scoped to columns, with the asymmetry explained. The
remote.tscall site said Shape Drift reads columns and indexes, and it reads only columns.The
uncoveredColumnsdoc now records that partitioned tables get no column check:SCHEMA_DUMP_SQLfiltersrelispartition = falseandrelkind in ('r','m'), so neither a partitioned parent nor its partitions reach the comparison. Tracked in Query-Doctor/Site#4008.Net 35 lines removed, one test added.
Tests
npm run typecheckclean. Full suite 43 files, 455 tests, all passing.One test added: two names differing only in quote count stay distinct.
Three tests in this area are mutation-checked. Removing the single un-doubling fails the embedded-quote case, reversing the check order fails the table-before-column case, and collapsing quotes to a fixed point fails the new distinctness case.
Out of scope, both needing a
corerelease.tableKeyandunquoteare duplicated in core'soptimizer/synthesize-reltuples.ts, where the copy strips one level and so mis-keys a table namedwe"ird, tracked in Query-Doctor/Site#4009.table()is a third copy of a fixture thatapply-statistics.test.tsandseed-stats-baseline.test.tsalready share, and belongs intest-utils.ts.