diff --git a/regress/expected/age_upgrade.out b/regress/expected/age_upgrade.out index edf8e6022..446e0eb76 100644 --- a/regress/expected/age_upgrade.out +++ b/regress/expected/age_upgrade.out @@ -27,16 +27,30 @@ -- upgrade template. -- -- Compared catalogs: --- pg_proc — functions, aggregates, procedures (name, args, properties) +-- pg_proc — functions, aggregates, procedures (name, args, properties +-- including probin/prosrc to catch C-symbol renames and +-- SQL-body changes) -- 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) +-- pg_depend — extension membership (every AGE-owned object must be +-- linked back to the extension via deptype='e'; catches +-- a missing ALTER EXTENSION age ADD ... in the template) -- -- All comparison queries should return 0 rows. -- +-- Note on synthetic-initial install (step 10): the synthetic '*_initial' +-- snapshot is built from a fixed historical commit, so its CREATE FUNCTION +-- statements may reference C symbols that have since been removed from the +-- current age.so. Step 10 disables check_function_bodies so that dlsym is +-- deferred to call time; the immediately-following ALTER EXTENSION UPDATE +-- (step 11) DROPs any such retired functions before any plan can call them. +-- This lets developers cleanly remove deprecated C entry points without +-- needing to keep error-raising stubs in age.so. +-- LOAD 'age'; SET search_path TO ag_catalog; -- Step 1: Clean up any graphs left by prior tests (deterministic, no output). @@ -56,15 +70,23 @@ BEGIN END $$; -- ===================================================================== --- FRESH INSTALL SNAPSHOTS (Steps 2-7) +-- FRESH INSTALL SNAPSHOTS (Steps 2-7b) -- Capture the catalog state from the default CREATE EXTENSION install. -- ===================================================================== -- Step 2: Snapshot functions (includes aggregates via prokind). +-- probin/prosrc capture the binding to the implementation: +-- * LANGUAGE c : probin = '$libdir/age', prosrc = C symbol name +-- (a renamed/retargeted symbol shows up here) +-- * LANGUAGE sql/plpgsql: probin = NULL, prosrc = function body text +-- (a body change in the upgrade template shows up here) +-- * LANGUAGE internal : probin = NULL, prosrc = builtin name 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 + prorettype::regtype::text AS rettype, proretset::text, + COALESCE(probin, '') AS probin, + COALESCE(prosrc, '') AS prosrc FROM pg_proc WHERE pronamespace = 'ag_catalog'::regnamespace ORDER BY proname, args; @@ -113,6 +135,57 @@ SELECT conname::text, contype::text, FROM pg_constraint WHERE connamespace = 'ag_catalog'::regnamespace ORDER BY conname; +-- Step 7b: Snapshot extension membership (pg_depend deptype='e'). +-- Every object that CREATE EXTENSION owns has a row in pg_depend linking +-- it to the extension. The upgrade template must produce the same set: +-- if it CREATEs an object but forgets to ALTER EXTENSION ADD it, the +-- catalog row exists (so funcs_match/rels_match would pass) but the +-- pg_depend link is absent and pg_dump --extension would diverge. +CREATE TEMP TABLE _fresh_extmembers AS +SELECT + CASE d.classid + WHEN 'pg_proc'::regclass + THEN 'function: ' || d.objid::regprocedure::text + WHEN 'pg_type'::regclass + THEN 'type: ' || d.objid::regtype::text + WHEN 'pg_class'::regclass + THEN 'relation: ' || d.objid::regclass::text + || ' (' || (SELECT relkind::text FROM pg_class WHERE oid = d.objid) || ')' + WHEN 'pg_operator'::regclass + THEN 'operator: ' || d.objid::regoperator::text + WHEN 'pg_cast'::regclass + THEN 'cast: ' || (SELECT castsource::regtype::text || ' -> ' || casttarget::regtype::text + FROM pg_cast WHERE oid = d.objid) + WHEN 'pg_opclass'::regclass + THEN 'opclass: ' || (SELECT opcname || ' (' || (SELECT amname FROM pg_am WHERE oid = opcmethod) || ')' + FROM pg_opclass WHERE oid = d.objid) + WHEN 'pg_constraint'::regclass + THEN 'constraint: ' || (SELECT conname || ' on ' || conrelid::regclass::text + FROM pg_constraint WHERE oid = d.objid) + WHEN 'pg_opfamily'::regclass + THEN 'opfamily: ' || (SELECT opfname || ' (' || (SELECT amname FROM pg_am WHERE oid = opfmethod) || ')' + FROM pg_opfamily WHERE oid = d.objid) + WHEN 'pg_amop'::regclass + THEN 'amop: ' || (SELECT (SELECT opfname FROM pg_opfamily WHERE oid = a.amopfamily) + || ' [strategy ' || a.amopstrategy || '] ' + || a.amoplefttype::regtype::text || ',' + || a.amoprighttype::regtype::text || ' op ' + || a.amopopr::regoperator::text + FROM pg_amop a WHERE a.oid = d.objid) + WHEN 'pg_amproc'::regclass + THEN 'amproc: ' || (SELECT (SELECT opfname FROM pg_opfamily WHERE oid = p.amprocfamily) + || ' [proc ' || p.amprocnum || '] ' + || p.amproclefttype::regtype::text || ',' + || p.amprocrighttype::regtype::text || ' fn ' + || p.amproc::regprocedure::text + FROM pg_amproc p WHERE p.oid = d.objid) + ELSE 'unhandled[' || d.classid::regclass::text || ']' + END AS member +FROM pg_depend d +WHERE d.deptype = 'e' + AND d.refclassid = 'pg_extension'::regclass + AND d.refobjid = (SELECT oid FROM pg_extension WHERE extname = 'age') +ORDER BY 1; -- Step 8: Drop AGE entirely. DROP EXTENSION age; -- Step 9: Verify we have an upgrade path available. @@ -124,6 +197,19 @@ FROM pg_available_extension_versions WHERE name = 'age'; (1 row) -- Step 10: Install AGE at the synthetic initial version. +-- +-- Disable check_function_bodies for this CREATE only. The synthetic +-- '*_initial' SQL is pulled from a fixed historical commit and may +-- declare C functions whose symbols have since been removed from the +-- current age.so. With check_function_bodies=on, PostgreSQL would dlsym +-- each such symbol at CREATE FUNCTION time and abort. Deferring the +-- symbol probe to call time is safe because step 11 (ALTER EXTENSION +-- UPDATE) immediately runs the upgrade template, which DROPs any +-- removed-in-HEAD functions before the test (or any user) can call them. +-- The fresh CREATE EXTENSION at step 35 keeps the GUC at its default, +-- so any inconsistency between HEAD's SQL and HEAD's age.so is still +-- caught at install time on the production code path. +SET check_function_bodies = off; DO $$ DECLARE init_ver text; BEGIN @@ -138,6 +224,7 @@ BEGIN EXECUTE format('CREATE EXTENSION age VERSION %L', init_ver); END; $$; +RESET check_function_bodies; -- Step 11: Upgrade to the current (default) version via the stamped template. DO $$ DECLARE curr_ver text; @@ -160,15 +247,17 @@ FROM pg_available_extensions WHERE name = 'age'; (1 row) -- ===================================================================== --- UPGRADED INSTALL SNAPSHOTS (Steps 13-18) +-- UPGRADED INSTALL SNAPSHOTS (Steps 13-18b) -- Capture the catalog state after upgrade from initial to current. -- ===================================================================== --- Step 13: Snapshot functions. +-- Step 13: Snapshot functions (probin/prosrc included; see step 2). 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 + prorettype::regtype::text AS rettype, proretset::text, + COALESCE(probin, '') AS probin, + COALESCE(prosrc, '') AS prosrc FROM pg_proc WHERE pronamespace = 'ag_catalog'::regnamespace ORDER BY proname, args; @@ -217,8 +306,54 @@ SELECT conname::text, contype::text, FROM pg_constraint WHERE connamespace = 'ag_catalog'::regnamespace ORDER BY conname; +-- Step 18b: Snapshot extension membership after upgrade (see step 7b). +CREATE TEMP TABLE _upgraded_extmembers AS +SELECT + CASE d.classid + WHEN 'pg_proc'::regclass + THEN 'function: ' || d.objid::regprocedure::text + WHEN 'pg_type'::regclass + THEN 'type: ' || d.objid::regtype::text + WHEN 'pg_class'::regclass + THEN 'relation: ' || d.objid::regclass::text + || ' (' || (SELECT relkind::text FROM pg_class WHERE oid = d.objid) || ')' + WHEN 'pg_operator'::regclass + THEN 'operator: ' || d.objid::regoperator::text + WHEN 'pg_cast'::regclass + THEN 'cast: ' || (SELECT castsource::regtype::text || ' -> ' || casttarget::regtype::text + FROM pg_cast WHERE oid = d.objid) + WHEN 'pg_opclass'::regclass + THEN 'opclass: ' || (SELECT opcname || ' (' || (SELECT amname FROM pg_am WHERE oid = opcmethod) || ')' + FROM pg_opclass WHERE oid = d.objid) + WHEN 'pg_constraint'::regclass + THEN 'constraint: ' || (SELECT conname || ' on ' || conrelid::regclass::text + FROM pg_constraint WHERE oid = d.objid) + WHEN 'pg_opfamily'::regclass + THEN 'opfamily: ' || (SELECT opfname || ' (' || (SELECT amname FROM pg_am WHERE oid = opfmethod) || ')' + FROM pg_opfamily WHERE oid = d.objid) + WHEN 'pg_amop'::regclass + THEN 'amop: ' || (SELECT (SELECT opfname FROM pg_opfamily WHERE oid = a.amopfamily) + || ' [strategy ' || a.amopstrategy || '] ' + || a.amoplefttype::regtype::text || ',' + || a.amoprighttype::regtype::text || ' op ' + || a.amopopr::regoperator::text + FROM pg_amop a WHERE a.oid = d.objid) + WHEN 'pg_amproc'::regclass + THEN 'amproc: ' || (SELECT (SELECT opfname FROM pg_opfamily WHERE oid = p.amprocfamily) + || ' [proc ' || p.amprocnum || '] ' + || p.amproclefttype::regtype::text || ',' + || p.amprocrighttype::regtype::text || ' fn ' + || p.amproc::regprocedure::text + FROM pg_amproc p WHERE p.oid = d.objid) + ELSE 'unhandled[' || d.classid::regclass::text || ']' + END AS member +FROM pg_depend d +WHERE d.deptype = 'e' + AND d.refclassid = 'pg_extension'::regclass + AND d.refobjid = (SELECT oid FROM pg_extension WHERE extname = 'age') +ORDER BY 1; -- ===================================================================== --- COMPARISON: Missing or extra objects (Steps 19-33) +-- COMPARISON: Missing or extra objects (Steps 19-33b) -- Any rows returned indicate a template deficiency. -- ===================================================================== -- Step 19: Functions MISSING after upgrade. @@ -241,13 +376,20 @@ ORDER BY 1; ---------------- (0 rows) --- Step 21: Function PROPERTY changes (volatility, strictness, kind, return type). +-- Step 21: Function PROPERTY changes +-- (kind, volatility, strictness, return type, return-set, binding, +-- body/symbol). The probin/prosrc check catches: +-- * a C function whose symbol was renamed in the upgrade template +-- * a SQL/plpgsql function whose body was changed in either path +-- * a language change between the fresh and upgrade installs. 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 + 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, + CASE WHEN f.probin <> u.probin THEN 'probin: ' || f.probin || '->' || u.probin END AS probin_change, + CASE WHEN f.prosrc <> u.prosrc THEN 'prosrc changed' END AS prosrc_change FROM _fresh_funcs f JOIN _upgraded_funcs u USING (proname, args) WHERE f.provolatile <> u.provolatile @@ -255,9 +397,11 @@ WHERE f.provolatile <> u.provolatile OR f.prokind <> u.prokind OR f.rettype <> u.rettype OR f.proretset <> u.proretset + OR f.probin <> u.probin + OR f.prosrc <> u.prosrc ORDER BY 1; - function_name | kind_change | volatility_change | strict_change | rettype_change | retset_change ----------------+-------------+-------------------+---------------+----------------+--------------- + function_name | kind_change | volatility_change | strict_change | rettype_change | retset_change | probin_change | prosrc_change +---------------+-------------+-------------------+---------------+----------------+---------------+---------------+--------------- (0 rows) -- Step 22: Relations MISSING after upgrade. @@ -380,6 +524,28 @@ ORDER BY 1; ------------------ (0 rows) +-- Step 33a: Extension members MISSING after upgrade +-- (object exists in pg_proc/pg_class/etc. but is not linked to the AGE +-- extension via pg_depend, i.e. ALTER EXTENSION age ADD ... was forgotten). +SELECT f.member AS missing_extension_member +FROM _fresh_extmembers f +LEFT JOIN _upgraded_extmembers u USING (member) +WHERE u.member IS NULL +ORDER BY 1; + missing_extension_member +-------------------------- +(0 rows) + +-- Step 33b: Extension members EXTRA after upgrade. +SELECT u.member AS extra_extension_member +FROM _upgraded_extmembers u +LEFT JOIN _fresh_extmembers f USING (member) +WHERE f.member IS NULL +ORDER BY 1; + extra_extension_member +------------------------ +(0 rows) + -- ===================================================================== -- SUMMARY (Step 34) -- ===================================================================== @@ -391,10 +557,11 @@ SELECT (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; - funcs_match | rels_match | types_match | ops_match | casts_match | opclass_match | constraints_match --------------+------------+-------------+-----------+-------------+---------------+------------------- - t | t | t | t | t | t | t + (SELECT count(*) FROM _fresh_constraints) = (SELECT count(*) FROM _upgraded_constraints) AS constraints_match, + (SELECT count(*) FROM _fresh_extmembers) = (SELECT count(*) FROM _upgraded_extmembers) AS extmembers_match; + funcs_match | rels_match | types_match | ops_match | casts_match | opclass_match | constraints_match | extmembers_match +-------------+------------+-------------+-----------+-------------+---------------+-------------------+------------------ + t | t | t | t | t | t | t | t (1 row) -- ===================================================================== @@ -404,7 +571,8 @@ SELECT 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; + _fresh_constraints, _upgraded_constraints, + _fresh_extmembers, _upgraded_extmembers; DROP EXTENSION age; CREATE EXTENSION age; -- Step 36: Remove synthetic upgrade test files from the extension directory. diff --git a/regress/sql/age_upgrade.sql b/regress/sql/age_upgrade.sql index f56f7ca93..98ce3e21e 100644 --- a/regress/sql/age_upgrade.sql +++ b/regress/sql/age_upgrade.sql @@ -28,16 +28,30 @@ -- upgrade template. -- -- Compared catalogs: --- pg_proc — functions, aggregates, procedures (name, args, properties) +-- pg_proc — functions, aggregates, procedures (name, args, properties +-- including probin/prosrc to catch C-symbol renames and +-- SQL-body changes) -- 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) +-- pg_depend — extension membership (every AGE-owned object must be +-- linked back to the extension via deptype='e'; catches +-- a missing ALTER EXTENSION age ADD ... in the template) -- -- All comparison queries should return 0 rows. -- +-- Note on synthetic-initial install (step 10): the synthetic '*_initial' +-- snapshot is built from a fixed historical commit, so its CREATE FUNCTION +-- statements may reference C symbols that have since been removed from the +-- current age.so. Step 10 disables check_function_bodies so that dlsym is +-- deferred to call time; the immediately-following ALTER EXTENSION UPDATE +-- (step 11) DROPs any such retired functions before any plan can call them. +-- This lets developers cleanly remove deprecated C entry points without +-- needing to keep error-raising stubs in age.so. +-- LOAD 'age'; SET search_path TO ag_catalog; @@ -60,16 +74,24 @@ END $$; -- ===================================================================== --- FRESH INSTALL SNAPSHOTS (Steps 2-7) +-- FRESH INSTALL SNAPSHOTS (Steps 2-7b) -- Capture the catalog state from the default CREATE EXTENSION install. -- ===================================================================== -- Step 2: Snapshot functions (includes aggregates via prokind). +-- probin/prosrc capture the binding to the implementation: +-- * LANGUAGE c : probin = '$libdir/age', prosrc = C symbol name +-- (a renamed/retargeted symbol shows up here) +-- * LANGUAGE sql/plpgsql: probin = NULL, prosrc = function body text +-- (a body change in the upgrade template shows up here) +-- * LANGUAGE internal : probin = NULL, prosrc = builtin name 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 + prorettype::regtype::text AS rettype, proretset::text, + COALESCE(probin, '') AS probin, + COALESCE(prosrc, '') AS prosrc FROM pg_proc WHERE pronamespace = 'ag_catalog'::regnamespace ORDER BY proname, args; @@ -125,6 +147,58 @@ FROM pg_constraint WHERE connamespace = 'ag_catalog'::regnamespace ORDER BY conname; +-- Step 7b: Snapshot extension membership (pg_depend deptype='e'). +-- Every object that CREATE EXTENSION owns has a row in pg_depend linking +-- it to the extension. The upgrade template must produce the same set: +-- if it CREATEs an object but forgets to ALTER EXTENSION ADD it, the +-- catalog row exists (so funcs_match/rels_match would pass) but the +-- pg_depend link is absent and pg_dump --extension would diverge. +CREATE TEMP TABLE _fresh_extmembers AS +SELECT + CASE d.classid + WHEN 'pg_proc'::regclass + THEN 'function: ' || d.objid::regprocedure::text + WHEN 'pg_type'::regclass + THEN 'type: ' || d.objid::regtype::text + WHEN 'pg_class'::regclass + THEN 'relation: ' || d.objid::regclass::text + || ' (' || (SELECT relkind::text FROM pg_class WHERE oid = d.objid) || ')' + WHEN 'pg_operator'::regclass + THEN 'operator: ' || d.objid::regoperator::text + WHEN 'pg_cast'::regclass + THEN 'cast: ' || (SELECT castsource::regtype::text || ' -> ' || casttarget::regtype::text + FROM pg_cast WHERE oid = d.objid) + WHEN 'pg_opclass'::regclass + THEN 'opclass: ' || (SELECT opcname || ' (' || (SELECT amname FROM pg_am WHERE oid = opcmethod) || ')' + FROM pg_opclass WHERE oid = d.objid) + WHEN 'pg_constraint'::regclass + THEN 'constraint: ' || (SELECT conname || ' on ' || conrelid::regclass::text + FROM pg_constraint WHERE oid = d.objid) + WHEN 'pg_opfamily'::regclass + THEN 'opfamily: ' || (SELECT opfname || ' (' || (SELECT amname FROM pg_am WHERE oid = opfmethod) || ')' + FROM pg_opfamily WHERE oid = d.objid) + WHEN 'pg_amop'::regclass + THEN 'amop: ' || (SELECT (SELECT opfname FROM pg_opfamily WHERE oid = a.amopfamily) + || ' [strategy ' || a.amopstrategy || '] ' + || a.amoplefttype::regtype::text || ',' + || a.amoprighttype::regtype::text || ' op ' + || a.amopopr::regoperator::text + FROM pg_amop a WHERE a.oid = d.objid) + WHEN 'pg_amproc'::regclass + THEN 'amproc: ' || (SELECT (SELECT opfname FROM pg_opfamily WHERE oid = p.amprocfamily) + || ' [proc ' || p.amprocnum || '] ' + || p.amproclefttype::regtype::text || ',' + || p.amprocrighttype::regtype::text || ' fn ' + || p.amproc::regprocedure::text + FROM pg_amproc p WHERE p.oid = d.objid) + ELSE 'unhandled[' || d.classid::regclass::text || ']' + END AS member +FROM pg_depend d +WHERE d.deptype = 'e' + AND d.refclassid = 'pg_extension'::regclass + AND d.refobjid = (SELECT oid FROM pg_extension WHERE extname = 'age') +ORDER BY 1; + -- Step 8: Drop AGE entirely. DROP EXTENSION age; @@ -133,6 +207,19 @@ SELECT count(*) > 1 AS has_upgrade_path FROM pg_available_extension_versions WHERE name = 'age'; -- Step 10: Install AGE at the synthetic initial version. +-- +-- Disable check_function_bodies for this CREATE only. The synthetic +-- '*_initial' SQL is pulled from a fixed historical commit and may +-- declare C functions whose symbols have since been removed from the +-- current age.so. With check_function_bodies=on, PostgreSQL would dlsym +-- each such symbol at CREATE FUNCTION time and abort. Deferring the +-- symbol probe to call time is safe because step 11 (ALTER EXTENSION +-- UPDATE) immediately runs the upgrade template, which DROPs any +-- removed-in-HEAD functions before the test (or any user) can call them. +-- The fresh CREATE EXTENSION at step 35 keeps the GUC at its default, +-- so any inconsistency between HEAD's SQL and HEAD's age.so is still +-- caught at install time on the production code path. +SET check_function_bodies = off; DO $$ DECLARE init_ver text; BEGIN @@ -147,6 +234,7 @@ BEGIN EXECUTE format('CREATE EXTENSION age VERSION %L', init_ver); END; $$; +RESET check_function_bodies; -- Step 11: Upgrade to the current (default) version via the stamped template. DO $$ @@ -167,16 +255,18 @@ SELECT installed_version = default_version AS upgraded_to_current FROM pg_available_extensions WHERE name = 'age'; -- ===================================================================== --- UPGRADED INSTALL SNAPSHOTS (Steps 13-18) +-- UPGRADED INSTALL SNAPSHOTS (Steps 13-18b) -- Capture the catalog state after upgrade from initial to current. -- ===================================================================== --- Step 13: Snapshot functions. +-- Step 13: Snapshot functions (probin/prosrc included; see step 2). 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 + prorettype::regtype::text AS rettype, proretset::text, + COALESCE(probin, '') AS probin, + COALESCE(prosrc, '') AS prosrc FROM pg_proc WHERE pronamespace = 'ag_catalog'::regnamespace ORDER BY proname, args; @@ -233,8 +323,55 @@ FROM pg_constraint WHERE connamespace = 'ag_catalog'::regnamespace ORDER BY conname; +-- Step 18b: Snapshot extension membership after upgrade (see step 7b). +CREATE TEMP TABLE _upgraded_extmembers AS +SELECT + CASE d.classid + WHEN 'pg_proc'::regclass + THEN 'function: ' || d.objid::regprocedure::text + WHEN 'pg_type'::regclass + THEN 'type: ' || d.objid::regtype::text + WHEN 'pg_class'::regclass + THEN 'relation: ' || d.objid::regclass::text + || ' (' || (SELECT relkind::text FROM pg_class WHERE oid = d.objid) || ')' + WHEN 'pg_operator'::regclass + THEN 'operator: ' || d.objid::regoperator::text + WHEN 'pg_cast'::regclass + THEN 'cast: ' || (SELECT castsource::regtype::text || ' -> ' || casttarget::regtype::text + FROM pg_cast WHERE oid = d.objid) + WHEN 'pg_opclass'::regclass + THEN 'opclass: ' || (SELECT opcname || ' (' || (SELECT amname FROM pg_am WHERE oid = opcmethod) || ')' + FROM pg_opclass WHERE oid = d.objid) + WHEN 'pg_constraint'::regclass + THEN 'constraint: ' || (SELECT conname || ' on ' || conrelid::regclass::text + FROM pg_constraint WHERE oid = d.objid) + WHEN 'pg_opfamily'::regclass + THEN 'opfamily: ' || (SELECT opfname || ' (' || (SELECT amname FROM pg_am WHERE oid = opfmethod) || ')' + FROM pg_opfamily WHERE oid = d.objid) + WHEN 'pg_amop'::regclass + THEN 'amop: ' || (SELECT (SELECT opfname FROM pg_opfamily WHERE oid = a.amopfamily) + || ' [strategy ' || a.amopstrategy || '] ' + || a.amoplefttype::regtype::text || ',' + || a.amoprighttype::regtype::text || ' op ' + || a.amopopr::regoperator::text + FROM pg_amop a WHERE a.oid = d.objid) + WHEN 'pg_amproc'::regclass + THEN 'amproc: ' || (SELECT (SELECT opfname FROM pg_opfamily WHERE oid = p.amprocfamily) + || ' [proc ' || p.amprocnum || '] ' + || p.amproclefttype::regtype::text || ',' + || p.amprocrighttype::regtype::text || ' fn ' + || p.amproc::regprocedure::text + FROM pg_amproc p WHERE p.oid = d.objid) + ELSE 'unhandled[' || d.classid::regclass::text || ']' + END AS member +FROM pg_depend d +WHERE d.deptype = 'e' + AND d.refclassid = 'pg_extension'::regclass + AND d.refobjid = (SELECT oid FROM pg_extension WHERE extname = 'age') +ORDER BY 1; + -- ===================================================================== --- COMPARISON: Missing or extra objects (Steps 19-33) +-- COMPARISON: Missing or extra objects (Steps 19-33b) -- Any rows returned indicate a template deficiency. -- ===================================================================== @@ -252,13 +389,20 @@ 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). +-- Step 21: Function PROPERTY changes +-- (kind, volatility, strictness, return type, return-set, binding, +-- body/symbol). The probin/prosrc check catches: +-- * a C function whose symbol was renamed in the upgrade template +-- * a SQL/plpgsql function whose body was changed in either path +-- * a language change between the fresh and upgrade installs. 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 + 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, + CASE WHEN f.probin <> u.probin THEN 'probin: ' || f.probin || '->' || u.probin END AS probin_change, + CASE WHEN f.prosrc <> u.prosrc THEN 'prosrc changed' END AS prosrc_change FROM _fresh_funcs f JOIN _upgraded_funcs u USING (proname, args) WHERE f.provolatile <> u.provolatile @@ -266,6 +410,8 @@ WHERE f.provolatile <> u.provolatile OR f.prokind <> u.prokind OR f.rettype <> u.rettype OR f.proretset <> u.proretset + OR f.probin <> u.probin + OR f.prosrc <> u.prosrc ORDER BY 1; -- Step 22: Relations MISSING after upgrade. @@ -352,6 +498,22 @@ LEFT JOIN _fresh_constraints f USING (conname, contype, table_name) WHERE f.conname IS NULL ORDER BY 1; +-- Step 33a: Extension members MISSING after upgrade +-- (object exists in pg_proc/pg_class/etc. but is not linked to the AGE +-- extension via pg_depend, i.e. ALTER EXTENSION age ADD ... was forgotten). +SELECT f.member AS missing_extension_member +FROM _fresh_extmembers f +LEFT JOIN _upgraded_extmembers u USING (member) +WHERE u.member IS NULL +ORDER BY 1; + +-- Step 33b: Extension members EXTRA after upgrade. +SELECT u.member AS extra_extension_member +FROM _upgraded_extmembers u +LEFT JOIN _fresh_extmembers f USING (member) +WHERE f.member IS NULL +ORDER BY 1; + -- ===================================================================== -- SUMMARY (Step 34) -- ===================================================================== @@ -364,7 +526,8 @@ SELECT (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; + (SELECT count(*) FROM _fresh_constraints) = (SELECT count(*) FROM _upgraded_constraints) AS constraints_match, + (SELECT count(*) FROM _fresh_extmembers) = (SELECT count(*) FROM _upgraded_extmembers) AS extmembers_match; -- ===================================================================== -- CLEANUP (Steps 35-36) @@ -374,7 +537,8 @@ SELECT 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; + _fresh_constraints, _upgraded_constraints, + _fresh_extmembers, _upgraded_extmembers; DROP EXTENSION age; CREATE EXTENSION age;