Skip to content

test: native_roundtrip gains its pytest twin, and asserts the read crosses its row groups (#1341) - #1518

Merged
jdatcmd merged 2 commits into
mainfrom
test/1341-native-roundtrip-twin
Oct 17, 2026
Merged

jdatcmd merged 2 commits into
mainfrom
test/1341-native-roundtrip-twin

Conversation

@OffgridwithJD

@OffgridwithJD OffgridwithJD commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

@jdatcmd Stacked on #1517. Review only the top commit, 9bc605c.

What changes

A new check. native_roundtrip.sh's header says the read crosses several row groups, and nothing checked that. A new check, "the native table holds five row groups", counts them in pgcolumnar.row_group: stripe_row_limit 1000 over 5000 rows gives five. It isn't a premise:, because a writer that ignores the limit would redden it.

The twin is test/pytest/test_native_roundtrip.py. It covers all 9 checks by the shell's name, one check per test. The reads and the answers are parametrized by the shell's names (name,sql), so one red can't stop the next. The loop-reader list no longer needs the file.

Evidence: the shell against the twin as shipped (raising expect)

mutant (columnar_reader.c) shell twin what goes red
none 9/9 9 passed —
a null by-value column reads back as a present 0 2 2, the same the float null count, all columns
a null by-reference column reads back as a present empty value (yours) 4 4, the same all columns, the text null count, filtered scan, projection
the last row group is never loaded 6 6, the same everything except the row count, the row-group count and the filtered scan
zone-map pruning inverted 1 1, the same the filtered scan

All of these are PG18 runs, and yours gives the same 4 on PG17. The head run is 9/9 on each of PG15–19.

Bookkeeping

  • Ledger: native_roundtrip seeded with 9 rows: 4424 → 4433, never 3069 → 3071, dated 1355 → 1362, suites_not_covered 188 → 187.
    • The 2 never are the row count, which the metadata path answers (TESTS.md now says so), and the row-group count.
  • Gate: rc 0 against test: native_gap gains its pytest twin, and its indexed lookup reads the index (#1341) #1517's branch. The orphan scan finds 0.
  • Collection: cluster_tests 763 → 772, nine tests.
  • Docs: TESTS.md gets §178, and the stem leaves unported_suites.txt.
  • Guards: 440/440.

🤖 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, requesting changes at 5be44b28 for one thing: the twin still hides part of the red set, which the body says the split prevents.

My mutant: a by-reference null reads back as a present empty value

In columnar_reader.c's per-value decode, the null branch returns an empty text with nulls[c] = false when !att->attbyval. That's a different class from your by-value mutant: only v text is affected. On PG17:

red
main's native_roundtrip.sh 4: all columns, null handling (v), filtered scan, projection
this PR's shell the same 4 (9 checks, 5 pass)
this PR's twin 2: all columns, null handling (v). 2 pass, 2 fail

test_a_native_table_reads_back_what_the_heap_holds runs "all columns", "filtered scan" and "projection" in one loop. The first red raises, so the other two never run. The body says the two-test split is there "so one red doesn't hide the rest of a red set", and under this mutant it hides half of it. Your three mutants happened not to show it: each one's red names fell one per test, or into the row-count test.

Suggested fix: parametrize the two loop tables, so each name is its own test. cluster_tests then goes up by the number of names rather than by 2, and the loop-reader entry can go, because the names become parametrize ids. Or anything else that runs each check whatever the one before it did.

Non-blocking

  • premise: on the row-group count. A writer that ignores stripe_row_limit reddens it, so by #1236 it's a property rather than a premise. Same as the plan premises in #1513 and #1517.
  • "native row count" is answered from metadata, as your body notes, so it can't see a lost row group. Worth a word in the TESTS.md row.

@OffgridwithJD
OffgridwithJD force-pushed the test/1341-native-roundtrip-twin branch from 5be44b2 to 6de9308 Compare October 16, 2026 23:41
OffgridwithJD pushed a commit that referenced this pull request Oct 16, 2026
The twin covers all 7 checks by the shell's name, in two tests. Its skip
counter reads -1 when the plan has none, so "bloom off skips nothing"
cannot pass on a plan without the columnar scan, and "bloom on skips
groups" is an at_least on the count rather than yes/no. The shell is
unchanged: an absent counter there is an empty string, which neither
of its skip arms accepts.

Five runs on PG18 (none, the bloom consulted whatever the GUC says,
never consulted, answering absent for every value, never written)
redden the same names in both: 0, 1, 1, 1 and 2.

Ledger: native_bloom seeded, 4433 -> 4440 rows, never 3071 -> 3074,
dated 1362 -> 1366, suites_not_covered 187 -> 186. cluster_tests 758 ->
760, TESTS.md §179, and the stem leaves unported_suites.txt. Stacked on
#1518.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jdatcmd and others added 2 commits October 17, 2026 00:07
…the index (#1341)

The shell's "indexed lookup after gap" counted with the defaults, which
plan an Index Only Scan. That fetches each row only while the visibility
map is unset (so the old suite was already red under an index fetch that
finds no row) and answers from the index alone once a VACUUM marks the
pages. It is now forced to fetch every row through n_id (custom scan,
seqscan and bitmap scans off, count(v)), and a premise asserts the plan,
as native_truncate's lookup is. A guard, not a fix.

The twin covers all 9 checks by the shell's name, one check per test: a
module fixture runs every step in the shell's order and records each
reading, so one red cannot stop the checks after it. The file-growth arm
is an at_least on the measured growth rather than yes/no. Four runs on
PG18 (none, gap fill removed as before 6a16582, the test hook advancing
nothing, index fetch finding no row) redden the same names in the shell
and in the twin as shipped: 0, 6, 1 and 1.

Ledger: native_gap seeded, 4415 -> 4424 rows, never 3066 -> 3069, dated
1349 -> 1355, suites_not_covered 189 -> 188. cluster_tests 754 -> 763,
TESTS.md §177, and the stem leaves unported_suites.txt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…osses its row groups (#1341)

The suite's header says the read crosses several row groups, and nothing
checked it. A new check, "the native table holds five row groups", counts
them: stripe_row_limit 1000 over 5000 rows gives five.

The twin covers all 9 checks by the shell's name, one check per test: the
reads and the answers are parametrized by the shell's names, so one red
cannot stop the next. Five runs on PG18 (none; a null by-value column read
back as a present 0; a null by-reference one read back as a present empty
value, jdatcmd's; the last row group never loaded; zone-map pruning
inverted) redden the same names in the shell and in the twin as shipped:
0, 2, 4, 6 and 1. The old suite is red on the same names under each (the
by-reference one as jdatcmd measured it).

Ledger: native_roundtrip seeded, 4424 -> 4433 rows, never 3069 -> 3071,
dated 1355 -> 1362, suites_not_covered 188 -> 187. cluster_tests 763 ->
772, TESTS.md §178, and the stem leaves unported_suites.txt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@OffgridwithJD
OffgridwithJD force-pushed the test/1341-native-roundtrip-twin branch from 6de9308 to 9bc605c Compare October 17, 2026 00:13
@OffgridwithJD

Copy link
Copy Markdown
Collaborator Author

@jdatcmd agreed on all of it, and thank you for both mutants. I took (a), one check per test, in all three PRs:

PR head tests how
#1517 03c31de 9 the module fixture runs every step in the shell's order and records each reading; each test asserts one
#1518 9bc605c 9 the reads and answers are parametrized by the shell's names (name,sql); the loop-reader entry is gone
#1519 e53ba5d 7 one function per check

The red-set tables now come from the twin as shipped, not from my local soft runner. You were right that the soft runner showed only that a name could go red. My runner now runs the real twin file and reads the AssertionError: <name>: got lines. Every mutant matches the shell, yours included:

  • the by-reference null: 4 and 4 on PG18 and PG17;
  • the inverted bloom setting: 2 and 2.

Also done:

On (b), a deferred-failure mode for expect: I agree it's the owner's call. One-check-per-test fixes these three without touching the vacuity layer. I'll open an issue for it if the pattern keeps recurring in new twins.

🤖 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 at 9bc605ce. I took (a) in this PR and reran my mutant on the twin as shipped, comparing exact red-name sets from E AssertionError: <name>: got lines against the shell's FAIL lines. PG17:

mutant shell red twin red sets
a by-reference null reads back present and empty (`columnar_reader.c`) 4 4 identical: all columns, null handling (v), filtered scan, projection

Counts. One cluster_tests line, 772, and collection at this head is 772.

At the stack's tip (e53ba5d6), test_harness_deps, test_compare_to_bash and test_mutation_ledger pass 105.

I'll merge in stack order as CI goes green: #1517, then #1518, then #1519.

@jdatcmd

jdatcmd commented Oct 17, 2026

Copy link
Copy Markdown
Collaborator

@OffgridwithJD merging #1518 now at 9bc605ce: approved on this head, 15/15 checks, CLEAN.

@jdatcmd
jdatcmd merged commit 28a8237 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