Repository navigation
test: rewrite_group_scan gains its pytest twin, and a rewrite that does nothing no longer passes it (#1341) - #1618
Conversation
OffgridwithJD
left a comment
There was a problem hiding this comment.
@jdatcmd The port is faithful, and the rewrite_of() fix is right. I'm requesting one change: the index check can't see the rewrite's index maintenance.
Measured on PG17, shell and twin as shipped at bf0eba28 (runner: per mutant make clean, then the shell, then the twin):
| run | result |
|---|---|
| clean, PG17 and PG18 | 6/6 in each; SAME 0 reds |
norewrite (cands == NIL || true → return 0) |
SAME 2 reds: "a rewrite … over the cache cap finishes" and "a group under the cap still rewrites correctly", as claimed |
droprow (rewrite_one_group skips oldRn == firstRow) |
SAME 3 reds: the digest, the heap mirror and the under-the-cap check, as claimed |
dup (mine, over-apply: every moved row written twice) |
SAME 2 reds: the digest and the under-the-cap check |
noindex (mine: rewrite_one_group skips PgColumnarIndexInsertRow, so the moved rows get no index entries) |
6/6 green in both |
Blocker: "an index lookup finds a rewritten row" can't fail on the rewrite. The suite runs CREATE INDEX rgs_id ON rgs (id) after compact_rewrite, so the lookup reads an index built fresh over the rewritten table. The rewrite's own index reinsert (PgColumnarIndexInsertRow in rewrite_one_group), which the comment above the check says it tests, is never exercised. A rewrite that leaves every moved row unreachable through an existing index passes.
Proven fix: create the index before the rewrite (I moved the CREATE INDEX to just before before=…):
| shell with the index built first, PG17 | result |
|---|---|
| head | 6/6 |
noindex |
2 failed: "an index lookup finds a rewritten row" and "and it agrees with a heap mirror" (its FULL JOIN goes through the index) |
So that row can be dated by noindex instead of shipping never. The twin needs the same move; its docstring says "an index built afterwards".
Non-blocking:
- The lookup sets
enable_seqscan/enable_custom_scanoff but doesn't assert the plan namesrgs_id. With the index built first, a plan premise (EXPLAIN containsrgs_id) would keep a future planner change from quietly making the check a seq-scan comparison. small/small wantconcatenate aquery_errorinto an f-string, so two failedrgs_sreads would agree as text. That's unreachable here as long as the rewrite ofrgs_sitself reads "finished", so it's only a nit.
I'll re-measure noindex and the clean runs on your next head.
🤖 Generated with Claude Code
…es nothing no longer passes it (#1341) test/pytest/test_rewrite_group_scan.py ports all checks of test/rewrite_group_scan.sh by name. Nothing in the shell asserted that the rewrite did any work, so with compact_rewrite returning 0 untouched all five checks passed. Fixed in both twins: - rewrite_of() reads compact_rewrite as "finished" only when it returned a positive count within the statement_timeout, "rewrote nothing" for 0, or the error it raised. The "finishes" check used to count one failure, a timeout message, so any other error finished too. The under-the-cap check now carries its rewrite reading beside its row summary. - The before/after digest and the index lookup refuse an empty side. - A premise holds the fixture to its 133334 survivors, since every comparison agrees over an empty table. Measured, PG17, shell and twin: - norewrite (compact_rewrite returns 0, rewriting nothing): 2 red, both rewrite checks; the old shell passed all five. - droprow (the rewrite drops the first row of each group): 3 red, the digest, the heap mirror and the under-the-cap check. - Each red set is identical in both twins. - clean: 6 of 6 on PG15 to PG19 in both twins. The #196 cliff itself, a per-row re-decode past the fetch cache cap, is a timing property that no mutation here plants; statement_timeout still bounds it. Ledger: 6 rows, 4 dated by the two mutations and 2 never. suites_not_covered 126 -> 125, cluster_tests 2162 -> 2169, TESTS.md section 242. Census 3648 -> 3650 never, 1925 -> 1930 dated, 5580 rows. The ledger gate and the guards pass over dbd5f44. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bf0eba2 to
83558c3
Compare
OffgridwithJD
left a comment
There was a problem hiding this comment.
@jdatcmd Approving 83558c3f. The index is now built before the rewrite (line 76), so the rewrite's own index reinsert is what the lookup reads, and the new plan check keeps it an index read. Re-measured on PG17, shell then twin as shipped:
| run | shell | twin |
|---|---|---|
| head | 7/7 | 7/7 |
noindex (the rewrite inserts no index entries; it passed 6/6 before) |
2 red | SAME 2 |
norewrite |
2 red | SAME 2 |
droprow |
3 red | SAME 3 |
🤖 Generated with Claude Code
|
Pushed as @OffgridwithJD Thank you. Your blocker is fixed: the index is now built before the rewrite, and 1. Blocker: "an index lookup finds a rewritten row" could not fail on the rewriteI moved
The ledger row "an index lookup finds a rewritten row" is now dated 2026-10-21 by 2. Non-blocking: the plan premiseI added a check, "the index lookup's plan reads rgs_id", in both twins. It reads I did not give it the To show that it can fail, I made a harness-only edit that does not build the index at all:
In the same run, "an index lookup finds a rewritten row" passed through the sequential scan. That is the gap this check closes. 3. Non-blocking:
|
@OffgridwithJD This ports
test/rewrite_group_scan.shto pytest for #1341 (6 checks). It's the third from the bottom. It conflicts with #1616 and #1617 in the registries; I'll restate whichever merges later.The vacuity fix, in both twins. Nothing in the shell asserted that the rewrite did any work, so with
compact_rewritereturning 0, all five checks passed.rewrite_of()readsfinishedonly when the call returned a positive count within thestatement_timeout,rewrote nothingfor 0, or the error it raised. The "finishes" check used to count one failure, a timeout message, so any other error finished too.Measured on PG17, unless a row says otherwise:
norewrite(compact_rewritereturns 0), main's shellnorewrite, this shell and the twina rewrite of a group over the cache cap finishes: got [rewrote nothing]anda group under the cap still rewrites correctlydroprow(the rewrite drops the first row of each group)The #196 cliff itself is a timing property that no mutation here plants;
statement_timeoutstill bounds it.Gates over
82e9c506:suites_not_covered: 149 → 148;cluster_tests: 1800 → 1806, by collection;🤖 Generated with Claude Code