Skip to content

warren.set_owner: ownership transfer; re-check ownership after the lock (M10 step 5) - #55

Merged
wfaithfull merged 7 commits into
mainfrom
m10-set-owner
Oct 23, 2026
Merged

wfaithfull merged 7 commits into
mainfrom
m10-set-owner

Conversation

@wfaithfull

Copy link
Copy Markdown
Contributor

This is M10 step 5 (DESIGN.md §5.7, §14 item 43).

warren.set_owner(name text, owner regrole)

  • Who may call it: the caller must be a member of the current owner, and able to SET ROLE to the new owner. On PG 16+ that is member_can_set_role; PG 15 has no SET option, so it uses is_member_of_role. A superuser always may.
  • What it does before changing anything:
    1. takes the graph's lock;
    2. opens the source queries as the new owner, under the graph's search_path: 42501 if the new owner can't read them, 42804 if the columns no longer fit;
    3. then writes owner, and readers minus the new owner;
    4. raises the writable-schema NOTICE, judged for the new owner.
  • What it leaves alone: versions, vectors and created_by. Nothing is evicted.
  • Labels: PARALLEL UNSAFE. It is added to parallel_labels, to access::guard's CALLS, and to pg_warren--0.5.0--0.6.0.sql.

A race fix that predates this PR

GraphDefinition::lock now re-reads owner and readers and re-checks ownership after the row lock. Before, a refresh, compact, promote, drop_version or contract queued behind an ownership change ran as the old owner. test/sql/set_owner_race.sql failed before the fix and passes after it.

Tests

  • 10 #[pg_test]s in catalog/owner.rs, written first. They cover transfer, readers, an unreadable source, a column mismatch, a caller who is not a member, WITH SET FALSE, a dropped role, the NOTICE, and a contraction.
  • pgrx tests on PG 16: 647 passed.
  • test/run.py on the race and docs tests, plus trusted install and the shared cache turned off.
  • schema-drift.sh: no drift.
  • test/upgrade.py on PG 16: upgrades from 0.1.0 through 0.5.0 to 0.6.0 pass.

Orphaned graphs, whose owner role was dropped, are step 6 (§14 item 45).


Generated by Claude Code

set_owner(name, owner regrole) gives a graph a new owner, as ALTER
MATERIALIZED VIEW ... OWNER TO does. The caller must be a member of the
current owner and able to SET ROLE to the new one (member_can_set_role on
PG 16+, membership on PG 15). It takes the graph's row lock, opens the
source queries as the new owner under the graph's search_path (42501 or
42804 before anything changes), keeps versions, vectors and readers (minus
the new owner), and raises create_graph's search_path notice judged for
the new owner. A contraction has no sources to open.

writable_schemas now takes the owner it judges for, and the node id type
check the build made is source::require_node_id_type, shared with
set_owner. Added to the 0.5.0 -> 0.6.0 upgrade script, parallel_labels
(UNSAFE) and access::guard's CALLS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Rxfe2L3iVjSwSBPdUu1Ke
GraphDefinition::lock re-read current_version and node_order but not
owner, and refresh, compact, promote, drop_version and contract into an
existing quotient checked ownership only before taking the lock. A call
queued behind a set_owner therefore ran with an ownership its caller no
longer had. lock now re-reads owner and readers and checks ownership again
for the action it is given. test/sql/set_owner_race.sql, written first,
failed with "refresh after the transfer is refused: OK"; it now covers
all five functions.

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

docs/semantics.md's Access section describes ownership transfer, with its
example mirrored in test/sql/docs_examples.sql. DESIGN.md §5.7 records the
implementation (member_can_set_role on PG 16+, is_member_of_role on PG 15,
lock re-checking ownership itself) and marks step 5 done; §7 and §9.3 list
set_owner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Rxfe2L3iVjSwSBPdUu1Ke
They checked ownership before the lock and never after: under READ
COMMITTED a call queued behind an uncommitted set_owner waited on the row
and EvalPlanQual then applied it to the new owner's graph. They now take
GraphDefinition::lock(action), which re-checks; drop_graph keeps letting a
superuser drop a graph whose owner was dropped, after the lock too.

set_owner_race gains grant, revoke and drop_graph cases, each seen failing
first ("... after the transfer is refused: OK"). Its cases, and
lock_reread_race's, now wait (bounded, 10 s) until the other session is
blocked on this one (pg_blocking_pids), instead of trusting dblink_is_busy
after a sleep, which is also true before the other session reaches the
lock. DESIGN.md §5.7 and §7 list every function that re-checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Rxfe2L3iVjSwSBPdUu1Ke
open_sources_as now runs under with_read_only, inside as_role and
with_search_path, as refresh reads the sources. search_path_notice checks,
from the server log, that set_owner raises the writable-schema NOTICE
judged for the new owner (a pg_test cannot read a NOTICE), so the pg_test
of the judgement drops its search_path UPDATE, which nothing read. A new
pg_test shows the old owner loses read access after a transfer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Rxfe2L3iVjSwSBPdUu1Ke
# Conflicts:
#	DESIGN.md
@wfaithfull
wfaithfull merged commit e59b0d0 into main Oct 23, 2026
16 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