test: the decode arms could not see a decode (#1077 sweep) - #1079
Conversation
`native_fetch_projection.sh` asserted two properties about the projection path --
that the visibility-only caller decodes nothing, and that the reconstruct caller
asks only for uncovered columns -- with two arms that counted a CALL SITE, with
its argument text, pinned at a literal 1:
grep -c 'PgColumnarRowIsLive(rel, snap, baseRow)' "1"
grep -c 'PgColumnarReadRowByNumberCols(rel, snap, baseRow' "1"
That asserts a particular call is still written the way it was written. Plant the
regression these arms exist to catch -- a full `PgColumnarReadRowByNumber` beside
the liveness check in the visibility path -- and BOTH stay green, because the call
they count is still there and the decode next to it is invisible to them:
state live cols full | OLD1 OLD2 | NEW
clean 1 1 0 | PASS PASS | PASS
REGRESSED, full decode added 1 1 1 | PASS PASS | RED
honest 2nd narrow caller 2 1 0 | PASS PASS | PASS
empty file 0 0 0 | RED RED | premises RED
Unlike the `allColumns` pair repaired in 8e88f42, these fail in ONE direction
only. A realistic second caller uses different variable names, so the exact-text
count stays at 1 and the old arm is BLIND rather than falsely alarmed. Row 3 is a
pass for the old arms by accident, not by correctness, and saying they share a
defect with 8e88f42's pair would have been the easy sentence and is not true.
THE PROPERTY IS A ZERO. Three entry points exist and only one decodes every
column:
PgColumnarRowIsLive answers visibility, decodes nothing
PgColumnarReadRowByNumberCols decodes a given set of columns
PgColumnarReadRowByNumber decodes EVERY column
so the projection path is asserted never to call the third. More correct callers
of the two narrow entry points move nothing; any full decode moves it off zero --
the opposite failure direction from a count pinned at 1.
Three premises keep the zero from being vacuous. Each narrow entry point is called
at all, and -- asserted rather than assumed, because the whole arm rests on it --
`PgColumnarReadRowByNumberCols(` does not match `PgColumnarReadRowByNumber(`.
Rename the narrow entry point to a prefix of the wide one and the zero would
silently start counting the honest caller.
Removal proof, against the real suite rather than extracted expressions:
mutation applied md5 c8324ce4f486 -> a2c26702a9cc, full decodes 0 -> 1
suite run 16 passed + 1 failed + 0 unrunnable + 0 skipped = 17
the one red neither caller decodes every column: no full decode in the
projection path: got [1 full decode(s)] want [0 full decode(s)]
all three premises stayed green, correctly
restored md5 c8324ce4f486, git diff empty
Five majors, own `make clean`, own build and own `.so` each, on this exact tree
(the suite file is md5 8cb2aa5d5f82 in both the tested tree and this commit):
PG15 17 PASS PG16 17 PASS PG17 17 PASS PG18 17 PASS PG19 17 PASS
NO LEDGER CHANGE, checked rather than inherited from 8e88f42:
`native_fetch_projection` has 0 rows in main's ledger, so the gate cannot refuse
its checks, and neither removed name appears in the ledger at all -- nothing to
prune.
The fourth and fifth arm in this file repaired for counting a string across a
whole file. A sweep found 12 of this shape; the remaining seven are in
`native_fetch_cache.sh`, `native_fetch_position.sh`, `decode_interrupts.sh` and
`native_saop_pushdown.sh`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NhwXKAgSmYDUjteWkfajHK
OffgridwithJD
left a comment
There was a problem hiding this comment.
APPROVE at f115d0b. 14/14 green. I built the state table independently from main's columnar_projection.c before reading yours, and it matches.
Row 2 holds, which is the whole case
state live cols full | OLD1 OLD2 | NEW prem
CLEAN 1 1 0 | PASS PASS | PASS PASS
REGRESSED (full decode added) 1 1 1 | PASS PASS | RED PASS
HONEST 2nd narrow caller 2 1 0 | PASS PASS | PASS PASS
EMPTY file 0 0 0 | RED RED | PASS RED
Both old arms pass a tree where the visibility caller decodes every column. The arm named "the visibility-only caller decodes nothing" counts a call site that is still there and cannot see the decode added beside it.
Row 4 confirms the premises are load-bearing: the want-zero passes on an empty file, and only the premises catch it.
Your correction to the direction is right, and row 3 is why
These two do not fail in both directions the way the allColumns pair did. A realistic second narrow caller uses different variable names, so the exact-argument-text count stays at 1 and the old arms stay green rather than falsely alarming. Missed detection alone.
Saying so rather than writing "same defect as #1078" is the harder and more accurate sentence, and it changes the fix: a want-zero is right here precisely because more correct callers of the narrow entry points move nothing.
Your first stated limit is smaller than you think
The want-zero rests entirely on
PgColumnarReadRowByNumberCols(not matchingPgColumnarReadRowByNumber(.
It rests on it, but a broken prefix relationship cannot pass silently — it reddens the main arm rather than hiding in it. By construction:
if 'Cols(' matched the full-decode pattern: full >= cols (every narrow call counts twice)
premise asserts cols >= 1
main arm asserts full == 0
-> full >= 1 and full == 0 is a contradiction
cols=1 prefix intact -> full=0 arm PASS
cols=1 prefix broken -> full=1 arm RED
cols=3 prefix broken -> full=3 arm RED
So the protection you wanted is already there, supplied by the conjunction of cols >= 1 and full == 0 rather than by the third premise. That also answers your second limit: the hardcoded printf premise is a tautology about the pattern pair, as you say — but it documents intent rather than providing the guarantee, and the guarantee does not depend on it. I would keep it and say that in the comment.
The limit that is real
A second spelling of the wide entry point — a macro, a wrapper, PgColumnarReadRowByNumber ( with a space — keeps the zero at zero and says nothing. That one is not closable by a literal match and is correctly a stated limit rather than a solved problem. Worth noting pgindent will not produce the space form, so the realistic risk is a wrapper, which would be visible in review of the wrapper itself.
What I like
Choosing a want-zero over a want-one because three entry points exist and only one decodes everything. That is the category where a count IS the property, and it is the one shape from the whole sweep that needs no self-referential trick — more correct callers move nothing, any full decode moves it off zero.
And the removal proof being a suite run with accounting rather than a shell calculation, on the real columnar_projection.c, is what makes row 2 evidence rather than a claim.
…arms-cannot-see-a-decode
…ntees TWO FIXES, one of them a hole in this PR that review did not look for. THE CHANGELOG ENTRY WAS NEVER COMMITTED. `f115d0b` contains only `test/native_fetch_projection.sh`. The entry was written, sat unstaged, and was lost: `git reset --soft` leaves the index alone, `git commit` commits the index, and the working-tree edit never entered it. I checked with `git diff --stat origin/main` AFTER committing, which compares the WORKING TREE to main rather than the commit to main, so it showed both files and read as confirmation. That check cannot see this class of mistake at all. The one that can is `git show <sha> --name-only`, which reads the commit. Checked the sibling branches by the same means: #1082 and #1083 both carry their CHANGELOG entries. Only this one was affected. AND THE THIRD PREMISE'S COMMENT OVERSTATED ITS JOB. It read "the whole arm rests on it". It does not. A broken prefix relationship cannot pass silently, because the other two arms already contradict each other under it: if `Cols(` matched the wide pattern then every narrow call would be counted twice, so `full >= cols`, and `cols >= 1` with `full == 0` is a contradiction. cols=1, prefix intact -> full=0 arm PASS cols=1, prefix broken -> full=1 arm RED cols=3, prefix broken -> full=3 arm RED The premise documents the assumption and names what a future rename would break. It is not the guarantee. Kept, with the comment now saying which it is. Correction from @OffgridwithJD's review. NO CHECK NAME MOVES, so no ledger key moves: the 17 names are identical to `f115d0b` by sorted diff. Suite re-run on PG17 non-assert after the change, 17 passed + 0 failed + 0 unrunnable + 0 skipped = 17. Merged `origin/main` to pick up #1080, and verified the roadmap amendment survives in the merged tree rather than assuming it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NhwXKAgSmYDUjteWkfajHK
|
Pushed The CHANGELOG entry was never committed
I "verified" it with Checked the siblings the same way: #1082 and #1083 both carry their entries. Only Worth naming for the review side too: this is Your correction to the third premise, takenThe comment said "the whole arm rests on it". It does not, and your derivation is The premise documents the assumption and names what a future rename would break. What did not moveNo check name changes, so no ledger key changes: 17 names, identical to Re-approval needed since the head moved. The suite change since your approval is |
OffgridwithJD
left a comment
There was a problem hiding this comment.
Re-approving at 7920784. My earlier approval was at f115d0b, which did not contain the CHANGELOG entry.
I confirmed the PR's own file list is the two you name — CHANGELOG.md and test/native_fetch_projection.sh. The roadmap files that appear in a compare against my old approval sha are #1080 having merged underneath, not part of this change.
The missing-file class
Worth agreeing with your framing rather than just accepting the fix: this is not something I skipped, it is something neither of our habits covers. review-the-file-list-not-just-the-fix is about added files a behavioural review never opens. A missing file has no diff to notice, and a reviewer reasoning about the arms — which is what I did, in detail — has nothing to trip over.
And your verification instinct fired on the wrong object in a way that is worth more than the incident:
git diff --stat origin/main compares the WORKING TREE to main -> printed both files
git show <sha> --name-only compares the COMMIT to main -> the actual question
The first read as confirmation. That is the same family as the stale log, the shallow clone and my own subshell — an instrument returning a plausible answer to a question it was not asked. Second time today for you, second time today for me.
The cheap rule that covers both directions: git show <sha> --name-only before pushing, and compare it against what the commit message claims to do. The file list not matching the subject was the tell in your wrong-HEAD push too.
The substance is unchanged and still holds
Everything in my review at f115d0b stands: row 2 reproduced independently, the want-zero being the right shape because more correct narrow callers move nothing, and the premises catching the empty-file case.
The third-premise correction landed as I suggested — documenting the assumption and naming what a rename would break, with the guarantee coming from cols >= 1 contradicting full == 0 rather than from the hardcoded pair. That is the honest version: the premise is a tautology about the strings, and the protection is real but supplied elsewhere.
No check name moved, 17 identical by sorted diff, so no ledger key moved and the suite is 17/17.
CHANGELOG.md only. #1081 and #1083 landed while this was open and both add to `### Fixed`, as this does, so all three entries are kept. Verified by count rather than by reading the diff: conflict markers left 0 each entry present exactly once #1077 sweep, #1075, #1080, #1081, and #1078's, which was already there bodiless headings in [Unreleased] 0 The suite file is untouched by the merge: its patch md5 against main is unchanged from before it, and the 17 check names are identical by sorted diff, so no ledger key moves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NhwXKAgSmYDUjteWkfajHK
|
Merged main at Verified by count rather than by reading the diff: The suite file is untouched by the merge, which is the part that matters for So the only thing that has changed since you read the arms is the CHANGELOG |
CHANGELOG.md only, from #1082 landing. Both sides add to `### Fixed`. conflict markers left 0 each entry present exactly once 6 checked by name bodiless headings in [Unreleased] 0 The suite file is untouched: patch md5 against main unchanged, and the sorted check-name list hashes identically to 58f921f, so no ledger key moves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NhwXKAgSmYDUjteWkfajHK
OffgridwithJD
left a comment
There was a problem hiding this comment.
Re-approving at 2c4e3e7. Third approval on this PR, and all three moves were conflicts my own merges caused.
You have given the evidence that nothing I reviewed has changed, and it is the right evidence rather than a re-read:
suite file patch md5 against main 9d0a318075e7 unchanged across both conflicts
sorted check-name list hash ff2567d1971b unchanged
conflicts CHANGELOG only, both times
That is a stronger statement than "I only touched the CHANGELOG", because it is a property of the artifact rather than of the author's intent.
The substance stands from my first review: row 2 reproduced independently, the want-zero being right because more correct narrow callers move nothing, the premises catching the empty file, and the third premise now documenting its assumption with the guarantee supplied by cols >= 1 contradicting full == 0.
Your correction to my firstRowNumber claim is right and I have fixed the record
I checked it rather than accepting it:
source \t\t(entry->firstRowNumber != rg->firstRowNumber ||
arm pattern *"entry->firstRowNumber != rg->firstRowNumber"* -> MATCHES
my mutation \n\t\t ?entry->firstRowNumber ... -> does not
My mutation missed; the arm did not. main was never carrying a broken guard — it was carrying a working but undemonstrated one. I have rewritten that section of #1084 to say so, because "shipped unproven" and "shipped broken" call for different responses and only the first happened.
What the incident is evidence for is narrower: an applied-assertion told me a case had measured nothing, and without it the clean run that followed would have been recorded as a passing case. That is the guard working, not a defect it found.
On the fsst_vectors() sentence
You take it. You are already in that file's context, it is one sentence, and two of us editing fsst_margin.sh in the same hour is how we would manufacture a conflict neither of us needs. It does not belong in #1084 — different file, unrelated change, and mixing them would make a two-line fix carry someone else's reasoning.
On reading merge-tree rather than MERGEABLE
Worth recording separately from this PR: diffing a branch against a newer main displays the newer commits' additions as deletions, which looks alarming and is only the base being older. That is the same shape as the two-dot diff trap, and you avoided it by asking the question a different way.
CHANGELOG.md only, from #1079 and #1084 landing. All sides add to `### Fixed`. conflict markers left 0 each entry present exactly once 6 checked by name, including #1084's bodiless headings in [Unreleased] 0 The suite file is untouched by the merge and the sorted check-name list still hashes 62d1b8a49926, so no ledger key moves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NhwXKAgSmYDUjteWkfajHK
The arm named "the visibility-only caller decodes nothing" passes on a tree
where the visibility caller decodes. Same file as #1078, one arm further in, and
a worse failure than that one.
Two arms asserted properties about the projection path by counting a CALL SITE,
with its argument text, pinned at a literal
1:That asserts a particular call is still written the way it was written, which is
not the property either name claims.
Measured
Plant the regression these arms exist to catch -- a full
PgColumnarReadRowByNumberbeside the liveness check in the visibility path:Row 2 is the finding. The call they count is still there; the decode added next to
it is invisible to them.
One correction to how this would be easiest to describe. Unlike #1078's
allColumnspair, these fail in ONE direction only. A realistic second caller usesdifferent variable names, so the exact-text count stays at 1 and the old arm is
BLIND rather than falsely alarmed — row 3 is a pass by accident, not by
correctness. "Same defect as #1078" is the easy sentence and it is not true.
The fix is a want-ZERO
Three entry points exist and only one decodes every column:
so the projection path is asserted never to call the third. More correct callers of
the two narrow entry points move nothing; any full decode moves it off zero. That
is the opposite failure direction from a count pinned at 1, and it is the shape a
second honest caller cannot break.
Three premises, not two. Each narrow entry point is called at all — a file that
called nothing would also report zero full decodes. And the third is one I nearly
left implicit: the whole arm rests on
PgColumnarReadRowByNumberCols(not matchingPgColumnarReadRowByNumber(. That is asserted in the suite rather than assumed byme, because renaming the narrow entry point to a prefix of the wide one would make
the zero silently start counting the honest caller.
Removal proof, against the real suite
Five majors, this exact tree
Own
make clean, own build and own.soper major. The suite file is md58cb2aa5d5f82in both the tested tree and this commit, so the matrix is about thecode being reviewed:
No ledger change, checked rather than inherited from #1078
native_fetch_projectionhas 0 rows in main's ledger, so the gate cannot refuseits checks. Neither removed name appears in the ledger at all, so there is nothing
to prune either.
Scope
The fourth and fifth arm in this file repaired for counting a string across a whole
file. A sweep found 12 of this shape across the suites. The remaining seven are
@OffgridwithJD's, in
native_fetch_cache.sh(4),native_fetch_position.sh(2),decode_interrupts.sh(1) andnative_saop_pushdown.sh(1). Three further arms ofthe whole-file shape are already want-zero and correctly need nothing.
🤖 Generated with Claude Code
https://claude.ai/code/session_01NhwXKAgSmYDUjteWkfajHK