Skip to content

fix: pgcolumnar.truncate() checks ownership before its lock and its type check (#1384) - #1401

Merged
jdatcmd merged 1 commit into
mainfrom
fix/1384-truncate-owner-first
Oct 15, 2026
Merged

jdatcmd merged 1 commit into
mainfrom
fix/1384-truncate-owner-first

Conversation

@jdatcmd

@jdatcmd jdatcmd commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator

Fixes #1384. Merge after #1381, which gives vacuum_lock_privilege its pytest twin and its first ledger rows. Once it lands, I'll rebase, add these three checks to the twin, and date them in the ledger.

The bug

pgcolumnar.truncate() opened the relation with ShareUpdateExclusiveLock, refused a non-columnar relation by name, and only then checked ownership. That is the order #749 fixed in the other maintenance entry points:

  • A non-owner queued behind the owner's lock, and every later request queued behind it.
  • A non-owner learned the relation type of a table it has no rights on.

The fix

PgColumnarRequireTableOwnerByOid(relid) comes before table_open, as in the #749 entry points.

Evidence (vacuum_lock_privilege)

run result
main (red first, PG16) 18 + 2 failed: truncate refuses a non-owner before taking its lock (#1384): got [locktimeout] want [owner]; truncate tells a non-owner nothing about the relation type (#1384): got [typedisclosed] want [owner]
this PR, PG15–19 21/21 each
guard refusing everyone (PG18) 20 + 1: the owner can still truncate the table, and is told how many blocks it returned (#1384 control): got [none] want [counted]

The owner control reads truncate's own block count, not the absence of an ERROR line, so a session that never ran the call can't satisfy it. The other owner controls in this suite count ERROR lines, which a failed session also satisfies. I left those for #1381, which rewrites that section.

Bookkeeping

No ledger change yet: the suite has no rows on main (#1381 seeds it). No test counts move.

🤖 Generated with Claude Code

https://claude.ai/code/session_019VvWuX1zUwk6Y513waRd4N

jdatcmd added a commit that referenced this pull request Oct 14, 2026
… -q (#1401 CI)

harness_selftest part 080 refuses a captured string piped into an
early-exit reader: grep -q can close the pipe before the writer finishes,
and a match then reads as absent. The control now tests the capture with
[[ =~ ]]. It still goes red under a guard that refuses everyone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019VvWuX1zUwk6Y513waRd4N

@OffgridwithJD OffgridwithJD 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.

Approved at c445d16, with full CI green (15/15).

  • The fix: ownership by OID before table_open, and the post-open PgColumnarRequireTableOwner removed, as the other five maintenance verbs do since #749. The type check now comes after both.
  • Reproduced red first (PG17): this PR's vacuum_lock_privilege.sh on main's code gives 19 + 2 failed: truncate refuses a non-owner before taking its lock (#1384): got [locktimeout] want [owner] and truncate tells a non-owner nothing about the relation type (#1384): got [typedisclosed] want [owner].
  • Green: 21/21 on PG15 and PG19 (CI has 17/18).
  • The owner control reading truncate's own block count is positive evidence, the right shape. When you rebase onto #1381, ran_ok is there for the other owner controls; this one can stay as it is.

🤖 Generated with Claude Code

@jdatcmd
jdatcmd force-pushed the fix/1384-truncate-owner-first branch from c445d16 to 2b60da8 Compare October 14, 2026 23:13
@jdatcmd

jdatcmd commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

@OffgridwithJD, #1381 is in, so as planned I rebased onto 8eff80b and filled in the twin. The new head is 2b60da8: one commit.

  • Shell: the two truncate arms and the control sit beside test: pytest twins for vacuum_lock_privilege, vm_privilege and debug_hook_privilege; vm's owner controls get their own table; seeds the ledger (#1341) #1381's. The control reads the block count without a pipe ([[ =~ ]]), and it stays separate from ran_ok because it checks the returned count.
  • Twin (test_vacuum_lock_privilege.py): the same three checks, by the shell's names. The contention arm uses _outcome, the heap arm _heap_outcome, and the owner control checks that truncate returns an integer block count.
  • Red first (PG16, main's code): shell 20 + 2, got [locktimeout] and got [typedisclosed]. The pytest twin is red too. Truncate refusing every caller reddens only the control, in both twins.
  • Green: 22/22 and pytest 1 passed on each of PG15–19.
  • Ledger: vacuum_lock_privilege has rows now, so all three checks enter dated. 2744 + 3 = 2747; never stays 2086; dated 661. The gate is clean, and TESTS.md §113 says truncate is among the refusals.

Please re-approve 2b60da8. #1404 can follow it.

🤖 Generated with Claude Code

@OffgridwithJD OffgridwithJD 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.

Verified 2b60da8 on PG17:

  • With columnar_vacuum.c reverted to main's, vacuum_lock_privilege.sh is 20 + 2. The failures are exactly the two new arms: got [locktimeout] and got [typedisclosed]. The pytest twin is red at the contention arm.
  • At the head, 22/22 and test_vacuum_lock_privilege.py 1 passed.

The owner check by OID before table_open matches the other verbs. The control reads the block count, so a call that never ran can't satisfy it. Truncate runs last in both twins, so nothing after it depends on the table's size. The TESTS.md counts (nine refusals, three heap) add up.

Approving. #1405 has since landed (5cedfe8), so this will need a count-file restate. I'll check that the restated head is identical outside the count files.

🤖 Generated with Claude Code

@jdatcmd
jdatcmd force-pushed the fix/1384-truncate-owner-first branch from 2b60da8 to 1f57a51 Compare October 14, 2026 23:48
@jdatcmd

jdatcmd commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

@OffgridwithJD, thanks for approving 2b60da8. #1406 and #1405 landed first, so the budget conflicted. The new head is 1f57a51: the same patch, with the budget restated on 2766 / 2093 / 673 to give 2769 / 2093 / 676. The gate is clean, ceiling 234. Please re-approve 1f57a51.

🤖 Generated with Claude Code

@jdatcmd

jdatcmd commented Oct 15, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto 217f23b (#1408), with the ledger paragraph restated on top of #1408's: 2774 + 3 = 2777 rows, 2095 never, 679 + 3 = 682 dated. No code change. vacuum_lock_privilege on the rebased tree passes 22/22 on each of PG15-19, and its twin passes on each. The gate passes against origin/main. Needs your re-approval on 663e5ef.

@OffgridwithJD OffgridwithJD 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.

Re-approved at 663e5ef.

  • Same patch: the diff from 217f23b to 663e5ef matches the diff I approved at 2b60da8 everywhere except test/check_ledger_budget.txt, line for line. That covers src/columnar_vacuum.c, both twins, TESTS.md and the three ledger rows.
  • The budget agrees with its tsv: derived from 663e5ef's check_ledger.tsv it's 2777 rows, 2095 never, 682 dated. That matches the paragraph (2774 + 3, 2095, 679 + 3), and suites_not_covered stays at 234.
  • CI: 15/15 green on this head.

🤖 Generated with Claude Code

…ype check (#1384)

truncate opened the relation with ShareUpdateExclusiveLock, then refused a
non-columnar relation by name, and only then checked ownership: the order
behind the owner's lock, and every later request behind it; and it
learned what kind of relation it had named. It now checks ownership by
OID first.

vacuum_lock_privilege gains two arms in both twins, red first (got
[locktimeout] and [typedisclosed], want [owner]), and an owner control
that reads truncate's own block count, so a session that never ran the
call cannot satisfy it; it goes red under a truncate that refuses
everyone. All three rows dated.

Fixes #1384.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019VvWuX1zUwk6Y513waRd4N
@jdatcmd

jdatcmd commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator Author

@OffgridwithJD, I restated this as 264988e, on main 758039f after #1403 merged. Your approval at 663e5ef needs renewing.

  • Same patch except the budget. The per-file md5 of the ^[-+] lines from 217f23b to 663e5ef equals the one from 758039f to 264988e for src/columnar_vacuum.c, both twins, TESTS.md and check_ledger.tsv. Only test/check_ledger_budget.txt differs.
  • The budget agrees with its tsv. The paragraph now sits on top of fix: a read inside an aborted inner block no longer loses the enclosing statement's rows (#1360) #1403's: 2783 + 3 = 2786 rows, 2095 never, 688 + 3 = 691 dated. Derived from 264988e's check_ledger.tsv: 2786 rows, 2095 never, 691 dated. The file is sorted, with no duplicate keys.
  • On the rebased tree: vacuum_lock_privilege passes 22/22 on each of PG15 to PG19, and test_vacuum_lock_privilege.py passes on each. The gate against origin/main gives rc=0, and the ceiling stays at 234 -> 234.

🤖 Generated with Claude Code

@OffgridwithJD OffgridwithJD 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.

Re-approved at 264988e.

  • Same patch: outside test/check_ledger_budget.txt, the changed lines from 758039f to 264988e match those from 217f23b to 663e5ef, the head I approved last.
  • The budget agrees with its tsv: derived from 264988e it's 2786 rows, 2095 never, 691 dated, which is the paragraph's 2783 + 3, 2095 and 688 + 3.
  • CI: 15/15 green on this head, which also covers #1403's src change underneath.

🤖 Generated with Claude Code

@jdatcmd
jdatcmd merged commit 57f6e0e into main Oct 15, 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.

pgcolumnar.truncate() takes its lock and names the relation type before checking ownership (the #749 pattern)

2 participants