blob: f56f7ca932b50fd328f466b967de5b656db1775b [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
--
-- Extension upgrade template regression test
--
-- Validates the upgrade template (age--<VER>--y.y.y.sql) by comparing
-- the pg_catalog entries of a fresh install against an upgraded install.
-- If any object exists in the fresh install but is missing after upgrade,
-- the template is incomplete. This catches the case where a developer adds
-- a new SQL object to sql/ and sql_files but forgets to add it to the
-- upgrade template.
--
-- Compared catalogs:
-- pg_proc — functions, aggregates, procedures (name, args, properties)
-- pg_class — tables, views, sequences, indexes (name, kind)
-- pg_type — types (name, type category)
-- pg_operator — operators (name, left/right types)
-- pg_cast — casts involving AGE types (source, target, context)
-- pg_opclass — operator classes (name, access method)
-- pg_constraint — constraints (name, type, table, referenced table)
--
-- All comparison queries should return 0 rows.
--
LOAD 'age';
SET search_path TO ag_catalog;
-- Step 1: Clean up any graphs left by prior tests (deterministic, no output).
DO $$
DECLARE
graph_name ag_graph.name%TYPE;
BEGIN
PERFORM set_config('client_min_messages', 'warning', true);
FOR graph_name IN
SELECT name
FROM ag_graph
ORDER BY name
LOOP
PERFORM drop_graph(graph_name, true);
END LOOP;
END
$$;
-- =====================================================================
-- FRESH INSTALL SNAPSHOTS (Steps 2-7)
-- Capture the catalog state from the default CREATE EXTENSION install.
-- =====================================================================
-- Step 2: Snapshot functions (includes aggregates via prokind).
CREATE TEMP TABLE _fresh_funcs AS
SELECT proname::text,
pg_get_function_identity_arguments(oid) AS args,
provolatile::text, proisstrict::text, prokind::text,
prorettype::regtype::text AS rettype, proretset::text
FROM pg_proc
WHERE pronamespace = 'ag_catalog'::regnamespace
ORDER BY proname, args;
-- Step 3: Snapshot relations (tables, views, sequences, indexes).
CREATE TEMP TABLE _fresh_rels AS
SELECT relname::text, relkind::text
FROM pg_class
WHERE relnamespace = 'ag_catalog'::regnamespace
AND relkind IN ('r', 'v', 'S', 'i')
ORDER BY relname;
-- Step 4: Snapshot types.
CREATE TEMP TABLE _fresh_types AS
SELECT typname::text, typtype::text
FROM pg_type
WHERE typnamespace = 'ag_catalog'::regnamespace
AND typname NOT LIKE 'pg_toast%'
ORDER BY typname;
-- Step 5: Snapshot operators.
CREATE TEMP TABLE _fresh_ops AS
SELECT oprname::text,
oprleft::regtype::text AS lefttype,
oprright::regtype::text AS righttype
FROM pg_operator
WHERE oprnamespace = 'ag_catalog'::regnamespace
ORDER BY oprname, lefttype, righttype;
-- Step 6: Snapshot casts involving AGE types, and operator classes.
CREATE TEMP TABLE _fresh_casts AS
SELECT castsource::regtype::text AS source_type,
casttarget::regtype::text AS target_type,
castcontext::text
FROM pg_cast
WHERE castsource IN (SELECT oid FROM pg_type WHERE typnamespace = 'ag_catalog'::regnamespace)
OR casttarget IN (SELECT oid FROM pg_type WHERE typnamespace = 'ag_catalog'::regnamespace)
ORDER BY source_type, target_type;
CREATE TEMP TABLE _fresh_opclass AS
SELECT opcname::text,
(SELECT amname FROM pg_am WHERE oid = opcmethod)::text AS amname
FROM pg_opclass
WHERE opcnamespace = 'ag_catalog'::regnamespace
ORDER BY opcname;
-- Step 7: Snapshot constraints.
CREATE TEMP TABLE _fresh_constraints AS
SELECT conname::text, contype::text,
conrelid::regclass::text AS table_name,
confrelid::regclass::text AS ref_table
FROM pg_constraint
WHERE connamespace = 'ag_catalog'::regnamespace
ORDER BY conname;
-- Step 8: Drop AGE entirely.
DROP EXTENSION age;
-- Step 9: Verify we have an upgrade path available.
SELECT count(*) > 1 AS has_upgrade_path
FROM pg_available_extension_versions WHERE name = 'age';
-- Step 10: Install AGE at the synthetic initial version.
DO $$
DECLARE init_ver text;
BEGIN
SELECT version INTO init_ver
FROM pg_available_extension_versions
WHERE name = 'age' AND version LIKE '%_initial'
ORDER BY version DESC
LIMIT 1;
IF init_ver IS NULL THEN
RAISE EXCEPTION 'No initial version available for upgrade test';
END IF;
EXECUTE format('CREATE EXTENSION age VERSION %L', init_ver);
END;
$$;
-- Step 11: Upgrade to the current (default) version via the stamped template.
DO $$
DECLARE curr_ver text;
BEGIN
SELECT default_version INTO curr_ver
FROM pg_available_extensions
WHERE name = 'age';
IF curr_ver IS NULL THEN
RAISE EXCEPTION 'No default version found for upgrade test';
END IF;
EXECUTE format('ALTER EXTENSION age UPDATE TO %L', curr_ver);
END;
$$;
-- Step 12: Confirm the upgrade succeeded.
SELECT installed_version = default_version AS upgraded_to_current
FROM pg_available_extensions WHERE name = 'age';
-- =====================================================================
-- UPGRADED INSTALL SNAPSHOTS (Steps 13-18)
-- Capture the catalog state after upgrade from initial to current.
-- =====================================================================
-- Step 13: Snapshot functions.
CREATE TEMP TABLE _upgraded_funcs AS
SELECT proname::text,
pg_get_function_identity_arguments(oid) AS args,
provolatile::text, proisstrict::text, prokind::text,
prorettype::regtype::text AS rettype, proretset::text
FROM pg_proc
WHERE pronamespace = 'ag_catalog'::regnamespace
ORDER BY proname, args;
-- Step 14: Snapshot relations.
CREATE TEMP TABLE _upgraded_rels AS
SELECT relname::text, relkind::text
FROM pg_class
WHERE relnamespace = 'ag_catalog'::regnamespace
AND relkind IN ('r', 'v', 'S', 'i')
ORDER BY relname;
-- Step 15: Snapshot types.
CREATE TEMP TABLE _upgraded_types AS
SELECT typname::text, typtype::text
FROM pg_type
WHERE typnamespace = 'ag_catalog'::regnamespace
AND typname NOT LIKE 'pg_toast%'
ORDER BY typname;
-- Step 16: Snapshot operators.
CREATE TEMP TABLE _upgraded_ops AS
SELECT oprname::text,
oprleft::regtype::text AS lefttype,
oprright::regtype::text AS righttype
FROM pg_operator
WHERE oprnamespace = 'ag_catalog'::regnamespace
ORDER BY oprname, lefttype, righttype;
-- Step 17: Snapshot casts and operator classes.
CREATE TEMP TABLE _upgraded_casts AS
SELECT castsource::regtype::text AS source_type,
casttarget::regtype::text AS target_type,
castcontext::text
FROM pg_cast
WHERE castsource IN (SELECT oid FROM pg_type WHERE typnamespace = 'ag_catalog'::regnamespace)
OR casttarget IN (SELECT oid FROM pg_type WHERE typnamespace = 'ag_catalog'::regnamespace)
ORDER BY source_type, target_type;
CREATE TEMP TABLE _upgraded_opclass AS
SELECT opcname::text,
(SELECT amname FROM pg_am WHERE oid = opcmethod)::text AS amname
FROM pg_opclass
WHERE opcnamespace = 'ag_catalog'::regnamespace
ORDER BY opcname;
-- Step 18: Snapshot constraints.
CREATE TEMP TABLE _upgraded_constraints AS
SELECT conname::text, contype::text,
conrelid::regclass::text AS table_name,
confrelid::regclass::text AS ref_table
FROM pg_constraint
WHERE connamespace = 'ag_catalog'::regnamespace
ORDER BY conname;
-- =====================================================================
-- COMPARISON: Missing or extra objects (Steps 19-33)
-- Any rows returned indicate a template deficiency.
-- =====================================================================
-- Step 19: Functions MISSING after upgrade.
SELECT f.proname || '(' || f.args || ')' AS missing_function
FROM _fresh_funcs f
LEFT JOIN _upgraded_funcs u USING (proname, args)
WHERE u.proname IS NULL
ORDER BY 1;
-- Step 20: Functions EXTRA after upgrade.
SELECT u.proname || '(' || u.args || ')' AS extra_function
FROM _upgraded_funcs u
LEFT JOIN _fresh_funcs f USING (proname, args)
WHERE f.proname IS NULL
ORDER BY 1;
-- Step 21: Function PROPERTY changes (volatility, strictness, kind, return type).
SELECT f.proname || '(' || f.args || ')' AS function_name,
CASE WHEN f.prokind <> u.prokind THEN 'prokind: ' || f.prokind || '->' || u.prokind END AS kind_change,
CASE WHEN f.provolatile<> u.provolatile THEN 'volatile: ' || f.provolatile|| '->' || u.provolatile END AS volatility_change,
CASE WHEN f.proisstrict<> u.proisstrict THEN 'strict: ' || f.proisstrict|| '->' || u.proisstrict END AS strict_change,
CASE WHEN f.rettype <> u.rettype THEN 'rettype: ' || f.rettype || '->' || u.rettype END AS rettype_change,
CASE WHEN f.proretset <> u.proretset THEN 'retset: ' || f.proretset || '->' || u.proretset END AS retset_change
FROM _fresh_funcs f
JOIN _upgraded_funcs u USING (proname, args)
WHERE f.provolatile <> u.provolatile
OR f.proisstrict <> u.proisstrict
OR f.prokind <> u.prokind
OR f.rettype <> u.rettype
OR f.proretset <> u.proretset
ORDER BY 1;
-- Step 22: Relations MISSING after upgrade.
SELECT f.relname || ' (' || f.relkind || ')' AS missing_relation
FROM _fresh_rels f
LEFT JOIN _upgraded_rels u USING (relname, relkind)
WHERE u.relname IS NULL
ORDER BY 1;
-- Step 23: Relations EXTRA after upgrade.
SELECT u.relname || ' (' || u.relkind || ')' AS extra_relation
FROM _upgraded_rels u
LEFT JOIN _fresh_rels f USING (relname, relkind)
WHERE f.relname IS NULL
ORDER BY 1;
-- Step 24: Types MISSING after upgrade.
SELECT f.typname || ' (' || f.typtype || ')' AS missing_type
FROM _fresh_types f
LEFT JOIN _upgraded_types u USING (typname, typtype)
WHERE u.typname IS NULL
ORDER BY 1;
-- Step 25: Types EXTRA after upgrade.
SELECT u.typname || ' (' || u.typtype || ')' AS extra_type
FROM _upgraded_types u
LEFT JOIN _fresh_types f USING (typname, typtype)
WHERE f.typname IS NULL
ORDER BY 1;
-- Step 26: Operators MISSING after upgrade.
SELECT f.oprname || ' (' || f.lefttype || ', ' || f.righttype || ')' AS missing_operator
FROM _fresh_ops f
LEFT JOIN _upgraded_ops u USING (oprname, lefttype, righttype)
WHERE u.oprname IS NULL
ORDER BY 1;
-- Step 27: Operators EXTRA after upgrade.
SELECT u.oprname || ' (' || u.lefttype || ', ' || u.righttype || ')' AS extra_operator
FROM _upgraded_ops u
LEFT JOIN _fresh_ops f USING (oprname, lefttype, righttype)
WHERE f.oprname IS NULL
ORDER BY 1;
-- Step 28: Casts MISSING after upgrade.
SELECT f.source_type || ' -> ' || f.target_type || ' (' || f.castcontext || ')' AS missing_cast
FROM _fresh_casts f
LEFT JOIN _upgraded_casts u USING (source_type, target_type, castcontext)
WHERE u.source_type IS NULL
ORDER BY 1;
-- Step 29: Casts EXTRA after upgrade.
SELECT u.source_type || ' -> ' || u.target_type || ' (' || u.castcontext || ')' AS extra_cast
FROM _upgraded_casts u
LEFT JOIN _fresh_casts f USING (source_type, target_type, castcontext)
WHERE f.source_type IS NULL
ORDER BY 1;
-- Step 30: Operator classes MISSING after upgrade.
SELECT f.opcname || ' (' || f.amname || ')' AS missing_opclass
FROM _fresh_opclass f
LEFT JOIN _upgraded_opclass u USING (opcname, amname)
WHERE u.opcname IS NULL
ORDER BY 1;
-- Step 31: Operator classes EXTRA after upgrade.
SELECT u.opcname || ' (' || u.amname || ')' AS extra_opclass
FROM _upgraded_opclass u
LEFT JOIN _fresh_opclass f USING (opcname, amname)
WHERE f.opcname IS NULL
ORDER BY 1;
-- Step 32: Constraints MISSING after upgrade.
SELECT f.conname || ' (' || f.contype || ' on ' || f.table_name || ')' AS missing_constraint
FROM _fresh_constraints f
LEFT JOIN _upgraded_constraints u USING (conname, contype, table_name)
WHERE u.conname IS NULL
ORDER BY 1;
-- Step 33: Constraints EXTRA after upgrade.
SELECT u.conname || ' (' || u.contype || ' on ' || u.table_name || ')' AS extra_constraint
FROM _upgraded_constraints u
LEFT JOIN _fresh_constraints f USING (conname, contype, table_name)
WHERE f.conname IS NULL
ORDER BY 1;
-- =====================================================================
-- SUMMARY (Step 34)
-- =====================================================================
-- Step 34: Verify all counts match (result: single row, all true).
SELECT
(SELECT count(*) FROM _fresh_funcs) = (SELECT count(*) FROM _upgraded_funcs) AS funcs_match,
(SELECT count(*) FROM _fresh_rels) = (SELECT count(*) FROM _upgraded_rels) AS rels_match,
(SELECT count(*) FROM _fresh_types) = (SELECT count(*) FROM _upgraded_types) AS types_match,
(SELECT count(*) FROM _fresh_ops) = (SELECT count(*) FROM _upgraded_ops) AS ops_match,
(SELECT count(*) FROM _fresh_casts) = (SELECT count(*) FROM _upgraded_casts) AS casts_match,
(SELECT count(*) FROM _fresh_opclass) = (SELECT count(*) FROM _upgraded_opclass) AS opclass_match,
(SELECT count(*) FROM _fresh_constraints) = (SELECT count(*) FROM _upgraded_constraints) AS constraints_match;
-- =====================================================================
-- CLEANUP (Steps 35-36)
-- =====================================================================
-- Step 35: Drop temp tables, restore AGE at default version.
DROP TABLE _fresh_funcs, _upgraded_funcs, _fresh_rels, _upgraded_rels,
_fresh_types, _upgraded_types, _fresh_ops, _upgraded_ops,
_fresh_casts, _upgraded_casts, _fresh_opclass, _upgraded_opclass,
_fresh_constraints, _upgraded_constraints;
DROP EXTENSION age;
CREATE EXTENSION age;
-- Step 36: Remove synthetic upgrade test files from the extension directory.
\! sh ./regress/age_upgrade_cleanup.sh