/*
 * Single, committed-once installer for the test suite's dependencies: the
 * cat_tools extension (see the modes below) and the test roles + grants.
 *
 * pgxntool's test/install feature runs this file COMMITTED, in its own
 * pg_regress session, BEFORE the main pgTAP suite. Because its state is
 * committed it persists into every test and runs ONCE instead of per-test
 * (pgTAP rolls back each test/sql/ file, so tests read these objects but never
 * modify them). Committing also MATTERS for correctness in update mode: the
 * update to the current version runs ALTER TYPE ... ADD VALUE on
 * cat_tools.relation_relkind / relation_type, and PostgreSQL forbids USING a
 * newly added enum value in the same transaction that added it (SQLSTATE 55P04,
 * "unsafe use of new value"). The suite uses those values, so the update must be
 * committed before the suite runs -- mirroring a real production update (ALTER
 * EXTENSION UPDATE commits, then later transactions use the new values).
 * deps.sql (run per-test) installs nothing; it only sets the psql variables the
 * suite references.
 *
 * Three modes, selected by the cat_tools.test_load_mode placeholder GUC, which
 * the Makefile TEST_LOAD_SOURCE block sets via PGOPTIONS (fresh is the default):
 *   - fresh (default): plain CREATE EXTENSION cat_tools (current version).
 *   - update: CREATE EXTENSION at an older version (cat_tools.test_update_from,
 *     default 0.2.2) then ALTER EXTENSION UPDATE -- to cat_tools.test_update_to
 *     when that GUC is non-empty, otherwise to the current default_version.
 *     Reusing the SAME suite and expected output asserts an updated database
 *     behaves identically to a fresh install.
 *   - existing: the extension is ALREADY installed (by binary pg_upgrade, or an
 *     ALTER EXTENSION UPDATE performed outside the suite). load.sql must NOT
 *     drop/create/update it -- that would destroy exactly what the suite
 *     validates. It only asserts presence + current version, then creates the
 *     test roles.
 *
 * Version floors:
 *   - 0.2.2 is the OLDEST cat_tools version that installs cleanly on the
 *     supported PostgreSQL range (PG12+); the 0.2.0/0.2.1 install scripts fail
 *     on PG11+/PG12+. It is the default update-from floor -- the WIDEST update
 *     path we can exercise here -- not a claim that we only care about 0.2.2.
 *   - PG12 is the PostgreSQL floor: ALTER TYPE ... ADD VALUE cannot run inside
 *     a transaction block (or an extension update script) at all before PG12.
 */
SET client_min_messages = WARNING;

/*
 * The test-role names come from test/roles.sql (the single source of truth,
 * also loaded per-test by test/deps.sql). Each :var holds the RAW identifier;
 * we quote it at every use site (:"use_role" for CREATE ROLE / GRANT, :'use_role'
 * for the pg_temp helper text arguments).
 */
\i test/roles.sql

/*
 * Mode selection. The Makefile always exports cat_tools.test_load_mode via
 * PGOPTIONS. Read it WITHOUT missing_ok: if the GUC did not propagate (a break
 * anywhere in make -> PGOPTIONS -> env -> psql), current_setting errors here and
 * the whole install step fails loudly, instead of silently falling back to a
 * default and running the wrong suite. The DO block then rejects any value
 * other than fresh/update/existing with a clear message.
 */
SELECT current_setting('cat_tools.test_load_mode') AS cat_tools_test_load_mode
\gset

DO $DO$
BEGIN
  IF current_setting('cat_tools.test_load_mode') NOT IN ('fresh', 'update', 'existing') THEN
    RAISE EXCEPTION
      'cat_tools.test_load_mode must be ''fresh'', ''update'' or ''existing'', got ''%'''
      , current_setting('cat_tools.test_load_mode')
    ;
  END IF;
END
$DO$;

SELECT
    :'cat_tools_test_load_mode' = 'update'   AS cat_tools_mode_update
  , :'cat_tools_test_load_mode' = 'existing' AS cat_tools_mode_existing
\gset

\if :cat_tools_mode_existing
/*
 * existing mode: do NOT touch the extension. Assert it is installed and at the
 * current default_version -- the pg_upgrade / external update the database just
 * went through is exactly what the suite is validating, so dropping or
 * reinstalling it would defeat the test. Fail loudly on absence or mismatch.
 * (CI additionally plants a dependency guard so a stray non-CASCADE drop would
 * error rather than silently reinstall; see bin/test_existing.)
 */
DO $DO$
DECLARE
  v_installed text := (SELECT extversion FROM pg_extension WHERE extname = 'cat_tools');
  v_default   text := (SELECT default_version FROM pg_available_extensions WHERE name = 'cat_tools');
