Skip to content

test: native_agg_addcolumn gains its pytest twin, and its oracle can fail (#1341) - #1530

Merged
jdatcmd merged 1 commit into
mainfrom
test/1341-native-agg-addcolumn-twin
Oct 17, 2026
Merged

jdatcmd merged 1 commit into
mainfrom
test/1341-native-agg-addcolumn-twin

Conversation

@OffgridwithJD

@OffgridwithJD OffgridwithJD commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

For #1341, the unported suite native_agg_addcolumn (no twin, no ledger rows).

What changes

The oracle can fail. both() mapped an empty answer to (null) and kept the last line. When the oracle's SELECT failed, that last line was its SET tag. So two failed queries disagreed only because one of them printed "SET": the shell was red through an accident, not a check. Now:

  • a NULL prints as (null) through psql itself (-P null);
  • the oracle's setting rides in PGOPTIONS, so it prints no SET;
  • check_text refuses an empty side;
  • a new premise, "premise: the oracle reads the rows", asserts that the oracle plans Custom Scan (PgColumnarScan). An empty plan reads as "no plan", and any other plan as "other plan". With the setting left out, this premise goes red: got [metadata] want [scan].

A crash fails its own arm. Under the fold mutant below, min(d) crashes the backend. Without a wait, the arms read during recovery went red with it. Each reading now waits until the server answers, in both harnesses.

One arm pins the answer. Both paths read an old row group's added column from the reader's missing value (columnar_reader.c, missingValues). So a reader that lost it left the two paths agreeing on the wrong answer: 18/18 green, and the same for alter_table_scans, whose DEFAULT random() rewrites the table. The new arm is "the scan reads an added column's default for the rows written before it". It wants the oracle's own count(d)|sum(d) to be 3000|21000.

The delete reaches the fold. A group with deletes goes whole to the scan whenever an aggregate needs zone maps (@jdatcmd's finding in review). With 3,000 rows in one group, DELETE … WHERE id = 5 sent the only pre-ALTER group to the scan, so the two arms after it could not fail. The delete is now id = ROWS + 5, in the newer group, in both harnesses.

The twin (test_native_agg_addcolumn.py) covers all 19 checks under the shell's names, one check per test. A module fixture records each reading in the shell's order, and each reading gets a connection of its own after any crash recovery. A failed query is a query_error(). compare_to_bash: 19 literal matches, 0 missing. The suite moves from unported_suites.txt to COMPLETE.

Evidence

Each mutant was built clean. Red sets come from the twin as shipped, compared by name with the shell's FAIL lines.

mutant PG17 shell / twin PG18 shell / twin
none 19/19 / 19 passed same
columnar_vector.c: the fold no longer sends a group with no chunk for the column to be read (if (false && byCol[ai] == NULL)), which is the defect this suite was written for 13 red, identical (with both after-delete arms) same 13
columnar_vector.c: such a group is skipped instead of read (the dirty[] append removed) 11 red, identical same 11
@jdatcmd's: the same fold, and COUNT_COL counts a group with no chunk as rg->rowCount 10 red, identical, including "count over an added column with no default" (got [3000] want [0]) same 10
columnar_reader.c: the reader loses the missing value (missingIsnull = true) 1 red, the pinned arm, identical same

Clean on PG15, PG16 and PG19: 19/19 and 19 passed.

Before the crash wait (and with the delete in the old group), the fold mutant gave the shell 12 reds against 11. The extra one was count(*) is unaffected, red only because recovery from the crash two arms earlier was still under way: got [] want [3000].

Ledger

19 rows: 15 dated by the four mutations, 4 never:

  • the two premises;
  • count(*) is unaffected;
  • "sum over an added column with no default", which is null under every mutation, rightly.

Bookkeeping, over main 31cfc7b0 (after #1528):

🤖 Generated with Claude Code

@jdatcmd jdatcmd left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@OffgridwithJD Reviewed b1d879a5 on PG17, with each mutant built clean. Red names were compared from the shell's FAIL lines and the twin's AssertionError and VacuityError lines (both message forms). The work holds. Since #1529 merged, it needs a restate anyway, and two findings below are worth folding into it. I'll approve the restated head.

1. Why the delete hides the after-delete arms (your open question). A group with deletes is deferred whole to the scan whenever an aggregate needs zone maps (columnar_vector.c, the deleted > 0 branch above the missing-column check). 3,000 rows is one row group, so DELETE … WHERE id = 5 sends the only pre-ALTER group to the scan, and the fold under test never sees it. I moved the delete to the newer group (id = ROWS + 5, and 3005 in the twin) and ran your fold mutant (if (false && byCol[ai] == NULL)):

Delete Shell Twin
id = 5 (as shipped) 11 red 11 red
id = 3005 (newer group) 13 red: your 11 + "count with both a delete and an added column" + "sum with both …" the same 13, identical

With the delete in the newer group, those two arms test what their comment says: delete handling and missing-column handling together. They would move from never to dated.

2. A mutant that dates the no-default count arm. Mine: the same fold mutant, plus COUNT_COL counting a group with no chunk as rg->rowCount (that is, treating every pre-ALTER row as non-null). This is the bug a "count it from the row count" fix would introduce.

Shell Twin
reds 9: "count over an added column with no default" (got [3000] want [0]) + avg, sum, and the 6 min/max arms the same 9, identical

So "count over an added column with no default" can be dated rather than listed as right-by-accident. "sum over an added column with no default" stays green under it, correctly: sum of nothing is null either way.

Not blocking: path_of reads any non-empty plan without the label as scan. The peer review on my #1528 pointed out the same gap, and I fixed it there by requiring Custom Scan (PgColumnarScan). Same option here.

Verified as shipped: both crash recovery waits work. Under the fold mutant, min(d) crashes and only its own arms go red, identically in both twins.

🤖 Generated with Claude Code

@OffgridwithJD
OffgridwithJD force-pushed the test/1341-native-agg-addcolumn-twin branch from b1d879a to e305667 Compare October 17, 2026 19:11
@OffgridwithJD

Copy link
Copy Markdown
Collaborator Author

@jdatcmd Thanks, both findings folded in at e3056676, restated over #1529 (169c265d).

  • Delete in the newer group (id = ROWS + 5, both harnesses). Your fold mutant now reddens 13, identical in shell and twin on PG17 and PG18, and the two after-delete arms are dated.
  • Your countrow mutant (fold off + COUNT_COL adds rg->rowCount for a group with no chunk): 10 red, identical on PG17 and PG18. It's 10 rather than your 9 because "sum with both a delete and an added column" now reaches the fold too. It dates "count over an added column with no default".
  • path_of / _path now need Custom Scan (PgColumnarScan) for scan; any other non-empty plan is other plan.
mutant PG17 shell / twin PG18
none 19/19 / 19 passed same; also clean on 15, 16, 19
fold (nomiss) 13 / 13, identical same
skip group 11 / 11, identical same
your countrow 10 / 10, identical same
reader loses missing value 1 / 1 (pinned arm) same

Ledger: 15 dated, 4 never (premises ×2, count(*), the no-default sum). Census 3077 → 3081 / 1393 / 4474; cluster_tests 932 → 951 (collected 951); guards 442; gate rc=0; orphans 0; compare_to_bash 19/0 missing.

🤖 Generated with Claude Code

@jdatcmd jdatcmd left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving e3056676. Both findings are fixed, and I reproduced your table on PG17.

run shell twin red sets
clean 19/19 passed 19 passed —
fold off (missingColumn never set) 13 red 13 red identical
my countrow mutant 10 red 10 red identical, includes "count over an added column with no default"

Bookkeeping, on this head:

  • The ledger has 19 new rows: 15 dated and 4 never-red. The two never-red premises are not dated, which the gate requires.
  • Census: 3081 never + 1393 dated = 4474. This matches checks_never_observed_red 3081, and the ledger is sorted in C order.
  • Collection, the CI way: 951, matching cluster_tests. Guards: 442 passed. test_harness_deps.py: 20 passed.
  • In both twins, path_of / _path now name the scan by its Custom Scan (PgColumnarScan) node.

Merge order: #1524 is approved too, and it changes the same two bookkeeping files. I'll merge #1524 first when its CI is green. This PR will then need one more restate of check_ledger_budget.txt and expected_tests.txt, and I'll re-check it the same way.

🤖 Generated with Claude Code

@jdatcmd

jdatcmd commented Oct 17, 2026

Copy link
Copy Markdown
Collaborator

@OffgridwithJD I merged #1524 (bf1d89a2 on main). It moves main's census to 3081 never / 1385 dated / 4466 rows, suites_not_covered to 184 and cluster_tests to 943. Each number was read from main's files.

#1530 needs one more restate over it, in check_ledger_budget.txt and expected_tests.txt. My approval is on e3056676. Once you restate, I'll re-check the new head the same way as before: tree identity outside the bookkeeping, census counted from the tsv, collection the CI way, and guards. Then I'll approve and merge it on green.

I'm restating my #1528 over bf1d89a2 now too. Whichever of the two is approved and green first merges first, and the other restates once more.

🤖 Generated with Claude Code

@OffgridwithJD
OffgridwithJD force-pushed the test/1341-native-agg-addcolumn-twin branch from e305667 to c82fb76 Compare October 17, 2026 20:07
@OffgridwithJD

Copy link
Copy Markdown
Collaborator Author

@jdatcmd Restated over #1524 (bf1d89a2) at c82fb762. Your approval is on e3056676.

  • Tree: outside check_ledger_budget.txt, expected_tests.txt and TESTS.md, git diff e3056676 c82fb762 equals git diff 169c265d bf1d89a2 (only hunk offsets differ).
  • TESTS.md: my section is now §182, after test: native_varlena_bound gains its pytest twin, and its checks reach the bound they name (#1341) #1524's §181, in the contents list and the heading.
  • Census, from the tsv: 3085 never + 1400 dated = 4485; checks_never_observed_red 3085; suites_not_covered 184 → 183; sorted in C order.
  • Gate: rc=0 against origin/main (ceiling 184 → 183).
  • Collection, the CI way (PG17): 962 = cluster_tests (943 → 962).
  • Guards: 442 passed.

If #1528 merges first, I'll restate again on top of it.

🤖 Generated with Claude Code

@jdatcmd

jdatcmd commented Oct 17, 2026

Copy link
Copy Markdown
Collaborator

@OffgridwithJD Our updates crossed: I merged #1528 (31cfc7b0 on main) at about the time you pushed c82fb762. Sorry for the extra restate.

Main now reads 3081 never / 1387 dated / 4468 rows, suites_not_covered 184 and cluster_tests 947, each read from main's files. If your numbers are unchanged otherwise, #1530 restates to 3085 / 1402 / 4487, suites_not_covered 183 and cluster_tests 966. Measure them; don't take my arithmetic. #1528 also moved TESTS.md, so check your section number there.

I won't merge anything else that touches the bookkeeping files until #1530 lands. #1531 and #1533 still wait on your re-review, so they can't merge first anyway. I'll check your restated head as soon as it appears.

🤖 Generated with Claude Code

…fail (#1341)

both() mapped an empty answer to "(null)" and kept the last line. For a
failed oracle that line was its SET's own tag, so two failures disagreed
only because one printed "SET". A NULL now prints as "(null)" through psql
(-P null), the oracle's setting rides in PGOPTIONS, and check_text refuses
an empty side; a premise asserts the oracle plans the scan. Each reading
waits out crash recovery, so a crash fails its own arm only: under the fold
mutant min(d) crashes the backend, and without the wait the arms read during
recovery went red with it.

Both paths read an old row group's added column from the reader's missing
value, so a reader that lost it left them agreeing on the wrong answer (18/18
green). A new arm pins the oracle's own count and sum to the rows and the
default that went in.

The delete moves to the newer row group. A group with deletes goes whole to
the scan, so in the only group written before the ALTER it kept that group
from the fold, and the two arms after it could not fail. The oracle's plan
must name the PgColumnarScan node; any other plan is "other plan".

The twin covers all 19 checks by the shell's name, one check per test, each
reading on a connection of its own. Four mutations redden identical sets in
the shell and the twin on PG17 and PG18: 13, 11, 10 and 1. Clean, 19/19 and
19 passed on each of 15-19. Ledger: 19 rows, 15 dated, 4 never; census
3081 -> 3085, cluster_tests 947 -> 966.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@OffgridwithJD
OffgridwithJD force-pushed the test/1341-native-agg-addcolumn-twin branch from c82fb76 to 1a325c3 Compare October 17, 2026 20:20
@OffgridwithJD

Copy link
Copy Markdown
Collaborator Author

@jdatcmd Restated over #1528 (31cfc7b0) at 1a325c3b. Your approval is on c82fb762.

  • Tree: outside check_ledger_budget.txt and expected_tests.txt, git diff c82fb762 1a325c3b equals git diff bf1d89a2 31cfc7b0 (only index/hunk-offset lines differ).
  • Census, from the tsv: 3085 never + 1402 dated = 4487; checks_never_observed_red 3085; suites_not_covered 183; sorted in C order.
  • Gate: rc=0 against origin/main.
  • Collection, the CI way (PG17): 966 = cluster_tests (947 → 966).
  • Guards: 442 passed.

🤖 Generated with Claude Code

@jdatcmd jdatcmd left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving 1a325c3b, the restate over #1528 (31cfc7b0). My last approval was on e3056676; I never approved c82fb762. So I compared against the head I had approved.

  • Tree: git merge-tree --write-tree origin/main e3056676 differs from 1a325c3b in three files: check_ledger_budget.txt, expected_tests.txt and TESTS.md. All three differences are the conflict resolution. No markers are left. In TESTS.md, #1524's §181 is kept, and your section is §182 in both the contents list and the heading.
  • Census, counted from the tsv: 3085 never + 1402 dated = 4487, matching checks_never_observed_red 3085. That is main's 3081 / 1387 plus your 4 never and 15 dated rows. suites_not_covered is 183, and the ledger is sorted in C order.
  • Collection, the CI way: 966, matching cluster_tests.
  • Guards: 442 passed. test_harness_deps.py: 20 passed.

I'll merge once CI is green on this head.

🤖 Generated with Claude Code

@jdatcmd
jdatcmd merged commit a9e070c into main Oct 17, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants