Skip to content

The companion-object remedy told 23 clusters to UPDATE an extension none of them had: the pg_extension row now ARRIVES at the fault mapping, and the remedy is the row's rather than an inference's (#3830) - #3839

Merged
erikdarlingdata merged 2 commits into
devfrom
fix/3830-pss-companion-two-branch
Sep 21, 2026
Merged

erikdarlingdata merged 2 commits into
devfrom
fix/3830-pss-companion-two-branch

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Sep 21, 2026 •

Copy link
Copy Markdown
Owner

pg_statement_stats declares pg_stat_statements_info as a companion the extension gains at 1.9, and since #3818 a 42P01 naming it is recorded as a companion fault with one remedy: ALTER EXTENSION pg_stat_statements UPDATE. That remedy was reached by inference — the base object resolved, therefore the extension is present, therefore it is present below 1.9 — and the middle step does not survive the Aurora flavor, where the base object that resolved is aurora_stat_statements(), which is not the extension's and needs no extension at all. The store-side read of pg_extension_availability settled what the 23 clusters #3818 counted actually were: installed_version empty in every database, the extension never created anywhere, the collector productive throughout through the Aurora function. The sentence told them to update something they did not have.

The row arrives instead of being inferred

PostgresDetectionQueryText — the statement every PostgreSQL connect already runs — gains one scalar: pg_stat_statements' extversion in the connected database, NULL when pg_extension has no row for it. No second query, no extra round trip, no grant beyond what the connect already needs. The extension name is a literal because a const cannot interpolate, and a pin compares it against PgStatementStatsCollector's own RequiredPgExtensions declaration so the two cannot drift.

It lands on ServerRuntime as a PgExtensionRowObservation: the row, plus the database it was read in, plus an Observed flag whose default is not observed rather than no row — a SQL Server target, a hand-built runtime and a host that never ran the read all produce default, and those two readings are opposite remedies. It rides on the runtime rather than on CollectorTargetInfo because nothing dispatches on it, and CollectorTargetInfo is the type whose facts the engine-capability sweep has to vary or fix.

PostgresFaultOutcome takes the observation and the flavor as two optional parameters, scopes the observation to the database the fault came from (pg_extension is per database, and a RunsPerDatabase collector faults in databases the connect-time read never saw), and asks the declared companion for a verdict. The verdict is computed at the mapping from the stored row, never stored itself — the same division CollectorMeasurement states, for the same reason.

Four verdicts, four sentences

What the flavor half rests on

PgExtensionDependency.AuroraNativeAlternative declares what the collector reads on Aurora instead of the extension's base object. Nothing else reads it; its only effect is that an absent extension can be called optional. A collector that reads its extension on Aurora too leaves it null and gets the required-install sentence, and a pin asserts the declared name is a surface the generated Aurora query actually calls and the vanilla query does not.

Corrections carried

Three comments in #3824's file set attributed the 1.8 state to those 23 clusters as fact. They now say which state was measured (pg_extension_availability: no row) and which was assumed. Below-1.9 remains a real state with a real remedy; it was not theirs, and the live rig that proves the gate holds at 1.8 proves it for one of the two absences rather than for the fleet.

Pins

PostgresFaultOutcomeTests asserts each of the four sentences as a whole string, not as fragments: the defect was a sentence whose clauses were individually plausible and wrong together. The fallback is asserted as equal to the no-argument call, so "the hedge is #3824's text" is pinned rather than commented. PgStatementStatsFlavorTests pins the declaration, the verdict over a fake row set (no row, 1.8, 1.9, 1.10, blank, unrankable), the per-database guard, and the version comparison on the pairs a string compare gets wrong. A source pin covers the wiring — read, carried, passed — because a revert that dropped the argument would compile, run, and go back to one remedy for three states with every sentence pin still green.

One pin repaired, not worked around

#2228's tripwire pin asserted EndsWith("connected_database") on both detection queries while its own doc said what it was protecting: the readers index by number, so a column inserted ahead of it shifts five other fields onto the wrong values. "Last in the list" is not that claim — it forbids appending, which cannot shift anything, and it would have passed a column inserted before connected_database with the reader updated to match. It now parses each select list into its trailing aliases and reads the ordinal that method's own body passes to IsDBNull, and compares the two; neither number is written down. A control on an arranged input asserts the parse counts columns rather than commas, since both queries carry a comma inside a function call and a comma inside a comment, in both comment spellings. Verified red-first by inserting a column ahead of connected_database.

No migration rung. No collector query changed. Lite has no PostgreSQL connect path and maps no PostgreSQL fault text, so there is no twin to keep in step.

Closes #3830

…one of them had: the pg_extension row now ARRIVES at the fault mapping, and the remedy is the row's rather than an inference's (#3830)

#3818 gave a missing companion object one remedy, ALTER EXTENSION pg_stat_statements UPDATE,
and reached it by inference: the base object resolved, therefore the extension is present,
therefore it is present below 1.9. The middle step does not survive the Aurora flavor. There the
base object that resolved is aurora_stat_statements(), which is not the extension's and needs
none, so "it resolved" licenses nothing about pg_extension - and the store-side read of
pg_extension_availability settled what the 23 clusters actually were: installed_version EMPTY in
every database, the extension never created anywhere, the collector productive throughout. They
were told to update something they did not have.

The state now arrives instead. The PostgreSQL detection query - one statement already being made
at every connect - gains one scalar for pg_stat_statements' extversion, NULL when pg_extension has
no row. That lands on ServerRuntime as a PgExtensionRowObservation: the row, with the database it
was read in, and a default that means NOT OBSERVED rather than no row. The fault mapping takes it
as a parameter beside the flavor, scopes it to the database the fault came from (pg_extension is
per database) and asks the declared companion for a verdict.

Four verdicts, four sentences. No row on Aurora: nothing to alter, the collector not waiting on
anything, CREATE EXTENSION optional and what it buys named. No row on vanilla: CREATE EXTENSION,
with the preload sentence back because the module has to be loaded first. Below 1.9: #3818's
remedy, with the version read out of the catalog rather than guessed. At or above: no update is
owed and it would change nothing - 1.10 is the case that decides this, since an ordinal string
compare ranks it below 1.9 and would send a fleet two releases ahead back to run the update twice.
Nothing observed, or a version nothing can rank: #3818's two-possibilities sentence, byte for
byte, which is all the fault alone supports.

The flavor half is declared, not guessed: PgExtensionDependency.AuroraNativeAlternative says what
the collector reads on Aurora instead of the extension's base object, and a collector that leaves
it null gets the required-install sentence.

The three comments attributing the 1.8 state to those 23 clusters now say which state was
measured and which was assumed. No rung; no collector query changed.
…reader's ordinal": both numbers are now derived and compared, and appending a column is allowed again (#3830)

#2228's tripwire pin asserted EndsWith("connected_database") on both detection queries, with a
doc paragraph saying what it was actually protecting: the readers index by number, so a column
inserted ahead of it shifts five other fields onto the wrong values. "Last in the list" is not
that claim. It forbids APPENDING, which cannot shift anything - this change appended one scalar
and the pin went red - and it would have passed a column inserted BEFORE connected_database with
the reader updated to match, which is the shift it exists to catch.

The pin now parses each select list into its trailing aliases and reads the ordinal that method's
own body passes to IsDBNull, and compares the two. Neither number is written down. The parse
handles the two shapes both queries carry - a comma inside a function call and a comma inside a
comment, in both comment spellings - and a control on an arranged input asserts it counts columns
rather than commas, because a splitter that got either wrong would compare two meaningless
numbers and pass.

Verified red-first by inserting a column ahead of connected_database in the PostgreSQL query: the
PostgreSQL half fails, the SQL Server half stays green.
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