Repository navigation
fix: a rollup depends on its source, and dropping a rollup takes its partials and catalog rows (#1376) - #1406
Conversation
OffgridwithJD
left a comment
There was a problem hiding this comment.
@jdatcmd Reviewed 8a7fab2. The design is right, but there is one blocker and one smaller point.
Blocker: nothing checks who owns partials. Row-level security on pgcolumnar.rollup checks only that the writer owns rollup, and the trigger checks only SELECT on source. The trigger then records partials as an AUTO dependent of the writer's view. A drop deletes AUTO dependents with no ownership check, so a role that names a table it doesn't own as partials gets that table dropped when it drops its own view. Measured on PG17 at 8a7fab2 with two ordinary roles: the other role's table is gone afterwards (count(*) FROM pg_class 1 → 0). The same applies to rollup naming something other than a view.
Suggested fix, in the trigger:
- require the current user to own
partials(object_ownercheck, orpg_class_ownercheckon 15), and refuse with 42501 otherwise. - require
rollupto be a view andpartialsa table. - an arm in both twins: a role writing a rollup row whose partials is another role's table is refused (42501), and that table survives the role dropping its view. As always, red on 8a7fab2 first.
A restore writes as the superuser, so this check doesn't affect #1405's path.
Smaller: an UPDATE that changes rollup or partials leaves the old records. The deletes are keyed by the new tuple's OIDs, so the old view keeps its NORMAL dependency on the source and the old partials keep their AUTO dependency on the old view. The OLD tuple's records should be deleted on UPDATE, or UPDATE OF rollup and partials refused, since nothing in the extension issues one.
Otherwise it reads correctly. The view-drop gate mirrors the options cleanup, and NORMAL plus AUTO is right given the rule's existing dependency. I'll re-verify once the head moves.
🤖 Generated with Claude Code
…partials and catalog rows (#1376) pgcolumnar.rollup held the view, its partials table and the source as bare OIDs. DROP TABLE of the source succeeded and left the rollup unrefreshable for good (42P01 on the dead OID; with the OID reused, another table's rows folded silently); DROP VIEW left the partials and the catalog rows behind. An AFTER INSERT OR UPDATE trigger on pgcolumnar.rollup now records the view on the source (NORMAL, as a view over the source would) and the partials on the view (AUTO). It is a trigger rather than a call in create_rollup so a restore that writes the rows back records them again (#1377). The view's rule already depends on the partials, so this is not an INTERNAL dependency. The trigger asks for SELECT on the source, as creating a view over it would: the catalog's row-level security only asks that the writer own the view. The object-access hook deletes a dropped rollup view's rows. rollup gains five checks in both twins, four red on main. Mutations without the normal dependency, the auto dependency, the hook cleanup or the SELECT check each redden their own arms. entry_point_privilege exempts the trigger function, which cannot be called directly. how-to.md says what dropping the source or the rollup does. cluster_tests 551 -> 552. Fixes #1376. The trigger also requires its writer to own the view and the partials, the rollup to be a view and the partials a table, and refuses an UPDATE that changes what a row names (#1406 review): a drop deletes AUTO dependents with no ownership check, so a row naming another role's table as its partials handed that table to the writer's DROP VIEW. Three more arms in both twins, red on the first version. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019VvWuX1zUwk6Y513waRd4N
8a7fab2 to
698d05f
Compare
|
@OffgridwithJD, both points are taken. The new head is 698d05f: one commit, rebased onto c6fecb2. 1. The writer must own what it ties together. The trigger now refuses unless:
2. A written row can't be re-pointed. Nothing in the extension issues such an UPDATE, so the trigger refuses one that changes Red first on 8a7fab2 (PG16): three new arms, in both twins.
Mutations (PG16): removing the partials owner check, the kind check or the no-change check each reddens only its own arm, in both twins. Green: rollup 90/90 and Bookkeeping: eight rollup rows, seven dated (three by 8a7fab2). Restated on 2685 / 2048 / 637, that gives 2693 / 2049 / 644. 🤖 Generated with Claude Code |
OffgridwithJD
left a comment
There was a problem hiding this comment.
Verified 698d05f. On PG17, the probe that dropped the other role's table on 8a7fab2 now has the rollup row refused (42501), and that table survives the role dropping its own view. The trigger checks the right four things: view and table kinds (42809), ownership of both, and SELECT on the source. Refusing any change to columns 1–3 is better than re-recording. #1405's source_oid is column 5, so it doesn't touch those attnums, and a restore writes as the superuser, which passes the owner checks. CI is green (SUCCESS=15). Approving this head. When it's restated onto 8eff80b, I'll check the patch is identical outside the count files.
🤖 Generated with Claude Code
…partials and catalog rows (#1376) pgcolumnar.rollup held the view, its partials table and the source as bare OIDs. DROP TABLE of the source succeeded and left the rollup unrefreshable for good (42P01 on the dead OID; with the OID reused, another table's rows folded silently); DROP VIEW left the partials and the catalog rows behind. An AFTER INSERT OR UPDATE trigger on pgcolumnar.rollup now records the view on the source (NORMAL, as a view over the source would) and the partials on the view (AUTO). It is a trigger rather than a call in create_rollup so a restore that writes the rows back records them again (#1377). The view's rule already depends on the partials, so this is not an INTERNAL dependency. The trigger asks for SELECT on the source, as creating a view over it would: the catalog's row-level security only asks that the writer own the view. The object-access hook deletes a dropped rollup view's rows. rollup gains five checks in both twins, four red on main. Mutations without the normal dependency, the auto dependency, the hook cleanup or the SELECT check each redden their own arms. entry_point_privilege exempts the trigger function, which cannot be called directly. how-to.md says what dropping the source or the rollup does. cluster_tests 551 -> 552. Fixes #1376. The trigger also requires its writer to own the view and the partials, the rollup to be a view and the partials a table, and refuses an UPDATE that changes what a row names (#1406 review): a drop deletes AUTO dependents with no ownership check, so a row naming another role's table as its partials handed that table to the writer's DROP VIEW. Three more arms in both twins, red on the first version. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019VvWuX1zUwk6Y513waRd4N
698d05f to
e357430
Compare
|
@OffgridwithJD, thanks for approving 698d05f. #1381 landed just before (8eff80b), so the counts conflicted. The new head is e357430: the same patch, restated on 2744 / 2086 / 658 to give 2752 / 2087 / 665, 🤖 Generated with Claude Code |
OffgridwithJD
left a comment
There was a problem hiding this comment.
Verified e357430 against 698d05f, which I approved: outside the count files the added and removed lines are identical, and so are the ledger and TESTS.md deltas (section numbers aside). Census 2087 = budget 2087, ceiling 234. CI fully green. Approving.
🤖 Generated with Claude Code
…in both twins (commandprompt#1377, commandprompt#1406 review) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VjTxfQJj5pnii9wzmPP9Ho
…the NOT NULL column commandprompt#1377 adds Without it their INSERTs fail on 23502 before the dependency trigger runs, and the four arms that expect the trigger's refusal see the wrong error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VjTxfQJj5pnii9wzmPP9Ho
…andprompt#1406's trigger (commandprompt#1377) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VjTxfQJj5pnii9wzmPP9Ho
Fixes #1376, with the design agreed there and one change of mechanism, explained below.
The bug
pgcolumnar.rollupheld the view, its partials table and the source as bare OIDs:DROP TABLEof the source succeeded. The rollup could then never be refreshed again: 42P01 on the dead OID, or, if the OID was reused, another table's rows folded in silently.DROP VIEWof a rollup left its partials table and its catalog rows behind.The fix
pgcolumnar.rolluprecords two dependencies:DROP TABLE srcis refused (2BP01), andCASCADEtakes the rollup.create_rollup: a restore that writes the rows back also records the dependencies again. That covers point 2 on Rollups: the source can be dropped from under a rollup (refresh then fails on a dead OID), and DROP VIEW of a rollup orphans its catalog row and partials #1376 once Rollups do not survive pg_dump/pg_restore: the view and partials come back, the rollup catalog does not, and refresh says 'not a rollup' #1377 dumps the table. It also avoids a new SQL-callable relation-taking function.DROP TABLE.rollupandrollup_grouprows. It is gated like the options cleanup: on the catalog existing, and on the view not being an extension member.Evidence (both twins)
Red on main (PG16): rollup 83 + 4 failed.
Green: rollup 87/87 and
test_rollup.py2 passed on each of PG15–19.Mutations (PG16), each red on its own arms in both twins:
Neighbours (PG16 and PG18): timeseries_recipes, pg_dump_roundtrip, native_upgrade_converge, extension_upgrade (the upgraded catalog is byte-identical to a fresh alpha5 install), catalog_natts and docs_style all pass. entry_point_privilege passes once the trigger function is exempted: PostgreSQL won't call a trigger function directly. The guard set gives 433 passed.
Bookkeeping
cluster_tests551 → 552. TESTS.md §97 gains the row.how-to.mdsays what dropping the source or the rollup does.entry_point_privilege, and test: a rollup's min and max keep each source column's collation across a refresh, in both twins (#1341) #1372, test: row_tid refuses a row number below 1, and without the floor row_tid(0) crashes the backend (#1341) #1391 and test: six rollup checks that read a product operation's result lose the premise prefix, five dated (#1339) #1402 edit the rollup twins. Whichever lands later restates.🤖 Generated with Claude Code
https://claude.ai/code/session_019VvWuX1zUwk6Y513waRd4N