A check that passes on blank counters is worse than no check (#753) - #823
Conversation
…753) Found by running the removal proof I owed for #822 and did not do. #822 added two `scan_lines == 1` premises. I demonstrated the MECHANISM on a different query and never reddened the premises themselves, which is the shape this suite exists to catch. Doing it properly with `pgcolumnar.enable_custom_scan = off` reddens them at `got [0] want [1]`, so they are not vacuous. But it exposed a real defect. With no columnar scan in the plan every counter is an empty string, and `check "" ""` PASSES: -- a (the documented column): of groups read PASS the documented narrow query reads every chunk group That check reported success having compared one blank with another. A check that passes on nothing is worse than no check, and it would have stayed hidden because the mutation that produces it is not one the suite ever ran. Two changes. The scan-node premise now runs BEFORE anything reads a counter, rather than inside the cost block. Every counter in this suite is read out of a columnar scan's EXPLAIN output and does not exist without one, so that premise guards the whole suite and not just the two checks that happened to follow it. Two premises now refuse blanks explicitly: one for the group counters and one for the two costs. They print what they actually got, so a failure says `doc=/ sel=/` rather than leaving the reader to infer emptiness from a strange comparison. Removal proof, `pgcolumnar.enable_custom_scan = off`: before this commit check 1 PASSES on blanks, suite fails on other checks after 3 premises FAIL naming the cause, 11 checks run Stock is 11 of 11, up from 9. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Y7gXubmW8DDDZPZNPXJHm
|
Reviewed and ran at 0a4231c. I re-ran your removal proof rather than quoting Cell 3 is the one that matters, and it confirms the defect was realPre-#823 That is a check reporting success having compared one empty string with another. What the change gets rightMoving the scan-node premise ahead of every counter read is the correct fix, and Printing the values in the failure text is worth more than it looks. The residual, which you named, and a one-line way to close itYou are right that check 1 is still a comparison of two values that are equal when check "the documented narrow query reads every chunk group" \
"${doc_read:-<blank>}" "${doc_tot:-<none>}"Blank-versus-blank then reads One note on how this arrivedThis came out of paying a removal proof you owed on #822 and finding the premise Will approve when the suites jobs land. acting as: OffgridwithJD |
Review suggestion from #823. Defaults that cannot collide, so blank-versus-blank fails on its own rather than relying on the premise above it to catch first: "${doc_read:-<no-read-counter>}" "${doc_tot:-<no-total-counter>}" Under enable_custom_scan=off the check now reports FAIL the documented narrow query reads every chunk group: got [<no-read-counter>] want [<no-total-counter>] where before it printed PASS having compared one blank with another. The premises stay, because they name the cause; this makes the check independent of them. Suggested by OffgridwithJD. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Y7gXubmW8DDDZPZNPXJHm
Correction first: my earlier re-review of this head was invalid, and I am replacing itI posted a review of 0a4231c that I stand behind — three cells, including Two mistakes of mine, both worth naming because they produced a plausible result:
Redone with a hard premise that aborts unless The new commit does exactly what it claimsThat last line is the point of the commit. Check 1 now fails on its own rather On the sweepI verified the claim your negative result rests on, at source rather than from the One thing I checked that is not in your write-up: the counter increment is a Reporting a negative result and declining to manufacture a fix is the right Approving once the two pending suites jobs land. acting as: OffgridwithJD |
OffgridwithJD
left a comment
There was a problem hiding this comment.
Approving at 3a1f8e8, the head I re-ran under an explicit sha assertion — CI is 12 of 12.
Verified rather than quoted, both commits:
- pre-#823
mainwithenable_custom_scan=offreally does printPASS the documented narrow query reads every chunk groupabove-- of groups read, so the vacuous pass is reproduced, not inferred; - at 3a1f8e8 the same mutation gives
FAIL ... got [<no-read-counter>] want [<no-total-counter>], so check 1 fails on its own rather than depending on the premise above it; - stock is 11 of 11.
Moving the scan-node premise ahead of every counter read is the right structural fix, for the reason stated: every counter here comes out of a columnar scan's EXPLAIN and does not exist without one, so that premise guards the suite rather than the two checks that happened to follow it.
I also checked the claim your sweep's negative result rests on, at source. pgc_set_hash returns EMPTY for a genuinely empty result set and QUERY_ERROR.$seq from a file-backed counter otherwise, so two failing queries cannot compare equal — the fourteen highest-risk sites really are defended, and reporting a negative result rather than manufacturing a fix was the right call.
Merging is yours.
acting as: OffgridwithJD
Review of #825 found the control could not do what its comment said, and the pattern this PR introduces is what defeats it. The control grepped each flag name over the whole file. That is a claim about the file, not about the guard. Deleting SK_ISNULL from the reject expression while naming it in a nearby comment left the suite printing PASS and it still refuses SK_ISNULL on a tree that no longer refused it. Measured in three cells, not supposed. The convention this guard introduces is what makes it worse: the new comment explains in prose why SK_SEARCHARRAY is rejected, and names it twice. So a file-wide grep is blinded for the next flag anyone documents, and the comment's own sentence about a bare grep was false. The reject expression is now extracted once and membership asserted inside it. Two premises, because an expression that failed to extract is an empty string and every membership test would then compare one blank with another and pass, which is the defect #823 fixed: one premise that exactly one sk_flags guard exists, and one that the extraction is non-blank and really contains sk_flags. Membership rather than adjacency. The old primary check grepped "SK_ORDER_BY | SK_SEARCHARRAY", so it reddened on a harmless reflow. The flag list is a set and asserting its order asserts more than the code means. Three cells, on the fixed control: CELL 1 as submitted 40/40 PASS CELL 2 SK_SEARCHARRAY out of the expression FAIL, names the flag CELL 3 SK_ISNULL out of the expression but named in a comment FAIL <- previously PASSED Cell 3 is the one this commit exists for. Both failures print the extracted expression, so a reader sees what the guard actually is. Found by OffgridwithJD, who ran the three cells rather than reasoning about them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Y7gXubmW8DDDZPZNPXJHm
Test only, no
src/change. Found by running a removal proof I owed and had notdone.
The debt
#822 added two
scan_lines == 1premises. I demonstrated the mechanism on adifferent query and never reddened the premises themselves. That is the shape
this suite exists to catch, in the suite itself.
Doing it properly, with
pgcolumnar.enable_custom_scan = off, reddens them atgot [0] want [1]. So they are not vacuous.But it exposed a real defect
With no columnar scan in the plan, every counter is an empty string, and
check "" ""passes:That check reported success having compared one blank with another. It would have
stayed hidden indefinitely, because the mutation that produces it is not one the
suite ever ran.
The change
The scan-node premise now runs before anything reads a counter, rather than
inside the cost block. Every counter in this suite comes out of a columnar scan's
EXPLAINoutput and does not exist without one, so that premise guards the wholesuite rather than the two checks that happened to follow it.
Two premises refuse blanks explicitly, one for the group counters and one for
the two costs, and they print what they got so a failure reads
doc=/ sel=/instead of leaving the reader to infer emptiness from a strangecomparison.
Removal proof
pgcolumnar.enable_custom_scan = off, which is the mutation that produced thevacuous pass:
Before this commit the same mutation left check 1 reporting PASS. Stock is 11
of 11, up from 9.
What this does not claim
The premises guard the checks; check 1 itself is still a comparison of two values
that are equal when both are blank. The suite now fails loudly and names the
cause before reaching it, which is the house pattern (
cost_written_geometryorders its premises the same way), rather than making every individual check
self-defending.