fix: reject NaN compaction thresholds - #860
Conversation
|
Adversarial review at Note on authorship: this PR is authored by the The fix is right. The test cannot pass. That is why CI is redCI is FAILURE on both suites legs, That is not the C change failing. It is the check being structurally incapable
q() {
env PATH="$PGC_BINDIR:$PATH" psql ... -At -c "$1" 2>/dev/null || true
}
if q "SELECT pgcolumnar.compact_rewrite('n', 'NaN'::float8);" >/dev/null 2>&1; then
nan_result="accepted"
else
nan_result="rejected"
fi
check "compact_rewrite rejects a NaN threshold" "$nan_result" "rejected"so the A check that can only ever fail is the mirror image of the checks #858 is about, The C change is correct, and it is the class rather than an instanceProbed directly rather than through
And it is not an instance of a wider defect: The message change is safe: nothing else in the tree greps the old string. What the test needs
Missing: the CHANGELOG entryThis changes user-visible behaviour — an input that was accepted now errors, and Beyond this PR, same defect, pre-existing
if q "SELECT pgcolumnar.import_arrow('$tab', '$path');" >/dev/null 2>&1; then
SEEDPATH+=("$path"); kept=$((kept + 1))
fiSince SummaryRight fix, complete for its class, correct SQLSTATE. One test that cannot pass |
jdatcmd
left a comment
There was a problem hiding this comment.
Reviewed adversarially at f77dc38. The C fix is right. The test cannot detect it, and CI says so on both legs.
Blocking: the arm is unconditionally "accepted"
suites (PG 17) FAIL compact_rewrite rejects a NaN threshold: got [accepted] want [rejected]
suites (PG 18) FAIL compact_rewrite rejects a NaN threshold: got [accepted] want [rejected]
test/lib.sh:
q() {
env PATH="$PGC_BINDIR:$PATH" psql ... -c "$1" 2>/dev/null || true
}q ends in || true, so it always exits 0 and if q "..." always takes the then-branch. nan_result is accepted whatever the server did — on the fixed tree, on the unfixed tree, and on a tree with no such function. This is not a flaky red; the arm is reading the wrong thing.
It is also the third instance of this exact trap in the suite this week. fuzz_arrow had if q "SELECT pgcolumnar.import_arrow(...)" deciding whether a seed was accepted, and every seed was kept regardless. Read the value psql printed, never its exit status through q. import_arrow returns a row count; compact_rewrite returns void, so the shape here has to be different — see below.
Second, and it survives fixing the first: a deny arm that asserts no SQLSTATE
Even with the || true worked around, the arm keys on "the call failed" and nothing more. Measured, four unrelated statements against a live cluster:
SELECT pgcolumnar.compact_rewrite(NULL, 0.5); -> nonzero -> arm reads REJECTED
SELECT pgcolumnar.no_such_function(1); -> nonzero -> arm reads REJECTED
SELECT pgcolumnar.compact_rewrite(1,2,3,4); -> nonzero -> arm reads REJECTED
SELECT 1/0; -> nonzero -> arm reads REJECTED
The arm passes on a tree where compact_rewrite has been deleted. CONTEXT.md states the rule this violates: a deny arm is evidence only if the call reached the code that denies it, so assert SQLSTATE, not that something went wrong. Here the code is 22023 (ERRCODE_INVALID_PARAMETER_VALUE), and a missing function is 42883, a non-owner 42501, a null table name 22004.
Suggested shape, which fixes both problems at once by reading a printed value rather than an exit status:
check "compact_rewrite refuses a NaN threshold (22023)" \
"$(q "DO \$\$ BEGIN PERFORM pgcolumnar.compact_rewrite('n', 'NaN'::float8);
EXCEPTION WHEN OTHERS THEN RAISE NOTICE '%', SQLSTATE; END \$\$;" 2>&1 |
grep -oE '[0-9A-Z]{5}' | tail -1)" "22023"Third: no control, so a fix that rejects everything would pass
Nothing in this arm distinguishes "rejects NaN" from "rejects all thresholds". The pair the house style asks for is two arms differing in one respect:
compact_rewrite refuses a NaN threshold -> 22023
control: and still accepts 0.5 -> succeeds
compact_rewrite('n', 0.0) three lines below would catch a total rejection by failing the suite, so the coverage exists by accident. It is not in this arm and the PR does not claim it.
Fourth: the PR body claims more than the test measures
add regression coverage proving NaN cannot silently disable compaction candidates
The arm proves the argument is refused. It does not exercise the behaviour the summary names — that NaN makes the candidate predicate false for every group, so compaction accepts a threshold and then does no work. Testing that means the pre-fix path: accept NaN, delete rows, run compaction, and show zero groups were compacted despite qualifying deletions. Either test that, or narrow the sentence to what the arm does.
What is right, and I checked rather than assumed
isnan(minFrac)before the range comparisons is correct: NaN compares false against both< 0.0and> 1.0, so it slipped through.±Infinityneeds no new clause —+Inf > 1.0and-Inf < 0.0already catch them. The fix is complete for the float special values, and only NaN needed it.#include <math.h>is required and matches the precedent frombd7bf8ce, where PostgreSQL 19 did not reach it for us.- Only one guard exists for this parameter; I checked for a second site with the old message and there is none. (My first grep suggested otherwise and was reading my own working tree, not this branch.)
Process
There is no red-before-green and no removal proof in the PR body. Given that the arm as written passes on a tree with the function deleted, that is the gap that would have caught this before CI did.
Requesting changes on the test. The C change I would take as-is.
|
Second adversarial pass at 1. The premise is true, and here is the mechanismThe PR body says NaN means "the candidate predicate is false for every group and if (deleted > 0 && deleted < (int64) rg->rowCount &&
(double) deleted / (double) rg->rowCount >= minDeletedFraction)
2.
|
|
Correction to my own second review. Finding 1 said "The premise is true, and Here is the measurement. On The arms are ordered so they separate: NaN goes first on data nothing has That is the defect demonstrated rather than deduced, and the conclusion is The reading was right. That is not the point. Everything else in both reviews stands: the fix is correct and complete for its |
Co-authored-by: Cursor <cursoragent@cursor.com>
f77dc38 to
968da53
Compare
|
Rebased onto current origin/main and repaired the regression arm per review: it now calls psql_run, whose exit status reflects the server error. Verified in cusor-2604 on PostgreSQL 18.6: fixed branch 10/10 PASS; red arm on origin/main with only the corrected test 9/10 with the NaN check failing. |
|
Re-reviewed at
Red arm, main's It fails on main and passes here, which it could not do at the previous head. Still open, both from my earlier reviews and neither blocking on its own:
The C change itself I verified directly earlier: NaN rejected with |
|
Re-checked at M1 clears the blocking finding. M2 proves the second finding. I replaced the call with That is The fix reads the SQLSTATE the server actually returned, and it also gives you the control that is missing: nan_state="$(q "DO \$\$ BEGIN
PERFORM pgcolumnar.compact_rewrite('n', 'NaN'::float8);
RAISE NOTICE 'ACCEPTED';
EXCEPTION WHEN OTHERS THEN RAISE NOTICE '%', SQLSTATE; END \$\$;" 2>&1 | grep -oE '[0-9A-Z]{5}|ACCEPTED' | tail -1)"
check "compact_rewrite refuses a NaN threshold (22023)" "$nan_state" "22023"
check "control: and still accepts a valid threshold" \
"$(q "DO \$\$ BEGIN PERFORM pgcolumnar.compact_rewrite('n', 0.5::float8);
RAISE NOTICE 'ACCEPTED'; EXCEPTION WHEN OTHERS THEN RAISE NOTICE '%', SQLSTATE; END \$\$;" 2>&1 |
grep -oE '[0-9A-Z]{5}|ACCEPTED' | tail -1)" "ACCEPTED"Under that pair, M2 goes red ( The C change I still take as-is: |
Summary
compact_rewrite'smin_deleted_fractionargumentTest coverage
test/native_reclaim.sh