CommitFest 7371 Review: Fix missing tts_tid in ExecForceStoreHeapTuple

code-review · generated from canonical Markdown

Type
code-review
Source
postgresql-master
Slug
commitfest-7371-review
Tags
code-reviewpatch-reviewpostgresql
Updated
2026-09-30T17:46:12.480374+00:00
Verdict / Summarywaiting-on-author
Front matter
schema: review-report-v2
slug: commitfest-7371-review
source: postgresql-master
target: CommitFest 7371 — Fix missing tts_tid in ExecForceStoreHeapTuple
base: b69356cd789fe963447177a0077c12c6c322c203
head: b69356cd789fe963447177a0077c12c6c322c203
verdict: waiting-on-author
diff_sha: 8e414d3c6e19a2146b1fbb1c7c71fb8a6180f2ea3fff37b995144cdb20aff927
model: glm-5.2:cloud
date: 2026-09-30

CommitFest 7371 — Review Report: Fix missing tts_tid in ExecForceStoreHeapTuple

Summary

Verdict: waiting-on-author (1 verified BLOCKER: test gap)

The patch adds slot->tts_tid = tuple->t_self; in the TTS_IS_BUFFERTUPLE branch of ExecForceStoreHeapTuple (src/backend/executor/execTuples.c:1768). This is a correct, minimal, one-line bug fix that restores the slot's tts_tid invariant after ExecClearTuple invalidated it, matching the established convention of tts_heap_store_tuple (line 497) and tts_buffer_heap_store_tuple (line 962).

The bug

KNN GiST scans with lossy distance recheck use a reorder queue in nodeIndexscan.c. When popping a tuple from the queue, ExecForceStoreHeapTuple is called with a buffer tuple slot. The TTS_IS_BUFFERTUPLE branch copies the tuple via heap_copytuple but previously did not set slot->tts_tid from tuple->t_self, leaving it at the InvalidItemPointer (4294967295,0) set by ExecClearTuple. With FOR UPDATE, the invalid tid is passed to heap_lock_tuple, which extends the relation with an orphan block before aborting with "attempted to lock invisible tuple".

Blocker split

StatusCount
Verified1
Unverified0

Findings

F-1

— BLOCKER — tests — Regression test does not exercise the buggy reorder-queue pop path

Location: src/test/regress/sql/gist.sql:246-251

Mechanism: The staged test queries ORDER BY v <-> point(50, 50) LIMIT 1 over circles centered at (i,i) with radius 0.5. For circle id=50, the query point (50,50) is inside the bounding box, so bbox distance = 0 and true distance = 0, making was_exact = true. With LIMIT 1 and an empty queue, the tuple is returned immediately without ever calling reorderqueue_push/reorderqueue_pop. The buggy ExecForceStoreHeapTuple pop path at nodeIndexscan.c:257 is NOT exercised.

Failure: A future regression reintroducing the missing tts_tid would not be caught. The test passes with or without the fix.

Verification: verified by static analysis and two-run validation. A replacement test was designed (querying point(0,0) outside all bounding boxes, forcing 100 reorder-queue pops) and validated: PASS on HEAD (with fix, all ctids valid), FAIL on BASE (without fix, all ctids = (4294967295,0), ERROR: attempted to lock invisible tuple).

Repair: Replace the weak test block with the strong test. The repair patch (f1-strong-test.patch) was applied, built, and pg_regress gist passed.

F-2

— MINOR — correctness — tts_tableOid not set in BUFFERTUPLE branch (pre-existing)

Location: src/backend/executor/execTuples.c:1759-1774

Mechanism: The TTS_IS_BUFFERTUPLE branch of ExecForceStoreHeapTuple does not set slot->tts_tableOid, unlike ExecStoreHeapTuple (line 1649) and ExecStoreBufferHeapTuple (line 1690) which both set slot->tts_tableOid = tuple->t_tableOid. Pre-existing, surfaced by this patch.

Verification: verified by static source comparison across sibling functions.

Repair: Add slot->tts_tableOid = tuple->t_tableOid; in the same branch.

F-3

— NIT — maintainability — No comment explaining why tts_tid must be set

Location: src/backend/executor/execTuples.c:1768

Mechanism: The patched line has no comment. ExecClearTuple invalidated tts_tid, and slot_getsysattr reads ctid from slot->tts_tid (not from the copied tuple). A future refactor might remove the line as "redundant".

Verification: verified by static analysis.

Repair: Add a two-line comment explaining the invariant restoration.

Verification Summary

FindingSeverityClassVerification StatusMethod
F-1BLOCKERtestsverifiedstatic analysis + two-run validation
F-2MINORcorrectnessverifiedstatic source comparison
F-3NITmaintainabilityverifiedstatic analysis

