Fix CREATE EXTENSION on PG16+ for non-superuser installs (#14) - #18
Merged
jnasbyupgrade merged 19 commits intoAug 12, 2026
Merged
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
jnasbyupgrade
force-pushed
the
fix/issue-14-clean
branch
from
July 14, 2026 22:26
024fff0 to
fcec706
Compare
jnasbyupgrade
added a commit
to jnasbyupgrade/test_factory
that referenced
this pull request
Jul 15, 2026
eb84bc6 Stamp 2.1.0 9fbe6f4 Fix results ordering, control file whitespace, ENABLE_* override, debug levels (Postgres-Extensions#31) 88bb4f2 Add Claude Code GitHub Actions workflows (Postgres-Extensions#41) af5bbbb ci: pass repo owners to run-tests.yml for fork-account matching (Postgres-Extensions#40) c7928af Fix repo-root guard to work inside a git worktree (Postgres-Extensions#39) b062fca ci: point reusable test workflow at @master b6cdbfd Add CI workflows and multi-session PR guard (Postgres-Extensions#33) 1ba0987 Stamp 2.0.3 1931cbe Fix pgxntool-sync remote and make it runnable without make (Postgres-Extensions#37) 8176304 Stamp 2.0.2 3e142ab Fix parse_control_file: remove comments before stripping quotes (Postgres-Extensions#27) cacc301 Stamp 2.0.1 bf1db6b Fix bash 3.2 / Linux compatibility issues (Postgres-Extensions#26) 62d0fcb Fix broken ifeq for --load-language=plpgsql on PG < 13 (Postgres-Extensions#24) 121f0b3 Stamp 2.0.0 ad3ca7e Remove .source support; add test/install, test/build, and verify-results (Postgres-Extensions#18) c010cf8 Fix bash 3.2 compatibility (Postgres-Extensions#23) abeb9d3 Remove .source file support from pg_regress integration (Postgres-Extensions#22) 08c1879 Stamp 1.1.2 6e0dad2 Fix double --dbname bug that defeated unique test database names 639756c Stamp 1.1.1 6ba3176 Fix pg_tle exception handler and empty upgrade files (Postgres-Extensions#15) 3b8cb2a Stamp 1.1.0 550a901 Remove commit.md (maintained in pgxntool-test) d73ca93 Add unique test database names to prevent conflicts (Postgres-Extensions#13) 9b344be Add update-setup-files.sh for 3-way merging after pgxntool-sync (Postgres-Extensions#12) ab7f6e2 Stamp 1.0.0 3a571ba Add pg_tle support and modernize test infrastructure (Postgres-Extensions#11) b96ea6d Add support for Claude code; build and doc improvements (Postgres-Extensions#9) e9c24de Fix pg_regress on versions > 12 (Postgres-Extensions#5) REVERT: bed3604 Fix pg_regress on versions > 12 (Postgres-Extensions#5) (Postgres-Extensions#6) git-subtree-dir: pgxntool git-subtree-split: eb84bc6e87e21f2ced11ff1b8ddb4028b7c67c8f
jnasbyupgrade
added a commit
to jnasbyupgrade/test_factory
that referenced
this pull request
Jul 16, 2026
eb84bc6 Stamp 2.1.0 9fbe6f4 Fix results ordering, control file whitespace, ENABLE_* override, debug levels (Postgres-Extensions#31) 88bb4f2 Add Claude Code GitHub Actions workflows (Postgres-Extensions#41) af5bbbb ci: pass repo owners to run-tests.yml for fork-account matching (Postgres-Extensions#40) c7928af Fix repo-root guard to work inside a git worktree (Postgres-Extensions#39) b062fca ci: point reusable test workflow at @master b6cdbfd Add CI workflows and multi-session PR guard (Postgres-Extensions#33) 1ba0987 Stamp 2.0.3 1931cbe Fix pgxntool-sync remote and make it runnable without make (Postgres-Extensions#37) 8176304 Stamp 2.0.2 3e142ab Fix parse_control_file: remove comments before stripping quotes (Postgres-Extensions#27) cacc301 Stamp 2.0.1 bf1db6b Fix bash 3.2 / Linux compatibility issues (Postgres-Extensions#26) 62d0fcb Fix broken ifeq for --load-language=plpgsql on PG < 13 (Postgres-Extensions#24) 121f0b3 Stamp 2.0.0 ad3ca7e Remove .source support; add test/install, test/build, and verify-results (Postgres-Extensions#18) c010cf8 Fix bash 3.2 compatibility (Postgres-Extensions#23) abeb9d3 Remove .source file support from pg_regress integration (Postgres-Extensions#22) 08c1879 Stamp 1.1.2 6e0dad2 Fix double --dbname bug that defeated unique test database names 639756c Stamp 1.1.1 6ba3176 Fix pg_tle exception handler and empty upgrade files (Postgres-Extensions#15) 3b8cb2a Stamp 1.1.0 550a901 Remove commit.md (maintained in pgxntool-test) d73ca93 Add unique test database names to prevent conflicts (Postgres-Extensions#13) 9b344be Add update-setup-files.sh for 3-way merging after pgxntool-sync (Postgres-Extensions#12) ab7f6e2 Stamp 1.0.0 3a571ba Add pg_tle support and modernize test infrastructure (Postgres-Extensions#11) b96ea6d Add support for Claude code; build and doc improvements (Postgres-Extensions#9) e9c24de Fix pg_regress on versions > 12 (Postgres-Extensions#5) REVERT: bed3604 Fix pg_regress on versions > 12 (Postgres-Extensions#5) (Postgres-Extensions#6) git-subtree-dir: pgxntool git-subtree-split: eb84bc6e87e21f2ced11ff1b8ddb4028b7c67c8f
3 tasks
jnasbyupgrade
added a commit
to jnasbyupgrade/test_factory
that referenced
this pull request
Jul 29, 2026
b67064b Release 2.2.0 (#77) d949d6d Fix stale README/CLAUDE.md docs; rename internal-only test shim (#76) 6d7822c ci: grant actions:write so bun cache save succeeds (#75) 2738f59 README.asc: fix stale docs, document undocumented API surface (#74) d1d3bee Add make pgxntool-version to print the embedded pgxntool version (#73) 493a0f8 Fix parallel-build SQL corruption, remote hardcoding, and add safety checks (#61) 3b0e3de Fix make pgtle PGTLE_VERSION=X being silently ignored (#66) 21b35ef ci: skip paired-test-PR requirement for doc-only PRs (#71) 55efd03 Rename EXTENSION_VERSION_FILES to EXTENSION__CURRENT_VERSION__FILES (#69) 9d0a856 Fix pgtle.sh rejecting non-numeric version aliases like 'stable' (#57) (#63) 9c2f75f Docs: versioned SQL file tracking tradeoffs and update-testing pattern (#60) c38cf2a ci: never check out fork PR head in claude-code-review pull_request_target job (#70) aab8f2b pgtle_versions.md: fix wrong uninstall_extension() version claim (#59) e2c9174 README.asc: make tag creates a git tag, not a branch (#58) eb84bc6 Stamp 2.1.0 9fbe6f4 Fix results ordering, control file whitespace, ENABLE_* override, debug levels (Postgres-Extensions#31) 88bb4f2 Add Claude Code GitHub Actions workflows (Postgres-Extensions#41) af5bbbb ci: pass repo owners to run-tests.yml for fork-account matching (Postgres-Extensions#40) c7928af Fix repo-root guard to work inside a git worktree (Postgres-Extensions#39) b062fca ci: point reusable test workflow at @master b6cdbfd Add CI workflows and multi-session PR guard (Postgres-Extensions#33) 1ba0987 Stamp 2.0.3 1931cbe Fix pgxntool-sync remote and make it runnable without make (Postgres-Extensions#37) 8176304 Stamp 2.0.2 3e142ab Fix parse_control_file: remove comments before stripping quotes (Postgres-Extensions#27) cacc301 Stamp 2.0.1 bf1db6b Fix bash 3.2 / Linux compatibility issues (Postgres-Extensions#26) 62d0fcb Fix broken ifeq for --load-language=plpgsql on PG < 13 (Postgres-Extensions#24) 121f0b3 Stamp 2.0.0 ad3ca7e Remove .source support; add test/install, test/build, and verify-results (Postgres-Extensions#18) c010cf8 Fix bash 3.2 compatibility (Postgres-Extensions#23) abeb9d3 Remove .source file support from pg_regress integration (Postgres-Extensions#22) 08c1879 Stamp 1.1.2 6e0dad2 Fix double --dbname bug that defeated unique test database names 639756c Stamp 1.1.1 6ba3176 Fix pg_tle exception handler and empty upgrade files (Postgres-Extensions#15) 3b8cb2a Stamp 1.1.0 550a901 Remove commit.md (maintained in pgxntool-test) d73ca93 Add unique test database names to prevent conflicts (Postgres-Extensions#13) 9b344be Add update-setup-files.sh for 3-way merging after pgxntool-sync (Postgres-Extensions#12) ab7f6e2 Stamp 1.0.0 3a571ba Add pg_tle support and modernize test infrastructure (Postgres-Extensions#11) b96ea6d Add support for Claude code; build and doc improvements (Postgres-Extensions#9) e9c24de Fix pg_regress on versions > 12 (Postgres-Extensions#5) REVERT: bed3604 Fix pg_regress on versions > 12 (Postgres-Extensions#5) (Postgres-Extensions#6) git-subtree-dir: pgxntool git-subtree-split: b67064b16ca2eb30761c078511dc8c974938480f
jnasbyupgrade
added a commit
to jnasbyupgrade/test_factory
that referenced
this pull request
Jul 30, 2026
…ions#18 test gap Mark both control files `superuser = false` (the pre-PG13 mechanism, not `trusted` -- `trusted` is an unrecognized control-file key on PG10-12, which this project's CI still tests, and errors out entirely there, not just for non-superuser attempts). Verified empirically against this container's PG12 and PG17 clusters. test/sql/install.sql now creates a disposable NOSUPERUSER + CREATEROLE role (mirroring what a real RDS/Aurora master user has) and installs through it via SET SESSION AUTHORIZATION, replacing the indirect pg_auth_members proxy check from Postgres-Extensions#18 with a genuine end-to-end repro: before the Postgres-Extensions#18 fix this fails with "must be able to SET ROLE test_factory__owner"; after the fix it succeeds. Two extra grants were needed beyond CREATEROLE, found by actually running this rather than reasoning about it: USAGE on the tap schema (a pgtap test-harness necessity, unrelated to what's under test) and CREATE on the current database (never granted to PUBLIC by default -- only CONNECT/TEMP are -- unlike what I'd assumed). Also fixed a real local-iteration flakiness this surfaced: test_factory__owner is deliberately left behind by DROP EXTENSION so a real install/uninstall cycle by the same installer keeps working, but this test creates a fresh disposable installer role every run, so an orphaned owner role from a previous run of this file belongs to an installer that no longer exists, breaking the GRANT ... WITH SET. install.sql now drops both roles at start and end. Verified stable across many repeated `make test` runs against the same cluster. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
3 tasks
…ruser As of PG16, CREATE ROLE no longer grants the creating role a SET-enabled membership in the new role, so the install's `SET ROLE test_factory__owner` fails unless the current role is a superuser (which bypasses the check). This surfaces only on non-superuser installs (e.g. RDS/Aurora). Grant the role back to the installing role WITH SET, gated on PG16+ (pre-16 GRANT already permits SET ROLE). Unconditional, so it also covers a pre-existing role where CREATE ROLE was a no-op. Fixes Postgres-Extensions#14 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Postgres-Extensions#14) The failure can't be reproduced under pg_regress, which runs as a superuser that bypasses the SET ROLE check. Instead assert the state the fix establishes: after install the installing role holds a SET-enabled membership in test_factory__owner (PG16+; skipped with identical output pre-16). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
master gained test/build/syntax.sql since fix/issue-14-clean branched, which re-runs sql/test_factory.sql bare (via \i) right after test/install/load.sql already installed the extension in the same database. That surfaced two problems the original fix never hit: - The unconditional re-GRANT emitted a NOTICE naming the installing role on this second run, making test/build/expected/syntax.out's content depend on which role runs the install (confirmed by reproducing locally as both "root" and "postgres" -- CI runs as "postgres", this container's shell runs as "root"). Guard the GRANT on an existing pg_auth_members SET-enabled membership check so it's a true no-op (no NOTICE) the second time. - That existence check referenced pg_auth_members.set_option directly, which doesn't exist before PG16. PL/pgSQL parses a query's text as soon as it reaches the statement, regardless of whether the surrounding IF branch ends up running, so this broke test-build on PG12 even though the check was already gated on server_version_num. Moved the check into the same EXECUTE-a-format()'d-string pattern already used for the GRANT, deferring the parse to runtime, only inside the PG16+ branch. Verified via `make lint` and `make test` on both PG17 and PG12 (the PG12 run confirms the pre-16 branch produces identical passing output), plus PG17 under TEST_LOAD_SOURCE=update and connected as role "postgres" (matching CI's PGUSER) to confirm the syntax.out fix isn't role-name-dependent. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
jnasbyupgrade
force-pushed
the
fix/issue-14-clean
branch
from
August 10, 2026 19:23
d03e425 to
9582bbd
Compare
…ndary Binary pg_upgrade does not re-run an extension's install script -- it reconstructs catalog objects directly to preserve OIDs/relfilenodes for the physical file copy. That means the WITH SET role-membership fix for issue Postgres-Extensions#14 (GRANT test_factory__owner TO <role> WITH SET TRUE, added in this branch) only has a chance to run once, at original install time; a pg_upgrade that crosses PG16 (where set_option/WITH SET was introduced) can't apply or repair it after the fact. CI's pg-upgrade-test job previously had a leg (17 -> 18) that stayed on one side of PG16 and a leg (10 -> 18) that crossed it, and that crossing leg was intermittently failing the new regression test for reasons outside test_factory's own SQL. Since nothing in this extension can fix that pg_upgrade limitation, replace the two legs with 10 -> 15 (entirely pre-16) and 16 -> 18 (entirely post-16), so CI deliberately never exercises the one upgrade path that's known not to work, instead of flaking on it. Document the new rationale in the job's comment and add a matching "Known limitation" section to README.md with the manual GRANT ... WITH SET TRUE workaround for anyone who hits this after a real pre-16 -> 16+ pg_upgrade. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ment and README sql/test_factory.sql declared three function parameters as _tf._test_factory.set_name%TYPE, which Postgres can't preserve exactly in a function's formal parameter list -- it silently resolves to the underlying type (text, confirmed against the table definition) and emits a NOTICE every time the function is created. This has always been present (confirmed on master before any of this session's changes) and was being masked in every real install by test/install/load.sql's own `SET client_min_messages = WARNING`, which only exists for the test harness's own signal-to-noise, not as a substitute for fixing the extension itself -- a real CREATE EXTENSION at psql's default message level shows this notice today. Since the type is already known and %TYPE buys nothing for a function parameter (resolved once at CREATE FUNCTION time either way), declaring it as plain text removes the notice at its source instead of relying on a caller to suppress it. Regenerated test/build/expected/syntax.out, which now shows only the two already-known pg_extension_config_dump() errors, no NOTICE lines. Verified locally on PG12 and PG17, fresh and update modes. Also: shortened the pg-upgrade-test job's comment in ci.yml (the reasoning for the two-legs-avoiding-PG16 split doesn't need restating at length -- "pg_upgrade doesn't re-run the script" is the whole point), and tightened the README's "Known limitation" section -- stated what actually happens instead of hedging with "may", and dropped the CI-can't-test-this framing entirely (not something a user-facing README needs, and "can't" overstated it anyway -- it's a choice not to invest in covering this path, not an inherent limitation).
…e script itself %TYPE on set_name parameters ties them to _test_factory.set_name's real column type, so a future column-type change can't silently drift out of sync with a hardcoded `text` -- restoring it, with a comment explaining why it's there despite the NOTICE it prints. Both that NOTICE and the GRANT-already-granted NOTICE from the previous commit are genuinely harmless install-time noise with no cleaner structural fix available (%TYPE can't be preserved exactly in a function's parameter list; re-granting an already-current membership is an intentional no-op, not a bug). The right place to quiet them is the install script itself, once, via SET LOCAL client_min_messages = WARNING right after saving the caller's original role -- SET LOCAL reverts automatically at the end of the install's own transaction, so it never leaks into the calling session, unlike relying on a caller (or, previously, test/install/load.sql) to set client_min_messages themselves. This also means the GRANT no longer needs its own existence-check guard against the notice -- back to a plain unconditional EXECUTE. Verified directly against a live database, not just the test suite: a genuinely fresh CREATE EXTENSION at psql's default message level, and a DROP/CREATE EXTENSION re-install with the role and grant already in place, both produce zero NOTICE output. Regenerated test/build/expected/syntax.out (now shows only the two known pg_extension_config_dump() errors, nothing else). Verified locally on PG12 and PG17, fresh and update modes.
…rsight Doesn't need justifying inline every time it's used.
…e-grant Caught in review (PR Postgres-Extensions#18 discussion): GRANT <role> TO <user> requires the grantor to hold ADMIN OPTION on the target role (or be superuser) to run AT ALL -- regardless of whether the grant would end up a no-op. Running it unconditionally broke an already-working setup: a role with pre-existing SET-enabled membership on test_factory__owner (however it got there -- e.g. a DBA pre-provisioned the role and granted SET directly, deliberately withholding ADMIN OPTION as a least-privilege measure) would previously install fine, but this fix's own GRANT attempt now fails with "permission denied to grant role ... Only roles with the ADMIN option ... may grant this role" -- a regression this PR introduced, confirmed against a live non-superuser role in exactly that configuration. Fixed by gating the GRANT on pg_has_role(current_user, 'test_factory__owner', 'SET') -- skip it entirely when already true. pg_has_role's 'SET' privilege type is PG16+ only, but as a plain function argument (not a catalog column reference) it's safe to call inside the same version-gated branch without needing EXECUTE to defer parsing, unlike the earlier pg_auth_members.set_option approach -- confirmed this doesn't break PG12. If the installer has neither SET-enabled membership nor ADMIN OPTION to grant it themselves, installation genuinely cannot proceed -- nothing in this script can grant a privilege on someone else's behalf. Catch that specific permission failure and raise a clear, actionable error naming exactly who needs to run what, instead of letting Postgres's generic "permission denied to grant role" surface. Verified against a live non-superuser role with neither SET nor ADMIN OPTION. (Hit and fixed a real bug in the fix itself while verifying live: RAISE's %-substitution is plain string interpolation, not format()'s %I/%L -- using %I directly in a RAISE message string produced "roleI" instead of a quoted identifier. Built the suggested command with format() first, then substituted the whole result in with an ordinary %.) README: broadened the "Known limitation" section -- the SET-enabled membership requirement isn't only a pg_upgrade concern; a fresh install by a role lacking both SET-enabled membership and ADMIN OPTION hits the same underlying requirement, just detected immediately now instead of failing later. Kept the pg_upgrade case separately, since that one genuinely can't be caught up front (no install script runs during a binary upgrade) and still needs the same manual GRANT afterward. Verified locally on PG12 and PG17 (fresh and update modes), plus directly against a live database: an installer with pre-existing SET-enabled membership but no ADMIN OPTION now installs cleanly (no failed GRANT attempt), and an installer with neither gets the new, clear error message.
CI caught this immediately (all PG10-18 jobs failed identically): the previous version of this test queried pg_auth_members directly for a literal grant row naming the installing role. That's not what the fix actually guarantees, and it stopped being true the moment the GRANT became conditional (previous commit) -- CI's installer is a real superuser (`postgres`), and a superuser always has effective SET privilege on every role via bypass, without needing (or, now, receiving) an explicit grant. The now-conditional GRANT correctly skips granting a superuser something they don't need, so the literal catalog row this test checked for was never created, and the assertion failed even though the underlying property (can this role SET ROLE test_factory__owner) was never actually false. Fixed by checking pg_has_role(current_user, 'test_factory__owner', 'SET') instead -- the same idiom the fix's own gating logic already uses, and the actual property in question. Correctly returns true for a superuser via bypass (no grant needed) and true for a non-superuser with an explicit grant (however they got it), matching what "SET-enabled membership" is actually supposed to mean here. My own local verification runs had been passing throughout today's earlier commits by accident: leftover GRANT ... WITH SET TRUE state from manual testing earlier in this session had polluted the shared local cluster, making the old catalog-row check pass locally against contaminated state that CI's genuinely fresh cluster never had. Re-verified this fix against a manually-cleaned local state (REVOKE test_factory__owner FROM root, postgres) on both PG17 and PG12, fresh and update modes -- confirmed the assertion now passes correctly, and confirmed no catalog grant gets created for a superuser install either way.
…d's job Per ../ai/CODE_STYLE.md (already-merged, authoritative policy that names this exact repo/PR as one of several where this mistake was reintroduced): CREATE EXTENSION/ALTER EXTENSION UPDATE already forces client_min_messages up to at least WARNING for the duration of an install script, restoring the caller's original setting the moment the script finishes -- confirmed directly against execute_extension_script() in Postgres's own source, and verified empirically here: a real CREATE EXTENSION never showed the %TYPE NOTICE in the first place, with or without my own SET LOCAL, because Postgres was already suppressing it. Adding it in the script itself was redundant at best, and worse than doing nothing in general: Postgres's mechanism only ever *raises* the level, but an unconditional SET LOCAL unconditionally *lowers* a caller who set something stricter (e.g. ERROR). Moved the suppression to test/build/syntax.sql instead, immediately before the \i -- that's the one place that actually needs it, since running the script bare via \i gets none of CREATE EXTENSION's built-in handling. Also fixed a bare `issue Postgres-Extensions#14` reference in ci.yml to a full URL, per ../ai/CODE_STYLE.md's rule for references to a still-relevant known limitation (as opposed to purely historical context, where a bare number is fine). Verified locally on PG12 and PG17, fresh and update modes, against a manually-cleaned (non-polluted) local role state.
…al coverage CREATE ROLE never grants the creator any relationship to the role it just created, on ANY PostgreSQL version -- not just PG16+ as the original fix assumed. This was only caught by actually running the suite as a genuine non-superuser: pre-16, `SET ROLE test_factory__owner` failed outright with zero prior grant, even though the original fix's comment claimed plain membership was already conferred automatically. Added the same GRANT-if-missing logic (gated on pg_has_role's 'MEMBER' privtype instead of 'SET') to both sql/test_factory.sql's test_factory__owner grant and test/install/load.sql's own test_role bootstrap, which has the identical issue. Added `superuser = false` to both control files -- previously neither set it, so `CREATE EXTENSION` was only reachable by an actual superuser on stock PostgreSQL regardless of role grants, making the whole fix unreachable via the extension's own documented install path (per PR Postgres-Extensions#18 review). Added bin/test_nonsuperuser and a new `test-nonsuperuser` CI job (matrixed across every supported major, mirroring `test`) that runs the full suite through a disposable, real non-superuser installer role. Without this, `pg_has_role(current_user, ...)` is unconditionally true for the superuser CI otherwise runs as, so neither the fix's own gating logic nor test/sql/base.sql's regression assertion could ever fail regardless of whether the underlying GRANT logic worked (per PR Postgres-Extensions#18 review). Also tightened base.sql's pre-16 branch, previously a literal `ok(true, ...)` with zero coverage, to the same pg_has_role-based check as PG16+. Updated README's "Known limitation" section to reflect that CREATE EXTENSION now genuinely reaches this scenario for a suitably-privileged non-superuser, covering both the PG16+ and pre-16 cases, and documented the new CI mechanism in test/CLAUDE.md. Verified locally on PG12 and PG17: fresh and update modes as superuser (no regressions), plus fresh mode as a genuine disposable non-superuser role, all passing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tion tf.register()/tf.get() never run SET ROLE -- they go through SECURITY DEFINER functions (_tf.test_factory__set, _tf.get, etc, all owned by test_factory__owner), which transparently run as the owner without any role-membership check. SET ROLE only appears once, inside the install script itself (sql/test_factory.sql:108). A missing SET-enabled grant can only bite the next install/reinstall action that re-runs the script, not ordinary use of the public API. Per PR Postgres-Extensions#18 review. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tra CI leg Per feedback: running the whole suite twice per PostgreSQL major (once as CI's superuser, once as a separate disposable non-superuser role via bin/test_nonsuperuser + a parallel test-nonsuperuser CI job) was overkill. A superuser bypasses every role-membership check involved in issue Postgres-Extensions#14's fix, so if a properly-privileged non-superuser can install successfully, a superuser certainly can too -- there's no distinct superuser-only code path worth testing separately. test/install/load.sql's fresh/update branch now creates a non-login test_factory_installer role (CREATEROLE only, no superuser bypass) and switches to it via SET SESSION AUTHORIZATION before running CREATE EXTENSION/ALTER EXTENSION UPDATE -- no separate connection, password, or CI job needed, since the switch only affects load.sql's own already-authenticated session. Every mode this suite runs in (CI's superuser, a developer's local superuser) now exercises the same non-superuser install path automatically. Removed bin/test_nonsuperuser and the parallel test-nonsuperuser CI job entirely. Moved the issue Postgres-Extensions#14 regression check out of test/sql/base.sql (which runs in a SEPARATE connection from load.sql, so it was checking pg_has_role for the wrong role -- whatever ambient superuser connects, not the installer that actually ran CREATE EXTENSION) into load.sql itself, right after install, still running as test_factory_installer. Verified locally on PG12 and PG17, fresh and update modes, as the ambient superuser only -- no PGUSER override, no separate role provisioning step. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…README claim sql/test_factory.sql: split the RAISE EXCEPTION messages into a primary message (lowercase, factual, no embedded suggestion) plus a HINT carrying the suggested GRANT command, per Postgres's own error style guide instead of cramming both into one message. test/install/load.sql: use \gexec instead of \gset-then-substitute for the dynamic GRANT CREATE ON DATABASE statement -- simpler, and removes the class of bug where a substituted bare variable is missing its trailing semicolon (confirmed CREATE ROLE has no IF NOT EXISTS variant in Postgres, so the existence-check dance for test_factory_installer is necessary, not an oversight -- same reason test_role above it needs the same pattern). Also moved the "why run as superuser" rationale for the drop-first reset into a small note directly above the DROP statements themselves, and shrunk the SET SESSION AUTHORIZATION comment to one line now that it no longer needs to carry that explanation too. README: the "Known limitation" paragraph claiming binary pg_upgrade could leave a database without the SET-enabled grant doesn't hold up -- verified directly that pg_dumpall (which pg_upgrade uses internally to carry role/membership state to the new cluster) emits a bare `GRANT role TO member;` for a pre-16 plain membership, and a bare GRANT like that defaults to SET TRUE on PostgreSQL 16+. Replaced the speculative limitation with a short manual-fix note for the unrelated case of a grant being revoked or a role being set up some other way. Detailed investigation belongs on the issue tracker, not the README. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…t its spirit sql/test_factory.sql: assign ERRCODE = 'insufficient_privilege' (SQLSTATE 42501) to both RAISE EXCEPTION calls -- an existing Postgres code that fits exactly (this IS a re-raise of the same insufficient_privilege condition just caught, with a clearer message), instead of leaving them at the generic default. Per ../ai/CLAUDE.md's newly-added RAISE style-guide section. test/sql/base.sql: the 2-line "--" comment I'd left here passed the linter's own comment-stacked-dashes check (which only flags 3+ consecutive lines), but that check is a narrow mechanical heuristic, not the actual style rule -- this comment is doing real explanatory work (a fact plus a pointer to detail elsewhere), which calls for a block comment regardless of its raw line count. Converted to /* */. Also pulled 3 newly-merged PRs into ../ai/ (was tracking a stale fork remote instead of upstream) -- confirmed sql/test_factory.sql's existing role-restore logic (plain SET ROLE at the end, not relying on SET LOCAL's transaction-boundary revert) already matches its newly-documented "session state in create/update scripts must be reverted explicitly" convention, so no change needed there. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This was referenced Aug 11, 2026
jnasbyupgrade
added a commit
that referenced
this pull request
Aug 12, 2026
- Adopts pgxntool's `stable` pseudo-version convention (per `../ai/RELEASE.md`): `default_version` in both `test_factory.control` and `test_factory_pgtap.control` now sits at the literal string `'stable'` between releases, instead of a real, already-released version number. - Without this, an ordinary source edit to `sql/test_factory.sql` would silently regenerate -- and corrupt -- the frozen, already-shipped `sql/test_factory--0.5.0.sql` (exactly what happened while developing #18, before this fix existed).
… merged to master Merged upstream/master (which now has Postgres-Extensions#42's stable-pseudo-version work) into this branch. The merge itself resolved cleanly for the control files (default_version='stable' from master combined with superuser=false from this branch, on non-overlapping lines), but silently kept this branch's own corrupted sql/test_factory--0.5.0.sql instead of master's correct one -- git's merge doesn't know that file is supposed to be immutable, so a clean textual merge isn't the same as a correct one. Restored it to match master exactly. Replaced Postgres-Extensions#42's no-op sql/test_factory--0.5.0--stable.sql placeholder with the real GRANT-if-missing content -- this branch is "the first SQL-touching PR since the last release" per ../ai/RELEASE.md, so it's responsible for populating this file for real, which is also the signal the new multi-extension release procedure uses to decide test_factory needs a version bump next release (test_factory_pgtap doesn't, since its own placeholder stays a genuine no-op). Replaced test/install/load.sql with the version that splits update-mode role handling from fresh-mode: installing the genuinely-unfixed real 0.5.0 (test_factory VERSION '0.5.0') cannot succeed as a non-superuser -- that's the original bug, not a versioning artifact -- so update mode stays on the ambient/superuser role throughout, while only fresh mode switches to the disposable non-superuser installer (which already fully covers the non-superuser install path). Moved the issue Postgres-Extensions#14 regression check into the fresh-mode branch specifically, since it was checking the wrong thing under update mode's ambient role. Verified locally on PG12 and PG17: fresh and update modes both pass, sql/test_factory--0.5.0.sql and sql/test_factory_pgtap--0.1.0.sql remain byte-identical to what shipped, and `make` no longer touches either. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Master's own Postgres-Extensions#43 further tightened the vendored linter's comment-stacked-dashes rule (2+ consecutive "--" lines now must be a block comment, down from 3+), which itself was needed to fix two pre-existing 2-line comments elsewhere. That change conflicted with this branch's own restructured test/install/load.sql, which had duplicated a similar 2-line comment into two places (fresh mode's own "Captured before..." note, and its "Everything from here on..." note) -- both now also need to be block comments under the new threshold. Fixed all three occurrences the linter flagged after resolving. Also caught and fixed my own mistake while resolving this: initially checked out the *previous* .vendor/linter submodule commit instead of the new one master's Postgres-Extensions#43 actually points at (misread the diff direction) -- confirmed against the real upstream linter repo's history before fixing it, which is what actually surfaced these findings in the first place. Verified locally on PG12 and PG17: fresh and update modes both pass, make lint is clean, and sql/test_factory--0.5.0.sql remains untouched.
…essages Per review: confirmed directly (SELECT current_user, and against a role deliberately named with an embedded literal double-quote) that current_user always returns the raw, unquoted role name -- so the "" in the message text on the line above is the only source of quoting, not something current_user already provides. Added a trailing comment on both occurrences (the mirrored pre-16 branch has the identical pattern) so this doesn't need re-deriving on the next read. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
CREATE EXTENSION test_factoryfails on PostgreSQL 16+ when installed by a non-superuser role:As of PG16,
CREATE ROLEno longer grants the creating role a SET-enabled membership in the new role, so the install'sSET ROLE test_factory__owneris rejected. A superuser bypasses the check, so this is a core PG16 behavior change, not anything platform-specific -- it just happens to be much easier to hit on RDS/Aurora, since those don't grant a true superuser role, but it affects any non-superuser install on PG16+ regardless of platform.Fix: after creating the role, grant it back to the installing role WITH SET, gated on PG16+ (pre-16
GRANT ... TOalready permitsSET ROLE). The grant is unconditional so it also covers the case where the role already existed.The failure can't be reproduced under
pg_regress, which runs as a superuser, so the added test instead asserts the SET-enabled membership the fix establishes.Fixes #14
🤖 Generated with Claude Code