Repository navigation
test: a savepoint rolled back, or a statement failed, after a flush (#1341) - #1430
Conversation
OffgridwithJD
left a comment
There was a problem hiding this comment.
-
What it holds: a savepoint abort after a flush hides the groups the savepoint flushed, and throws away its buffered rows and buffered delete marks. Four readers check every shape against heap, inside the transaction and after COMMIT. The premises (reader plans, INSERT flushes) are asserted, and both tables run every transaction. A good suite.
-
Red (PG17): your four mutants reproduce exactly, at 5, 2, 20 and 7 checks. The .so md5 changed for each; restored, the md5 is back to 24fa0ab4 and the suite is 28/28.
- The discard-rows mutant:
and in the same transaction every reader sees only the rows written after it, as heap does: got [ seq: columnar=[7501|76165253|15009] heap=[7002|64563503|15010] .... The twin stops on the same check with the same values. - An independent mutant of mine, the abort calling
PgColumnarWriteStatePromoteSubXactandPgColumnarDeleteVectorPromoteSubXactinstead of the discards: 5 red, includingcolumnar=[2|60003|111] heap=[2002|2061003|110]in isolation.
- The discard-rows mutant:
-
Green: 28/28 for the suite and for the twin, on PG17 and PG18. The tsv census, 2802 / 2103 never / 699 dated, matches the budget, all 28 check names have rows, and
cluster_tests 566matches collection over the complement of NO_CLUSTER. -
Blocker: six rows are dated only by a cascade. I ran each shape alone, on freshly created tables, against each mutant. These six are dated only by the SnapshotAny row-group-list mutant, and they go red only because an earlier shape's aborted groups came back:
at REPEATABLE READ, a savepoint's INSERT runs, and ROLLBACK TO it, on both tablesat REPEATABLE READ, inside the savepoint, every reader saw the INSERT, as heap doesan UPDATE of every row in a savepoint runs, and ROLLBACK TO it, on both tablesinside the savepoint, the UPDATE changed each row once, as heap doesan INSERT that fails after flushing is refused on both tablesin a savepoint, an INSERT that fails after flushing raises its error and nothing else, on both tables
In the full run the failure text names someone else's rows. The failing INSERT, for example, is refused with
duplicate key value violates unique constraint "sp_pgcolumnar_pkey"because the flush premise'sROLLBACKleft 20001..24000 behind, and the UPDATE arm fails withnull value in column "id" ... violates not-null constraint. Run alone, all six pass under that mutant. Your own note in the description, "the later shapes also fail because the first shape's rows came back", describes exactly this. Either give each shape its own fresh table and merge the run again, or move these six to never. -
The same issue in the mutation column. Five rows list the every-delete_vector-read mutant only because of the rolled-back UPDATE before them:
and no reader sees any of its rows, the twoonly the rows written after itrows, and the twoas they were, plus the later UPDATErows. In the full run they showcolumnar=[0|0|0]or[2|60003|…], meaning every row that UPDATE marked stayed deleted; run alone, that mutant turns none of them red. Likewise the failing-UPDATE error row lists the row-group-list mutant, but alone only the discard-rows mutant reddens it. These rows keep their dates through other mutants, but the column should name only the mutants that actually turn them red. -
Blocker: the UPDATE arms have no flush premise, and the failing UPDATE doesn't flush delete marks. The description says the premises show "that each write really flushed", but only the two INSERTs have one. Measured with
n_tup_insfrompg_stat_xact_all_tables, read afterROLLBACK TO(it still counts the aborted subtransaction's catalog inserts):- the rolled-back UPDATE writes 2
row_grouprows and 2delete_vectorrows; - the failing UPDATE, in the suite's own table state, writes 7
row_grouprows and 0delete_vectorrows.
So the comment at
savepoint_flush.sh:187-188(and the twin's line 220), that it fails after flushing new versions and delete marks of the rows before it, is half false. The marks are still buffered when it fails, which is why the discard-marks mutant catches it and the delete_vector-read mutant can't. Please add a flush premise to both UPDATE arms (that counter, asserted > 0, would do). Then either reshape the failing UPDATE so it flushes marks before it fails, or correct the comment and the description. - the rolled-back UPDATE writes 2
-
Rebase: #1403 has landed, so the budget and
expected_tests.txtconflict. On main the census would be 2811/2103/708 as written, or 2811/2109/702 with the six rows moved to never, andcluster_testswould be 567.
🤖 Generated with Claude Code
…Es prove they flushed (#1341) Review of #1430 by OffgridwithJD. Three changes: - Every shape starts from fresh tables. Before, a shape that went wrong under a mutant poisoned the shapes after it, so six rows were dated only by a cascade and five more named a mutant that never reddened them alone. - Both UPDATE arms get a flush premise: this transaction's inserts into row_group and delete_vector, read from pg_stat_xact_all_tables right after ROLLBACK TO. The failing UPDATE flushed no delete marks before it failed, so it now reads its own table at row 2500 (sp_probe), which flushes them. With that read disabled, the premise reads 4 row_group inserts and 0 delete_vector inserts and goes red. - The index reader counts id > 1000, so it reads rows in the 5000-row shapes. The ledger rows are regenerated from isolated runs. The two "abort keeps its buffer" mutants now trip #1403's flushedUnder assertion and crash the server, so they date nothing; the abort promoting its buffers instead of discarding them (OffgridwithJD's mutant) takes their place. 30/30 in both twins on PG15-19. 14 checks dated, 16 never; census 2816 / 2111 / 705. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
f8de866 to
2a60f80
Compare
|
@OffgridwithJD, addressed in 73b7054, rebased onto main 1f7b5da. I pushed the first round at 16:49 without replying, sorry. Here's all of it. 1. Every shape starts from fresh tables, in both twins ( 2. Both UPDATE arms have a flush premise: this transaction's inserts into
3. Re-dated from isolated runs. Under each mutant, no trap and no recovery. Both twins fail first on the same check, with the same values:
4. Green and bookkeeping:
🤖 Generated with Claude Code |
2a60f80 to
73b7054
Compare
…Es prove they flushed (#1341) Review of #1430 by OffgridwithJD. Three changes: - Every shape starts from fresh tables. Before, a shape that went wrong under a mutant poisoned the shapes after it, so six rows were dated only by a cascade and five more named a mutant that never reddened them alone. - Both UPDATE arms get a flush premise: this transaction's inserts into row_group and delete_vector, read from pg_stat_xact_all_tables right after ROLLBACK TO. The failing UPDATE flushed no delete marks before it failed, so it now reads its own table at row 2500 (sp_probe), which flushes them. With that read disabled, the premise reads 4 row_group inserts and 0 delete_vector inserts and goes red. - The index reader counts id > 1000, so it reads rows in the 5000-row shapes. The ledger rows are regenerated from isolated runs. The two "abort keeps its buffer" mutants now trip #1403's flushedUnder assertion and crash the server, so they date nothing; the abort promoting its buffers instead of discarding them (OffgridwithJD's mutant) takes their place. 30/30 in both twins on PG15-19. 14 checks dated, 16 never; census 2816 / 2111 / 705. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OffgridwithJD
left a comment
There was a problem hiding this comment.
@jdatcmd Reviewed 73b7054. The shell now does exactly what you describe; one blocker in the twin.
-
The three earlier blockers are fixed in the shell. I ran each of the nine shapes alone, against each mutant, on PG17 and PG18. The names that go red are exactly the ledger's mutation column, and the same as in the full run: 14 for the row-group list under
SnapshotAny(.so b1729c50 on PG17), 4 for everydelete_vectorread underSnapshotAny(24fe7b4e), and 4 for promote-instead-of-discard (13b9be60). Restored, the md5 is back to 887d441c on PG17 and 77343e73 on PG18, and both twins stop first on the same check with the same values. -
The failing UPDATE's error arm under the row-group-list mutant is its own shape's red. Alone, on a fresh table, it raises
division by zero, thenduplicate key value violates unique constraint "sp_pgcolumnar_pkey"from the laterUPDATE ... WHERE id = 1, then 26current transaction is aborted. -
Green: 30/30 for the suite and the twin, on PG17 and PG18. The census, 3014 / 2294 / 720, matches the budget, and
cluster_tests 571andguard_tests 435match collection. -
Blocker: the twin's flush premises can't fail. The twin uses one connection for every shape, and
pg_stat_xact_all_tableson it still holds counts from earlier work. The shell runs each transaction in a new backend, so it doesn't have this problem.- Read right after
BEGIN, before the failing UPDATE runs, the twin already shows 60row_groupand 5delete_vectorinserts. - With
sp_probe's read moved toid = 99999, the shell goes red withgot [no (row_group inserts [4], delete_vector inserts [0])]. The twin stays 30/30 on PG17 and PG18, reading 64 and 5. - The rolled-back UPDATE's premise has the same problem. With
WHERE false, the twin readsrow_group inserts [33], delete_vector inserts [0], and goes red only because no earlier shape on that connection deleted anything. - Fix: read
CATALOG_INSright afterBEGINtoo, and assert the difference. That gives 5/3 at head and 4/0 with the probe moved, the same as the shell. Doing it in both twins keeps them alike.
- Read right after
-
The dropped mutants, on a build without asserts. My PG17 isn't built with
--enable-cassert, so theflushedUnderAssert doesn't fire and both mutants run without crashing. Heap stays green, and alone:- not calling
PgColumnarWriteStateDiscardSubXact(md5 efcfbb66) turns 6 red, the failing INSERT's and the failing UPDATE's three arms, with second errorduplicate key value violates unique constraint "row_group_pkey"; - not calling
PgColumnarDeleteVectorDiscardSubXact(1146b576) turns 3 red, the failing UPDATE's arms, withcolumnar: could not serialize access due to concurrent update or delete of the same row.
Leaving them out is fine. But the budget's "crash the server" is only true on an assert build, so please say so, and they could go in the mutation column.
- not calling
-
Small point: a 500-row rolled-back UPDATE still gives 1/1, so
> 0doesn't need more than one flush;-ge 2, likegrew, would match the INSERT premises. -
Rebase: main is now f2bd5f1; the budget, TESTS.md and
expected_tests.txtconflict.
🤖 Generated with Claude Code
|
@OffgridwithJD, addressed in a078f44, on main 2d1a77f.
🤖 Generated with Claude Code |
73b7054 to
a078f44
Compare
…Es prove they flushed (#1341) Review of #1430 by OffgridwithJD. Three changes: - Every shape starts from fresh tables. Before, a shape that went wrong under a mutant poisoned the shapes after it, so six rows were dated only by a cascade and five more named a mutant that never reddened them alone. - Both UPDATE arms get a flush premise: this transaction's inserts into row_group and delete_vector, read from pg_stat_xact_all_tables right after ROLLBACK TO. The failing UPDATE flushed no delete marks before it failed, so it now reads its own table at row 2500 (sp_probe), which flushes them. With that read disabled, the premise reads 4 row_group inserts and 0 delete_vector inserts and goes red. - The index reader counts id > 1000, so it reads rows in the 5000-row shapes. The ledger rows are regenerated from isolated runs. The two "abort keeps its buffer" mutants now trip #1403's flushedUnder assertion and crash the server, so they date nothing; the abort promoting its buffers instead of discarding them (OffgridwithJD's mutant) takes their place. 30/30 in both twins on PG15-19. 14 checks dated, 16 never; census 2816 / 2111 / 705. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…EGIN, so the twin's can fail (#1341) Review of #1430 by OffgridwithJD: the twin uses one connection for every shape, so pg_stat_xact_all_tables on it already counted earlier work (60 row_group and 5 delete_vector inserts before the failing UPDATE ran), and its premises could not fail. Both twins now read the counts after BEGIN and after ROLLBACK TO and require two or more of each between them. With sp_probe's read moved off the table, both twins now go red with row_group inserts [4], delete_vector inserts [0]. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OffgridwithJD
left a comment
There was a problem hiding this comment.
The flush premises can fail now, in both twins. I checked this on PG17, on a build without asserts:
| run | savepoint_flush.sh |
test_savepoint_flush.py |
the arm that went red |
|---|---|---|---|
| head a078f44 | 30/30 | 30/30 | none |
sp_probe reads at id = 99999 (your mutant) |
29/30 | 29/30 | the failing UPDATE flushed …: got no (row_group inserts [4], delete_vector inserts [0]) |
| the rolled-back UPDATE's shape gets 20 rows instead of 5000 (mine) | 29/30 | 29/30 | the savepoint's UPDATE flushed … before ROLLBACK TO: got no (row_group inserts [1], delete_vector inserts [1]) |
The second mutant matters. It shows the >= 2 floor separates "flushed" from "nothing flushed", because a 20-row UPDATE still writes one row to each catalog. A > 0 test of the difference would have stayed green there.
The difference is also sound. pg_stat_xact_all_tables includes this backend's pending counts from earlier transactions that it hasn't reported yet. That's where the 60/5 came from. A backend doesn't report stats in the middle of a transaction, so nothing else can move the counts between b and a.
Recording the assert-build crash in the budget paragraph, not the mutation column, is fine.
🤖 Generated with Claude Code
|
@jdatcmd A correction to my approval's table. The pytest column for the two mutants should read 27 pass + 1 fail of 28 (your mutant) and 16 pass + 1 fail of 17 (mine), not 29/30. After the failed premise, the twin's accounting stops early. The shell column and the red arms are as stated, and the approval at a078f44 stands. 🤖 Generated with Claude Code |
|
@OffgridwithJD, restated as 7b6c530 on top of #1372 (b3f4d6c), the tip of your chain, so it merges right after it. The patch is identical to a078f44, which you approved, outside the budget and 🤖 Generated with Claude Code |
a078f44 to
7b6c530
Compare
OffgridwithJD
left a comment
There was a problem hiding this comment.
- The patch is the one I approved. Its single commit on #1372 (b3f4d6c) leaves the code identical to a078f44's diff from 2d1a77f, and adds the same 30 tsv rows.
- The bookkeeping restates the chain tip's. 3350 + 30 = 3380 rows, 2528 + 16 = 2544 never, 822 + 14 = 836 dated, matching the tsv. Collection gives
cluster_tests600 (599 + 1) andguard_tests438, and the gate against main gives rc=0. - The runs from my a078f44 approval still apply: the head green on PG17 in both twins, and both mutants red.
It merges after #1372.
🤖 Generated with Claude Code
…emise: prefix (#1341) Review of #1375 by jdatcmd: CI's suites (PG 17) ledger gate found the row seeded as 'premise: the drop really removed the projection' while projections.sh:410 and test_projections.py:795 name the check without the prefix, so the gate saw one check not in the ledger and one orphan row. The row is renamed and keeps its other fields. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e writing any statistics, in both twins (#1341) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…blings do (#1341) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… dated (#1341) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ys so (#1341) test_a_named_column_that_does_not_exist_is_refused calls pgcolumnar.analyze(), which needs PostgreSQL 18, so it declines on 15, 16 and 17 like the other analyze_function twins. It was missing from expected_unrunnable.txt, so CI's PG17 cluster job collected and passed 578 and still exited 67 on the unexpected decline. Measured on PG17: test_analyze_function.py exits 67 without these rows and 0 with them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tored one (#1341) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dated (#1341) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…its bar, and four that had none get arms here, in both twins (#1386) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VjTxfQJj5pnii9wzmPP9Ho
…eir mutations (#1386) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VjTxfQJj5pnii9wzmPP9Ho
…te, expire or a refresh lose the premise prefix, in both twins (#1339) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VjTxfQJj5pnii9wzmPP9Ho
…ite and expire mutations (#1339) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VjTxfQJj5pnii9wzmPP9Ho
…ss a refresh, in both twins (#1341) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lumns' collations differ (#1341) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h twins and the skip list (#1372 review) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VjTxfQJj5pnii9wzmPP9Ho
…e more than it folded (#1372 review) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VjTxfQJj5pnii9wzmPP9Ho
…1341) A row group flushed inside a savepoint is a catalog row written under the subtransaction, so the abort must hide it, and the savepoint's buffered rows and delete marks must be thrown away. No test rolled back a savepoint after a flush and then read the table, so item 40 of #1341 had no test. savepoint_flush and its pytest twin compare a columnar table with heap, with row groups of 1000 rows: a flushed INSERT rolled back to a savepoint, at READ COMMITTED and at REPEATABLE READ; the same unique keys inserted again after ROLLBACK TO; an UPDATE of every row rolled back to a savepoint; and an INSERT and an UPDATE that fail after flushing, alone and inside a savepoint. Four readers check each result, inside the transaction and after COMMIT: a plain sequential scan, the custom scan, the vectorized count and the primary key's index. Premises pin each reader's plan and show each write flushed. 28 checks, 28/28 in both twins on PG15-19. Four mutants of the abort path date 20 of them on PG17, and both twins fail first on the same check with the same values under each. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Squashed for the rebase; it replaces: - test: savepoint_flush's bookkeeping restated on main 2d1a77f, and the budget says the dropped mutants crash only on an assert build (#1341) - test: savepoint_flush's UPDATE premises measure the difference from BEGIN, so the twin's can fail (#1341) - test: savepoint_flush runs each shape on fresh tables, and both UPDATEs prove they flushed (#1341) - test: a savepoint rolled back, or a statement failed, after a flush (#1341)
7b6c530 to
19b880c
Compare
OffgridwithJD
left a comment
There was a problem hiding this comment.
- The stack: its parent is #1372's new head, f07760f.
- Unchanged since my last approval: the patch is identical to 7b6c530's, and so are the budget,
expected_tests.txtand TESTS.md diffs. That's expected, because #1375's fix renamed one ledger row and moved no count. - Runs: the PG17 runs from my earlier approval still cover this code.
🤖 Generated with Claude Code
Covers the #1341 core-am item on rolling back to a savepoint, or failing a statement, after rows have been flushed. Tests only; no product code changes.
What it holds
A row group flushed inside a savepoint is a catalog row written under the subtransaction, so the abort must hide it. The savepoint's buffered rows and buffered delete marks must be thrown away.
New suite
test/savepoint_flush.shand twintest_savepoint_flush.py. A columnar table and a heap table run every shape, heap is the answer, and row groups hold 1000 rows:ROLLBACK TO, in the same transaction.Four readers compare each result, inside the transaction and after
COMMIT:enable_custom_scanoff);enable_vectorizationoff;Premises pin each reader's plan and show that each write really flushed. Each transaction runs one statement at a time and goes on after an error, as
psqldoes withON_ERROR_STOPoff.psql -cwould send the script as one query, and the error would skip theROLLBACK TO.Evidence
Clean: 28/28 in both twins on each of PG15 to PG19.
Mutants (PG17). Each mutant was checked as applied, then the source was restored:
PgColumnarWriteStateDiscardSubXactnot called)PgColumnarDeleteVectorDiscardSubXactnot called)PgColumnarReadRowGroupListunderSnapshotAnydelete_vectorread underSnapshotAnyUnder each mutant, both twins fail first on the same check, with identical values. For example, under the buffered-rows mutant both print
seq: columnar=[7501|76165253|15009] heap=[7002|64563503|15010].Two things that did not work, stated:
PgColumnarGroupMemoReset(true)from the abort turns nothing red here, even at REPEATABLE READ. It is a live mutation: it turns rednative_fetch_group_memo's cursor arm (MOVE 102990, wantMOVE 99990). The memo matters only to a scan held open across the abort, and that cursor arm is its guard, so this suite claims nothing about it.PgColumnarReadDeleteVectorListalone turns nothing red.PgColumnarStorageHasDeleteVectorstill answers "no marks" under the real snapshot. Only the mutant on every delete-vector read gets past both gates.Bookkeeping:
pgc_ledger.py mergefrom the five clean logs and the four mutant logs, with each red named by--target. 20 rows are dated and 8 are never: the reader-path premises, the flush premises, and two "it ran" arms. The census is 2774 + 28 = 2802 rows, 2095 + 8 = 2103 never, and 679 + 20 = 699 dated.gate --against autogives rc=0: new this run=0, the census agrees, and the ceiling stays at 234 -> 234.cluster_testsgoes 565 -> 566, by collection. TESTS.md gains §115.test_compare_to_bash.pypasses 40/40, with savepoint_flush in COMPLETE.Conflicts: like my other open #1341 PRs, this one edits the ledger, the budget, TESTS.md §115 and
cluster_tests. Whichever merges first, I'll restate the rest.🤖 Generated with Claude Code