BEGIN
  IF v_installed IS NULL THEN
    RAISE EXCEPTION 'test_load_mode=existing but the cat_tools extension is not installed';
  END IF;
  IF v_installed IS DISTINCT FROM v_default THEN
    RAISE EXCEPTION
      'cat_tools is installed at version % but the current default_version is %'
      , v_installed, v_default
    ;
  END IF;
END
$DO$;
\else
/*
 * fresh / update: (re)install from scratch. Drop-first so a re-run on a
 * persistent cluster installs the newest build instead of reusing stale
 * objects. Drop the extension, then the roles.
 *
 * DROP EXTENSION does not remove cat_tools__usage: the extension scripts create
 * it with CREATE ROLE, and roles are global objects, not extension members, so
 * they survive DROP EXTENSION. The 0.2.2 install script uses a bare CREATE ROLE
 * (unlike the current version's duplicate-tolerant DO block), so a leftover role
 * would break a re-run in update mode. Drop all three roles explicitly via
 * pg_temp.drop_role(): DROP OWNED BY first strips privileges granted TO the role
 * (e.g. CREATE on public, cat_tools__usage membership) so DROP ROLE cannot fail
 * with a dependency error; the pg_roles guard skips a not-yet-existing role
 * (DROP OWNED BY errors on one), and format(%I) quotes the name correctly for
 * the test roles (which contain spaces and mixed case, so they must be quoted).
 */
DROP EXTENSION IF EXISTS cat_tools CASCADE;

CREATE FUNCTION pg_temp.drop_role(
  role_name text
) RETURNS void LANGUAGE plpgsql AS $$
BEGIN
  IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = role_name) THEN
    EXECUTE format('DROP OWNED BY %I', role_name);
    EXECUTE format('DROP ROLE IF EXISTS %I', role_name);
  END IF;
END
$$;

SELECT pg_temp.drop_role(:'use_role');
SELECT pg_temp.drop_role(:'no_use_role');
SELECT pg_temp.drop_role('cat_tools__usage');

\if :cat_tools_mode_update
/*
 * update mode: install an older version, then ALTER EXTENSION UPDATE. The
 * from/to versions come from the Makefile (TEST_UPDATE_FROM / TEST_UPDATE_TO,
 * exported as GUCs). An empty test_update_to means "update to the current
 * default_version" (the widest path); a non-empty value targets a specific
 * version (e.g. the explicit 0.2.1 -> 0.2.2 script).
 */
SELECT current_setting('cat_tools.test_update_from') AS cat_tools_test_update_from \gset
SELECT current_setting('cat_tools.test_update_to')   AS cat_tools_test_update_to   \gset
/*
 * Build the optional target clause once so a SINGLE ALTER EXTENSION covers both
 * cases: an empty test_update_to yields '' (update to the current
 * default_version -- the widest path); a non-empty value yields "TO '<v>'".
 * format(%L) quotes the version literal safely; the bare :clause interpolation
 * below then drops it in verbatim.
 */
SELECT CASE WHEN :'cat_tools_test_update_to' = '' THEN ''
            ELSE format('TO %L', :'cat_tools_test_update_to') END
  AS cat_tools_update_to_clause \gset

CREATE EXTENSION cat_tools VERSION :'cat_tools_test_update_from';
/*
 * Suppress the deprecation NOTICEs the update scripts emit, matching the
 * approach used by test/build/upgrade.sql.
 */
SET client_min_messages = ERROR;
ALTER EXTENSION cat_tools UPDATE :cat_tools_update_to_clause;
SET client_min_messages = WARNING;
\else
CREATE EXTENSION cat_tools;
\endif
-- end \if :cat_tools_mode_update (fresh vs. update install branch)
\endif
-- end \if :cat_tools_mode_existing (existing mode skips the whole (re)install block)

/*
 * Roles and grants the test suite depends on. Formerly created per-test in
 * deps.sql; now committed here once. Created idempotently because the source
 * differs by mode: fresh/update dropped them just above, while a freshly
 * pg_upgraded database (existing mode) never had them (roles are global, and
 * only the extension -- not the test roles -- is created before the pg_upgrade).
 * GRANT is idempotent, so re-applying the grants is always safe.
 */
CREATE FUNCTION pg_temp.create_role(
  role_name text
) RETURNS void LANGUAGE plpgsql AS $$
BEGIN
  IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = role_name) THEN
    EXECUTE format('CREATE ROLE %I', role_name);
  END IF;
END
$$;

SELECT pg_temp.create_role(:'no_use_role');
SELECT pg_temp.create_role(:'use_role');

GRANT cat_tools__usage TO :"use_role";
/*
 * PG15+ removed CREATE on the public schema from PUBLIC; grant it explicitly
 * for tests that create shadow names in public to check catalog-lookup
 * correctness.
 */
GRANT CREATE ON SCHEMA public TO :"use_role";

-- vi: expandtab ts=2 sw=2