All three findings are verified. The test-author designed and two-run validated a replacement test for F-1 (PASS on HEAD, FAIL on BASE). The verifier independently confirmed the analysis and validated all repair patches in a disposable worktree (build + pg_regress gist PASS).

Repair Validation Summary

FindingRepairValidationMethods
F-1Replace weak test with strong test (query point(0,0), 100 pops)passedapply-check, diff-check, compile, targeted-test, two-run
F-2Add slot->tts_tableOid = tuple->t_tableOid;passedapply-check, diff-check, compile, targeted-test
F-3Add comment explaining tts_tid restorationpassedapply-check, diff-check, compile, targeted-test, static-check

Repair patches:

All patches are concrete git unified diffs bound to frozen HEAD b69356cd789fe963447177a0077c12c6c322c203.

Changed-Code Coverage

Coverage requirement: required (1 changed instrumentable path)

Changed instrumentable path: src/backend/executor/execTuples.c (line 1768)

Test commands

make -C src/backend/executor CFLAGS="-O0 -g -fprofile-arcs -ftest-coverage" execTuples.o
make -s -j4 && make -s install
initdb --no-sync -D <cov-pgdata> && pg_ctl start
rm -f src/backend/executor/*.gcda
src/test/regress/pg_regress --inputdir=src/test/regress --bindir=<inst>/bin --user=regress gist
gcov src/backend/executor/execTuples.c

Execution

Raw metrics

MetricCount
Changed executable lines1
Covered changed lines1
Compiler uncovered changed lines0
Uncovered changed lines0
Structural uncovered changed lines0
Changed lines without coverage coordinates0
Changed branches0
Covered changed branches0

Touched functions

PathFunctionLinesEnteredWhole-function linesWhole-function branches
src/backend/executor/execTuples.cExecForceStoreHeapTuple1751–1788yes (113 calls)7/8 covered3/5 covered (2 pre-existing gaps, not introduced by patch)

gcov evidence for line 1768

function ExecForceStoreHeapTuple called 113 returned 100% blocks executed 94%
      111: 1768:		slot->tts_tid = tuple->t_self;

Line 1768 (the changed line) was executed 111 times — fully covered.

Uncovered regions

None. The single changed executable line is fully covered.

Uncovered branches

None. The changed line is an unconditional assignment with no branch.

Non-instrumented changes

PathReason
src/test/regress/sql/gist.sqlTest SQL input; not compilable C, no gcov counters. Acts as the exercising harness.
src/test/regress/expected/gist.outExpected regression output; not executable, no coverage counters.

Excluded changed coordinates

None.

COV-* findings

None. The changed line is fully covered and introduces no new branch.

Security Review

No security findings. The patch is a minimal, correct, one-line fix that:

pgsql-hackers Thread Ledger

Status: no-thread-found

Reconstruction: Searched the pgsql-hackers mail archive. Found only the initial patch submission (Message-ID CAM6Zo8wZOLnCWRO_tuuXVX9J4N4JN6GsEnk8WJtT0=_0zy-1dw@mail.gmail.com, 2026-09-01, by virender.cse@gmail.com). No review replies, author follow-ups, or committer responses were found.

All three findings (F-1, F-2, F-3) have disposition new — they were not raised in the thread.

Aggregated Test Plan

  1. Strong reorder-queue regression test (F-1 repair): queries point(0,0)

to force 100 reorder-queue pops through the TTS_IS_BUFFERTUPLE branch of ExecForceStoreHeapTuple, asserts all ctids are valid, and exercises FOR UPDATE row locking. Validated: PASS on HEAD, FAIL on BASE.

  1. Coverage confirmation: gcov shows line 1768 hit 111 times by the gist

regress suite with the strong test applied.

Provenance / Model Routes

SeatAgentModel
Orchestratorpg-review-harnessglm-5.2:cloud
Patch reviewpg-patch-reviewerglm-5.2:cloud
Security reviewpg-security-reviewerglm-5.2:cloud
Test authorpg-test-authorglm-5.2:cloud
Finding verifierpg-finding-verifierglm-5.2:cloud
Coverage reviewerpg-change-coverage-reviewerglm-5.2:cloud

Model routing: user-specified override glm-5.2:cloud applied to all seats.

Deterministic Verdict

waiting-on-author

Rationale: F-1 is a verified BLOCKER (the regression test does not exercise the buggy path). The production fix itself is correct and safe, but the author must replace the weak test with the strong test before this patch is ready for committal. F-2 (MINOR) and F-3 (NIT) are recommended improvements that can be addressed in the same revision.

Residual Risk

proposal-only; the author must apply it.

reorder-queue path (all tuples from the same relation).

(informational, flagged by the patch-reviewer as out of scope).