diff --git a/HISTORY.md b/HISTORY.md index f96bf99..76c6d30 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -3,7 +3,11 @@ stable ------ -No user-facing changes yet. +== CREATE EXTENSION no longer requires a superuser +count_nulls is pure SQL functions, so the control file now says +`superuser = false`: any role with `CREATE` on the target schema can install +it. The test suite runs as an ordinary role throughout, so this can't +silently regress. 1.0.0 ----- diff --git a/README.md b/README.md index 9a9e623..cca31ee 100644 --- a/README.md +++ b/README.md @@ -47,11 +47,14 @@ You need to run the test suite using a super user, such as the default make test PGUSER=postgres Once count_nulls is installed, you can add it to a database. If you're running -PostgreSQL 9.1.0 or greater, it's a simple as connecting to a database as a -super user and running: +PostgreSQL 9.1.0 or greater, it's as simple as connecting to a database and +running: CREATE EXTENSION count_nulls; +No superuser required: count_nulls is nothing but SQL functions, so any role +with `CREATE` on the target schema can add it. + If you've upgraded your cluster to PostgreSQL 9.1 and already had count_nulls installed, you can upgrade it to a properly packaged extension with: diff --git a/bin/test_existing b/bin/test_existing index 0a9dab5..995b7a9 100755 --- a/bin/test_existing +++ b/bin/test_existing @@ -184,10 +184,13 @@ update_ext() { # CREATE EXTENSION count_nulls at VERSION, into a freshly, randomly # generated schema - shared with test/install/load.sql's own fresh/update # installs via test/helpers/create_test_schema.sql (see that file for the -# full rationale). +# full rationale). This invocation sets no count_nulls.test_load_mode GUC, +# so count_nulls_load_mode is supplied here instead: it's a genuine fresh +# install (prepare-old), never 'existing'. create_extension_in_schema() { local db=$1 version=$2 - psql -d "$db" -v ON_ERROR_STOP=1 -v version="$version" -f test/helpers/create_test_schema.sql + psql -d "$db" -v ON_ERROR_STOP=1 -v version="$version" \ + -v count_nulls_load_mode=fresh -f test/helpers/create_test_schema.sql } # --------------------------------------------------------------------------- diff --git a/count_nulls.control b/count_nulls.control index bf8f662..5c938a7 100644 --- a/count_nulls.control +++ b/count_nulls.control @@ -2,3 +2,8 @@ comment = 'Count the number of null arguments' default_version = 'stable' relocatable = false +# Pure SQL functions, nothing privileged - anyone with CREATE on the target +# schema can install it. Enforced by the suite running as a non-superuser +# (test/helpers/use_test_user.sql), which fails outright if this reverts to the +# default. +superuser = false diff --git a/test/README.md b/test/README.md index 64219cc..08da8a3 100644 --- a/test/README.md +++ b/test/README.md @@ -1,124 +1,49 @@ # count_nulls test suite -This suite is structured differently from most pgTAP-based extension tests: -rather than each `test/sql/*.sql` file writing its own independent -assertions, `core/functions.sql` defines a shared library of `test__*` -functions (pgTAP's `runtests()` naming convention) that test files `\i` and -then invoke via `runtests()`. +Unusually for a pgTAP suite, assertions aren't written per test file: +`core/functions.sql` defines a shared library of `test__*` functions that +`sql/extension_tests.sql` `\i`s and then runs via `runtests()`. ## Layout -- `install/load.sql` — installs count_nulls once, committed, before the main - `test/sql/` schedule (see pgxntool/README.asc's `test/install` section). - Its own output isn't tracked (see `install/.gitignore`) - correctness - comes from this file failing loudly if something's wrong, not from a - textual comparison. -- `deps.sql` — loaded by every test file (via `load.sql` -> - `pgxntool/setup.sql` -> `deps.sql`). No longer installs count_nulls - itself (that's `install/load.sql`'s job); only for genuine per-test - dependency statements. Currently empty - see its own header comment for - why it's kept that way rather than deleted. -- `core/functions.sql` — a shared helper, `\i`'d by `sql/extension_tests.sql`. - Defines `ncs()` (discovers, live, which schema count_nulls is actually - installed in - never trusts a hardcoded/passed-in value) plus a battery of - `test__*` functions covering function definitions, immutability/ - strictness, and behavior across `anyarray`/`json`/`jsonb` and both - trigger functions. -- `../bin/compare_fresh_vs_update` — not part of the pgTAP suite itself: a - standalone script the `test` CI job's update leg runs after - `TEST_LOAD_SOURCE=update`, which installs a fresh copy and a - 0.9.6-then-updated copy of the extension into their own scratch - databases - each an unqualified `CREATE EXTENSION`, so both land in the - same default schema by construction, which is all the diff needs to - isolate real update-vs-fresh divergence rather than a spurious - schema-name difference - and diffs `pg_get_functiondef`/comments/ACLs for - every object the extension owns. Catches an update script leaving some - definition subtly different from a fresh install, even when the fixed - pgTAP suite above still passes (it only asserts the specific behaviors it - happens to check). The `pg-upgrade-test` CI job also reuses it (via its - optional `EXISTING_DB` argument) to compare a fresh install against the - real, already-populated databases a binary `pg_upgrade` just produced, - discovering and matching that database's own randomly generated schema - (from `helpers/create_test_schema.sql`, via `bin/test_existing - prepare-old`) instead of generating a new one. -- `sql/extension_tests.sql` — `\i`'s `core/functions.sql`, adds two more - `test__*` functions of its own (`test__check_ncs`, asserting count_nulls - landed where expected; `test__shutdown__drop_all`, asserting it can be - cleanly dropped), then runs everything via `runtests()`. -- `helpers/create_test_schema.sql` — creates the freshly, randomly generated - schema and installs count_nulls into it (see "Schema targeting" below). - Shared by `install/load.sql`'s fresh/update modes and `bin/test_existing`'s - `prepare-old` - two separate call sites, one shared implementation. -- `helpers/find_test_schema.sql` — rediscovers the randomly generated schema - count_nulls was installed into (see "Schema targeting" below), for - sessions that didn't create it themselves. - -## Schema targeting - -`helpers/create_test_schema.sql` always installs count_nulls into its own -freshly, randomly generated schema - never a fixed name, and never no -schema at all. It's shared by `install/load.sql`'s fresh/update modes (`\i`'d -in the same psql session) and `bin/test_existing`'s `prepare-old` (a -separate, `-f`'d invocation) - the schema-targeting behavior described here -applies to both. The generated name -(`'count_nulls test schema ' || substr(md5(random()::text), 1, 12)`) has two -deliberate properties: - -- A constant prefix (`count_nulls test schema `, with a trailing space) - that by itself already requires SQL identifier quoting - so every single - run exercises the suite's `%I`-qualification, not just a dedicated - "quoting" leg that could bitrot independently of a "plain" one. -- The same prefix doubles as a marker for stale-schema cleanup: before - generating a new name, `helpers/create_test_schema.sql` finds and drops - any already-existing schema matching the prefix (`nspname LIKE - 'count_nulls test schema %'`), so a schema left behind by a run that - crashed before reaching its own teardown doesn't accumulate run over run. - -`helpers/create_test_schema.sql` targets the generated schema via `CREATE -EXTENSION ... WITH SCHEMA`, never by mutating its own search_path first. - -**Cross-session discovery.** Some scripts/sessions (e.g. `bin/test_existing`'s -steps, each a fresh `psql -f ...` invocation with no memory of another -invocation's `\gset` variables) need the generated name without having -created it themselves. `helpers/find_test_schema.sql` looks it up live via -`pg_namespace`, hard-failing (not a pgTAP assertion - a genuinely broken -condition, like zero or more than one matching schema) if it can't find -exactly one, and sets `:"test_schema"` via `\gset` for the including script -to use. - -**Why this proves anything.** Because count_nulls' own schema is randomly -named, it can never coincidentally end up on the test session's -search_path - so `core/functions.sql`'s `%I`-qualified calls (via `ncs()`) -only pass if they're genuinely correct, never because count_nulls' schema -happened to be reachable unqualified. `test__check_ncs` in -`sql/extension_tests.sql` is what actually checks this, via the fixed `SET -SEARCH_PATH` in `core/functions.sql`. - -**Assertion descriptions deliberately never embed the schema name.** -`core/functions.sql`'s assertions build the SQL they *execute* via `%I` -qualification (through `ncs()`, so they're always correct no matter which -real schema count_nulls landed in) but pass an *explicit*, schema-free -description to every pgTAP call - overriding pgTAP's own auto-generated -descriptions, which otherwise embed the schema. This is what keeps -`test/expected/extension_tests.out` a single file that passes no matter -which randomly generated name count_nulls actually landed in. - -**`test__shutdown__drop_all` only asserts the extension can be dropped - -it doesn't clean up the schema itself.** Dropping the schema count_nulls -was installed into isn't something this suite is testing, and -`helpers/create_test_schema.sql` already unconditionally drops any -leftover schema before the next run creates its own, so a second, per-run -drop here would only ever be redundant. Its output is identical on every -run (one `ok` row), so `test/expected/extension_tests.out` needs no -numbered pg_regress alternate for this function. +- `install/load.sql` — installs count_nulls once, committed, before the + `test/sql/` schedule. Its output isn't tracked; it fails loudly instead. +- `deps.sql` — per-test-session setup; drops the session to the test user. +- `core/functions.sql` — `ncs()`, plus the shared `test__*` library. +- `sql/extension_tests.sql` — adds `test__check_ncs` and + `test__shutdown__drop_all`, then calls `runtests()`. +- `helpers/use_test_user.sql` — switches the session to the non-superuser role. +- `helpers/create_test_schema.sql` — installs count_nulls at `:version` into + a fresh, randomly named schema. +- `helpers/find_test_schema.sql` — finds that schema again, from a session + that didn't create it. +- `../bin/compare_fresh_vs_update` — not part of this suite: diffs a fresh + install against an updated one (definitions, comments, ACLs). + +## Two things to know before changing anything + +**The suite runs as an ordinary role, not a superuser** (`Test user for +count_nulls`). That's what makes `superuser = false` in +`count_nulls.control` a tested property rather than a claim. See +`helpers/use_test_user.sql`, including why it deliberately does *not* switch +when a real `pg_upgrade` has left the extension owned by someone else. + +**count_nulls is installed into a randomly named schema**, never a fixed one +and never the default. That's what gives `core/functions.sql`'s +`%I`-qualified calls their meaning: the extension's schema can never +coincidentally land on `search_path`, so those calls only pass if they're +genuinely correct. `test__check_ncs` asserts it. Two consequences: + +- Nothing may assume the name — use `ncs()` from SQL, or + `helpers/find_test_schema.sql` from a separate session. +- Assertions pass an explicit, schema-free description to every pgTAP call, + overriding pgTAP's own (which embeds the schema). That's what keeps + `expected/extension_tests.out` a single file valid for every run. ## Regenerating expected output -Never hand-edit files under `expected/`. Regenerate via `make results` -(guarded by `make verify-results`, which refuses to copy while -`regression.diffs` shows real failures - use -`PGXNTOOL_ENABLE_VERIFY_RESULTS=no` to bypass that guard for a run you've -already reviewed and know is a legitimate, intentional change, not a way to -skip reviewing the diff). `make results` only ever writes the unsuffixed -default; alternates (`_1.out`, ...) have to be copied by hand from a real -`test/results/.out` for that scenario. +Never hand-edit `expected/`. Use `make results`, guarded by `make +verify-results` (which refuses while `regression.diffs` shows real failures; +`PGXNTOOL_ENABLE_VERIFY_RESULTS=no` bypasses it for a diff you've already +reviewed). It only writes the unsuffixed default — alternates (`_1.out`, …) +must be copied by hand from `test/results/.out`. diff --git a/test/deps.sql b/test/deps.sql index 231a50c..01be477 100644 --- a/test/deps.sql +++ b/test/deps.sql @@ -1,16 +1,14 @@ /* - * Intentionally empty. test/install/load.sql now installs count_nulls once, - * committed, before test/sql/ runs (see its header comment), so this - * per-test file no longer has anything to do. + * Per-test-session setup: pgxntool's setup.sql `\i`s this for every file + * under test/sql/. See pgxntool/README.asc's "test/install" section for why + * installing count_nulls is not done here. * - * Can't be deleted: pgxntool/test/pgxntool/setup.sql (vendored, never - * hand-edited) unconditionally does `\i test/deps.sql`, so every test - * session's setup would fail without it. It's also one of only two files - * (.gitignore, test/deps.sql) that pgxntool's subtree-sync reconciliation - * tracks and 3-way-merges on every `git subtree pull`. - * - * Kept for future use: add per-test dependency statements here again if a - * genuine need arises - e.g. relaying a value into the per-test session via - * a psql variable - same role this file played before test/install took - * over installing count_nulls. + * Read without missing_ok: a genuinely unpropagated GUC must fail loudly, + * not be indistinguishable from a deliberately empty one. (current_setting's + * missing_ok argument is 9.6 anyway, and CI covers 9.4.) These are real + * pg_regress sessions, so the Makefile has exported it via PGOPTIONS. */ +SELECT current_setting('count_nulls.test_load_mode') AS count_nulls_load_mode +\gset + +\i test/helpers/use_test_user.sql diff --git a/test/helpers/create_test_schema.sql b/test/helpers/create_test_schema.sql index bd236e7..28166ed 100644 --- a/test/helpers/create_test_schema.sql +++ b/test/helpers/create_test_schema.sql @@ -1,52 +1,36 @@ /* * Creates a fresh, randomly named schema and installs count_nulls into it. - * Shared by test/install/load.sql (fresh/update modes - same psql session, - * `\set version` then `\i` this file) AND bin/test_existing's prepare-old - * (a SEPARATE invocation - `-v version=` on the command - * line). Unusual for a test/ file to also be invoked from bin/, but the - * creation logic is identical in both cases, so it lives here once instead - * of being duplicated. - * - * :version must always be set explicitly to either the literal string - * 'current' (no VERSION clause - installs whatever the current default is) - * or a real version string (targets that specific version) - matching the - * same 'current' sentinel bin/test_existing's assert_version()/ - * current_version() already use, for the same reason: an empty string is a - * HARD ERROR rather than a valid signal, so an accidentally-unpropagated - * :version fails loudly instead of silently installing 'current' when - * something else was actually intended. - * - * The guard below bridges :version into the DO block via a SET + a real - * GUC (like test/install/load.sql's count_nulls.test_load_mode) rather than - * referencing :'version' directly inside the DO $$ ... $$ body: psql does - * NOT interpolate variables inside dollar-quoted strings (confirmed - * directly - a bare :'version' inside a $$ ... $$ block reaches the server - * un-substituted and is a syntax error), only in plain top-level SQL text - * such as the version_clause SELECT below. - * - * The generated name's constant prefix (a literal trailing space included) - * already guarantees SQL identifier quoting is required before the random - * suffix is even appended - unlike a mixed-case-only name, which would - * only force quoting by coincidence of which characters the randomness - * happened to produce. - * - * Cleanup-before-create: a prior run that crashed before reaching its own - * teardown would otherwise leave its randomly-named schema behind forever, - * since nothing else knows that name to find and drop it later. Matching - * on the constant prefix finds and drops any such leftovers before - * generating this run's own name. See test/helpers/find_test_schema.sql - * for how later, separate sessions rediscover the name this creates. + * Shared by test/install/load.sql (fresh/update modes, same psql session) + * and bin/test_existing's prepare-old (a separate invocation) - the + * creation logic is identical in both, so it lives here once. */ SET count_nulls.test_schema_version = :'version'; +/* + * Bridged through a GUC instead of referencing :'version' directly inside + * the DO block: psql doesn't interpolate variables inside dollar-quoted + * strings. + * + * 'current' means install whatever the current default is, matching the + * sentinel bin/test_existing already uses; empty is a hard error so an + * unpropagated :version can't silently install 'current' instead. + */ DO $$ BEGIN IF current_setting('count_nulls.test_schema_version') = '' THEN - RAISE EXCEPTION ':version must be set explicitly - use ''current'' to install whatever the current default is, never an empty string, so an accidentally-unpropagated value fails loudly instead of silently installing ''current'' when something else was actually intended'; + RAISE EXCEPTION $msg$:version must be set explicitly, or 'current'$msg$; END IF; END $$; +/* + * A run that crashed before its own teardown leaves a schema nothing else + * knows the name of, so match the prefix (see the name generation below) + * and drop it here - before the test-user switch, since a leftover schema + * can belong to any role and only the connecting one is sure to be able to + * drop it. See find_test_schema.sql for how later sessions rediscover the + * name this creates. + */ DO $$ DECLARE r record; @@ -57,22 +41,31 @@ BEGIN END $$; +/* + * :count_nulls_load_mode must already be set by the caller (test/install/ + * load.sql, or bin/test_existing's -v on the command line) - this file + * installs count_nulls, so it can't know on its own whether that's + * genuinely a fresh/update run rather than 'existing'. + */ +\i test/helpers/use_test_user.sql + +/* + * Trailing space alone forces identifier quoting, so every run exercises + * %I-qualification rather than passing by luck of the random suffix. Must + * match the prefix cleanup matches on above. + */ SELECT 'count_nulls test schema ' || substr(md5(random()::text), 1, 12) AS schema \gset CREATE SCHEMA :"schema"; -/* - * WITH SCHEMA targets the schema directly without touching search_path at - * all, so a successful install actually proves the install script itself - * doesn't need search_path arranged any particular way - the same - * qualification-correctness principle behind randomizing the schema name - * in the first place. Mutating search_path before CREATE EXTENSION - * instead would let the install succeed via a coincidentally arranged - * search_path, masking the extension's own install script secretly - * depending on unqualified name resolution during install. - */ SELECT CASE WHEN :'version' = 'current' THEN '' ELSE format(' VERSION %L', :'version') END AS version_clause \gset +/* + * WITH SCHEMA rather than arranging search_path first: this way a + * successful install proves the install script doesn't depend on + * unqualified name resolution, instead of hiding it behind a search_path + * that happened to suit. + */ CREATE EXTENSION count_nulls WITH SCHEMA :"schema":version_clause; diff --git a/test/helpers/use_test_user.sql b/test/helpers/use_test_user.sql new file mode 100644 index 0000000..6bd01e5 --- /dev/null +++ b/test/helpers/use_test_user.sql @@ -0,0 +1,170 @@ +/* + * Switches the session to a non-superuser role, so everything that follows + * (installing count_nulls, and the pgTAP suite itself) only passes if it + * genuinely works without superuser rights. count_nulls is pure SQL + * functions - nothing in it needs superuser - so a suite that silently ran + * as one could never notice count_nulls.control regressing back to the + * default superuser = true. + * + * Included from every entry point that starts a session the suite runs in: + * test/deps.sql (each test/sql/ session, via pgxntool's setup.sql) and + * test/helpers/create_test_schema.sql (the install session, and + * bin/test_existing's prepare-old). + * + * The RESET ROLE below is what makes a second \i of this file in one + * session behave exactly like the first: it hands the privileges back + * before anything that needs them. That isn't hypothetical - psql older + * than 10 has no \if, so test/install/load.sql's mode branches all run and + * create_test_schema.sql gets included twice. + */ +RESET ROLE; + +/* + * The one and only definition of the test user's name. Deliberately + * sentence-like: unlikely to collide with a real role on a cluster someone + * points the suite at, and (like the generated schema name) it can't be + * spelled without SQL identifier quoting, so every run exercises that. + */ +\set test_user 'Test user for count_nulls' + +/* + * Every decision is made server-side, by a function taking the role name and + * returning the role this session should actually run as, because psql has + * no way to branch before 10: \if is psql 10, and CI covers back to 9.4, + * where psql reports it as an invalid command and then carries straight on + * into the branch it should have skipped. (\gset, below, is fine - 9.3.) + * + * p_load_mode must come from the caller, not from reading + * count_nulls.test_load_mode in here: the Makefile only exports that GUC via + * PGOPTIONS for pg_regress sessions, and bin/test_existing's prepare-old + * invokes psql directly without it. Every includer sets the psql variable + * count_nulls_load_mode before \i-ing this file. + * + * TODO: collapse this into a \gset + \if once 10 is the oldest version + * supported - the plpgsql is only here to work around \if's absence. + */ +CREATE OR REPLACE FUNCTION pg_temp.count_nulls_prepare_test_user( + p_test_user name + , p_load_mode text +) RETURNS name LANGUAGE plpgsql AS $body$ +DECLARE + /* + * The managed-cloud analogues of superuser: AWS RDS and Aurora's + * rds_superuser, Cloud SQL's cloudsqlsuperuser, Azure Flexible Server's + * azure_pg_admin. None carries the rolsuper attribute - that's the whole + * point of them - so rolsuper and is_superuser can't see them and they + * have to be named. Both checks below treat them as equivalent to + * superuser: enough to set the test user up, and disqualifying for the + * test user itself. + */ + c_managed_superuser_roles CONSTANT name[] := + '{rds_superuser,cloudsqlsuperuser,azure_pg_admin}' + ; + + /* + * Joining pg_roles rather than naming the roles to pg_has_role() keeps the + * ones that don't exist on this cluster out of it entirely - it errors on + * a role that isn't there. MEMBER, not USAGE, is deliberately over-strict: + * it counts a role that merely *could* SET ROLE without having done so. + */ + c_admin CONSTANT boolean := current_setting('is_superuser') = 'on' OR EXISTS( + SELECT 1 FROM pg_roles + WHERE rolname = ANY(c_managed_superuser_roles) + AND pg_has_role(current_user, oid, 'MEMBER') + ); + + c_extension_owner CONSTANT name := ( + SELECT pg_get_userbyid(extowner) FROM pg_extension WHERE extname = 'count_nulls' + ); + v_disqualifying name; +BEGIN + /* + * In existing mode, an extension owned by someone else is tolerated - the + * test user can't manage it, so stay as the connecting role. Any other + * mode treats this as an error: silently staying would run the suite + * with the connecting role's (often superuser) privileges, proving + * nothing. + * + * PostgreSQL has no ALTER EXTENSION ... OWNER TO, so pg_upgrade can't + * preserve extension ownership (BUG #18625) - which is why existing mode + * can meet a foreign owner at all. Member functions keep their owner; + * only the extension object doesn't. + */ + IF c_extension_owner IS NOT NULL AND c_extension_owner <> p_test_user THEN + IF p_load_mode = 'existing' THEN + RETURN current_user; + END IF; + + RAISE EXCEPTION + 'count_nulls is owned by "%", not test user "%", and load mode "%" expects no pre-existing installation' + , c_extension_owner, p_test_user, p_load_mode + ; + END IF; + + IF c_admin THEN + IF NOT EXISTS(SELECT 1 FROM pg_roles WHERE rolname = p_test_user) THEN + EXECUTE format('CREATE ROLE %I', p_test_user); + END IF; + + /* + * A real superuser can already SET ROLE to anything. A managed-cloud + * admin can't, even to a role it just created: from PG16 a CREATEROLE + * creator gets ADMIN on that role, which is not the same as SET. + */ + IF current_setting('is_superuser') = 'off' THEN + EXECUTE format('GRANT %I TO %I', p_test_user, current_user); + END IF; + + /* + * Can't fold into the CREATE ROLE above: the role is cluster-wide and + * outlives any one run, but this grant lives in the current database's + * ACL, so a run against a new database still has to issue it. + * + * These two grants are all the suite gets. Anything else turning out to + * be necessary is a finding about count_nulls, not something to grant. + */ + EXECUTE format( + 'GRANT CREATE ON DATABASE %I TO %I', current_database(), p_test_user + ); + + /* + * setup.sql creates schema tap as the connecting role, which grants + * nobody else USAGE - without this, runtests() is invisible. + */ + IF EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'tap') THEN + EXECUTE format('GRANT USAGE ON SCHEMA tap TO %I', p_test_user); + END IF; + END IF; + + IF NOT EXISTS(SELECT 1 FROM pg_roles WHERE rolname = p_test_user) THEN + RAISE EXCEPTION + 'role "%" does not exist, and this session cannot create it' + , p_test_user + ; + END IF; + + IF (SELECT rolsuper FROM pg_roles WHERE rolname = p_test_user) THEN + RAISE EXCEPTION 'test user "%" must not be a superuser', p_test_user; + END IF; + + SELECT rolname INTO v_disqualifying + FROM pg_roles + WHERE rolname = ANY(c_managed_superuser_roles) + AND pg_has_role(p_test_user, oid, 'MEMBER') + ; + + IF v_disqualifying IS NOT NULL THEN + RAISE EXCEPTION + 'test user "%" must not be granted %', p_test_user, v_disqualifying; + END IF; + + RETURN p_test_user; +END +$body$; + +SELECT pg_temp.count_nulls_prepare_test_user(:'test_user', :'count_nulls_load_mode') AS count_nulls_run_as +\gset + +SET ROLE :"count_nulls_run_as"; + +-- vi: expandtab sw=2 ts=2 diff --git a/test/install/load.sql b/test/install/load.sql index 5e186b5..919867c 100644 --- a/test/install/load.sql +++ b/test/install/load.sql @@ -24,7 +24,7 @@ * Read without missing_ok: a genuinely unpropagated GUC must fail loudly, * not be indistinguishable from a deliberately empty one. */ -SELECT current_setting('count_nulls.test_load_mode') AS count_nulls_test_load_mode +SELECT current_setting('count_nulls.test_load_mode') AS count_nulls_load_mode , current_setting('count_nulls.test_load_mode') = 'update' AS count_nulls_update_mode , current_setting('count_nulls.test_load_mode') = 'existing' AS count_nulls_existing_mode \gset @@ -33,7 +33,7 @@ DO $$ BEGIN IF current_setting('count_nulls.test_load_mode') NOT IN ('fresh', 'update', 'existing') THEN RAISE EXCEPTION - 'count_nulls.test_load_mode must be ''fresh'', ''update'' or ''existing'', got ''%''' + $msg$count_nulls.test_load_mode must be 'fresh', 'update' or 'existing', got '%'$msg$ , current_setting('count_nulls.test_load_mode') ; END IF; @@ -82,7 +82,7 @@ BEGIN FROM pg_available_extensions WHERE name = 'count_nulls'; ELSE RAISE EXCEPTION - $$count_nulls.test_existing_deploy must be 'filesystem' or 'pgtle', got '%'$$ + $msg$count_nulls.test_existing_deploy must be 'filesystem' or 'pgtle', got '%'$msg$ , v_deploy ; END IF;