From 00e90d8ad795cc119704519fc999d4c892eaaec3 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Fri, 7 Aug 2026 16:48:30 -0500 Subject: [PATCH 01/10] Build cat_tools from its 0.3.0 git tag; adapt to its API/enum changes pgxn install --unstable cat_tools resolves to the newest release actually published to the PGXN package index, which is still 0.2.1 (2017) and fails standalone on modern PostgreSQL with "column oid specified more than once" at CREATE EXTENSION. A fixed release, 0.3.0, is tagged in cat_tools' own git repo but hasn't been uploaded to PGXN yet, so the Makefile's cat_tools target now clones Postgres-Extensions/cat_tools at the 0.3.0 tag and builds/installs it directly. Since this is the first time object_reference's suite has actually run against a real, working cat_tools, two small fallout fixes are needed: - cat_tools.function__arg_types_text() is deprecated in 0.3.0 in favor of cat_tools.routine__parse_arg_types_text() (identical signature/body, just renamed, deprecated one emits a WARNING on every call). Switched object_reference's one call site to the non-deprecated name. - cat_tools 0.3.0's object_type enum grew two new members, "partitioned table" and "partitioned index". pg_get_object_address() doesn't recognize either (only the base table/index types they derive from), so object_reference classifies them as unsupported, matching object_reference.unsupported()'s existing handling of "event trigger" for the same reason. test/sql/all.sql's sanity-check of the unsupported set is updated to match. sql/object_reference--stable.sql and test/expected/zzz_build.out are regenerated (make results) to match. Extracted from PR #5, which had scope-crept into also carrying this fix alongside the actual CI/pgxn-tools migration; splitting it out here so it can be reviewed and merged independently. --- Makefile | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b9a161e..60bcf3e 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,23 @@ extra_clean += $(wildcard test/dump/*.log) dump_test: test/dump/run.sh test/helpers/object_table.sql $(wildcard test/dump/*.sql) $< -f # Force drop of databases if they exist +CAT_TOOLS_VERSION = 0.3.0 +CAT_TOOLS_BUILD_DIR = tmp/cat_tools-$(CAT_TOOLS_VERSION) +extra_clean += $(CAT_TOOLS_BUILD_DIR) + .PHONY: cat_tools cat_tools: $(DESTDIR)$(datadir)/extension/cat_tools.control $(DESTDIR)$(datadir)/extension/cat_tools.control: - pgxn install --unstable cat_tools + # `pgxn install --unstable cat_tools` resolves to the newest release + # published to the PGXN package index, which is still 0.2.1 -- it fails + # standalone on modern PostgreSQL with "column oid specified more than + # once" at CREATE EXTENSION. A fixed release, 0.3.0, is tagged in + # cat_tools' own git repo but hasn't been uploaded to PGXN yet, so build + # it from that tag directly until PGXN has it. + rm -rf $(CAT_TOOLS_BUILD_DIR) + git clone --branch $(CAT_TOOLS_VERSION) --depth 1 https://github.com/Postgres-Extensions/cat_tools.git $(CAT_TOOLS_BUILD_DIR) + $(MAKE) -C $(CAT_TOOLS_BUILD_DIR) install PG_CONFIG=$(PG_CONFIG) DESTDIR=$(DESTDIR) + rm -rf $(CAT_TOOLS_BUILD_DIR) .PHONY: count_nulls count_nulls: $(DESTDIR)$(datadir)/extension/count_nulls.control From c9f80346fd3fd9ca9cc999870fa1a5413907e16b Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Fri, 7 Aug 2026 16:59:58 -0500 Subject: [PATCH 02/10] Revert the Makefile git-tag-build workaround; PGXN now serves cat_tools 0.3.0 pgxn install --unstable cat_tools now resolves to cat_tools 0.3.0 directly (confirmed against the live PGXN index and by a clean CREATE EXTENSION cat_tools; both locally and, once pushed, in the actual object_reference CI run for #5's slimmed CI-migration branch -- it went fully green without this Makefile change at all). The PGXN package index being stuck at the broken, 2017-era 0.2.1 release was true when this fix was first written, but isn't true anymore, so the git-clone-from-tag workaround has nothing left to work around. Keeping it would leave a Makefile comment describing a problem that no longer exists. The SQL/test fixes (renamed function call, new object_type enum members classified as unsupported) are unaffected -- those are needed regardless of how cat_tools 0.3.0 gets installed. --- Makefile | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/Makefile b/Makefile index 60bcf3e..b9a161e 100644 --- a/Makefile +++ b/Makefile @@ -10,23 +10,10 @@ extra_clean += $(wildcard test/dump/*.log) dump_test: test/dump/run.sh test/helpers/object_table.sql $(wildcard test/dump/*.sql) $< -f # Force drop of databases if they exist -CAT_TOOLS_VERSION = 0.3.0 -CAT_TOOLS_BUILD_DIR = tmp/cat_tools-$(CAT_TOOLS_VERSION) -extra_clean += $(CAT_TOOLS_BUILD_DIR) - .PHONY: cat_tools cat_tools: $(DESTDIR)$(datadir)/extension/cat_tools.control $(DESTDIR)$(datadir)/extension/cat_tools.control: - # `pgxn install --unstable cat_tools` resolves to the newest release - # published to the PGXN package index, which is still 0.2.1 -- it fails - # standalone on modern PostgreSQL with "column oid specified more than - # once" at CREATE EXTENSION. A fixed release, 0.3.0, is tagged in - # cat_tools' own git repo but hasn't been uploaded to PGXN yet, so build - # it from that tag directly until PGXN has it. - rm -rf $(CAT_TOOLS_BUILD_DIR) - git clone --branch $(CAT_TOOLS_VERSION) --depth 1 https://github.com/Postgres-Extensions/cat_tools.git $(CAT_TOOLS_BUILD_DIR) - $(MAKE) -C $(CAT_TOOLS_BUILD_DIR) install PG_CONFIG=$(PG_CONFIG) DESTDIR=$(DESTDIR) - rm -rf $(CAT_TOOLS_BUILD_DIR) + pgxn install --unstable cat_tools .PHONY: count_nulls count_nulls: $(DESTDIR)$(datadir)/extension/count_nulls.control From 682da629c8d00c6e7243466c035cec51af6ddca2 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Tue, 4 Aug 2026 19:19:12 -0500 Subject: [PATCH 03/10] Remove reg* pseudotype columns; add object info functions Reconciles the substantive feature delta from new_features (PR #2) onto the current 'stable' baseline (post PR #5/#16: pgxn-tools testing, cat_tools 0.3.0, and the linter): - _object_reference._object_oid: drop the per-catalog regclass/regconfig/ regdictionary/regnamespace/regoperator/regprocedure/regtype columns and their unique indexes plus the count_nulls-backed null_count trigger that enforced "exactly one is set". classid is now plain oid and object_oid (also NOT NULL) is the sole identifier column, so there's nothing left to arbitrate between. - _object_reference._object_v / _object_v__for_update: drop the reg* columns from the column list to match. - _object_reference._object_oid__add: replace the dynamic, format()-built INSERT that picked a reg* column based on cat_tools.object__reg_type() with a plain INSERT into object_oid. - Drop the count_nulls search_path DO block (dead now that the trigger using it is gone) and the count_nulls dependency throughout (control, Makefile, test setup). - Add object_reference.object__describe()/object__identity(), thin wrappers around pg_describe_object()/pg_identify_object(); and object__cleanup(), which best-effort deletes an object record (ignoring foreign_key_violation if it's still referenced elsewhere). Wire object__cleanup() up to a new AFTER DELETE trigger on object_group__object so removing an object from its last group automatically attempts cleanup. - _object_v__for_update (the getsert core): refuse to track objects living in a pg_temp*/pg_toast_temp* schema, since a tracked reference would outlive the temporary object it points to. - test/sql/object_group.sql: switch the two scratch tables from TEMP to regular tables (object__getsert now rejects temp objects) and add coverage for the new automatic-cleanup trigger. - test/sql/base.sql: replace the count_nulls-relocation test (relocation was already unsupported and the whole extension no longer depends on count_nulls) with coverage for object_oid, object__describe(), object__identity(), and temp-object rejection. sql/object_reference--0.1.0.sql (the frozen historical release) and the META files are untouched. default_version stays 'stable'; sql/object_reference--stable.sql is regenerated to match sql/object_reference.sql. make lint and make test (including the dump/restore test) pass on both PostgreSQL 12 and 17. Supersedes PR #2 (new_features) and, for the update/upgrade test infrastructure built on top of it, sets up the rebuild of PR #3. Co-Authored-By: Claude Sonnet 5 --- Makefile | 7 +- object_reference.control | 2 +- sql/object_reference.sql | 184 +++++++++++++++++++----------------- test/deps.sql | 1 - test/expected/base.out | 15 +-- test/expected/zzz_build.out | 72 +++++++++++++- test/load.sql | 1 - test/sql/base.sql | 47 ++++----- test/sql/object_group.sql | 44 ++++++--- test/sql/zzz_build.sql | 1 - 10 files changed, 232 insertions(+), 142 deletions(-) diff --git a/Makefile b/Makefile index b9a161e..0b7fd85 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ include pgxntool/base.mk testdeps: $(wildcard test/*.sql test/helpers/*.sql) # Be careful not to include directories in this testdeps: test_factory -install: cat_tools count_nulls +install: cat_tools test: dump_test extra_clean += $(wildcard test/dump/*.log) @@ -15,11 +15,6 @@ cat_tools: $(DESTDIR)$(datadir)/extension/cat_tools.control $(DESTDIR)$(datadir)/extension/cat_tools.control: pgxn install --unstable cat_tools -.PHONY: count_nulls -count_nulls: $(DESTDIR)$(datadir)/extension/count_nulls.control -$(DESTDIR)$(datadir)/extension/count_nulls.control: - pgxn install --unstable count_nulls - .PHONY: test_factory test_factory: $(DESTDIR)$(datadir)/extension/test_factory.control $(DESTDIR)$(datadir)/extension/test_factory.control: diff --git a/object_reference.control b/object_reference.control index 5c7d974..142d0b1 100644 --- a/object_reference.control +++ b/object_reference.control @@ -2,4 +2,4 @@ comment = 'Provides reference IDs for database objects' default_version = 'stable' relocatable = false schema = 'object_reference' -requires = 'cat_tools, count_nulls' +requires = 'cat_tools' diff --git a/sql/object_reference.sql b/sql/object_reference.sql index 51cae76..95567ae 100644 --- a/sql/object_reference.sql +++ b/sql/object_reference.sql @@ -2,18 +2,7 @@ \echo You really, REALLY do NOT want to try and load this via psql!!! \echo It will FAIL during pg_dump! \quit --- This BS is because count_nulls is relocatable, so could be in any schema -DO $$ -BEGIN - RAISE DEBUG 'initial search_path = %', current_setting('search_path'); - PERFORM set_config('search_path', current_setting('search_path') || ', ' || extnamespace::regnamespace::text, true) -- true = local only - FROM pg_extension - WHERE extname = 'count_nulls' - ; - RAISE DEBUG 'search_path changed to %', current_setting('search_path'); -END -$$; -/* EXCLUDED CODE: schema-restriction check below not currently enforced +/* DO $$ DECLARE c_schema CONSTANT name := (SELECT extnamespace::regnamespace::text FROM pg_extension WHERE extname = 'cat_tools'); @@ -191,7 +180,7 @@ GRANT REFERENCES ON _object_reference.object TO object_reference__dependency; CREATE TABLE _object_reference._object_oid( object_id int PRIMARY KEY REFERENCES _object_reference.object ON DELETE CASCADE ON UPDATE CASCADE - , classid regclass NOT NULL + , classid oid NOT NULL /* EXCLUDED CODE: TODO: needs to be a trigger CONSTRAINT classid_must_match__object__address_classid CHECK( classid IS NOT DISTINCT FROM cat_tools.object__address_classid(object_type) ) @@ -199,50 +188,11 @@ CREATE TABLE _object_reference._object_oid( , objid oid NOT NULL , objsubid int NOT NULL CONSTRAINT objid_must_match CHECK( -- _object_reference._sanity() depends on this! - objid IS NOT DISTINCT FROM coalesce( - regclass::oid -- Need to cast first item to generic OID - , regconfig - , regdictionary - , regnamespace -- SED: REQUIRES 9.5! - , regoperator - , regprocedure - , regtype - , object_oid - ) + objid IS NOT DISTINCT FROM object_oid ) , CONSTRAINT object__u_classid__objid__objsubid UNIQUE( classid, objid, objsubid ) - , regclass regclass - CONSTRAINT regclass_classid CHECK( regclass IS NULL OR classid = cat_tools.object__reg_type_catalog('regclass') ) - , regconfig regconfig - CONSTRAINT regconfig_classid CHECK( regconfig IS NULL OR classid = cat_tools.object__reg_type_catalog('regconfig') ) - , regdictionary regdictionary - CONSTRAINT regdictionary_classid CHECK( regdictionary IS NULL OR classid = cat_tools.object__reg_type_catalog('regdictionary') ) - , regnamespace regnamespace -- SED: REQUIRES 9.5! - CONSTRAINT regnamespace_classid CHECK( regnamespace IS NULL OR classid = cat_tools.object__reg_type_catalog('regnamespace') ) -- SED: REQUIRES 9.5! - , regoperator regoperator - CONSTRAINT regoperator_classid CHECK( regoperator IS NULL OR classid = cat_tools.object__reg_type_catalog('regoperator') ) - , regprocedure regprocedure - CONSTRAINT regprocedure_classid CHECK( regprocedure IS NULL OR classid = cat_tools.object__reg_type_catalog('regprocedure') ) - -- I don't think we should ever have regrole since we can't create event triggers on it --- , regrole regrole - , regtype regtype - CONSTRAINT regtype_classid CHECK( regtype IS NULL OR classid = cat_tools.object__reg_type_catalog('regtype') ) - , object_oid oid + , object_oid oid NOT NULL ); -CREATE TRIGGER null_count - AFTER INSERT OR UPDATE - ON _object_reference._object_oid - FOR EACH ROW EXECUTE PROCEDURE not_null_count_trigger( - 5 -- First 4 fields, + 1 - , 'only one object reference field may be set' - ) -; -CREATE UNIQUE INDEX _object_oid__u_regclass ON _object_reference._object_oid(regclass) WHERE regclass IS NOT NULL; -CREATE UNIQUE INDEX _object_oid__u_regconfig ON _object_reference._object_oid(regconfig) WHERE regconfig IS NOT NULL; -CREATE UNIQUE INDEX _object_oid__u_regdictionary ON _object_reference._object_oid(regdictionary) WHERE regdictionary IS NOT NULL; -CREATE UNIQUE INDEX _object_oid__u_regoperator ON _object_reference._object_oid(regoperator) WHERE regoperator IS NOT NULL; -CREATE UNIQUE INDEX _object_oid__u_regprocedure ON _object_reference._object_oid(regprocedure) WHERE regprocedure IS NOT NULL; -CREATE UNIQUE INDEX _object_oid__u_regtype ON _object_reference._object_oid(regtype) WHERE regtype IS NOT NULL; SELECT __object_reference.create_function( '_object_reference._sanity' @@ -302,13 +252,6 @@ CREATE VIEW _object_reference._object_v AS , i.classid , i.objid , i.objsubid - , i.regclass - , i.regconfig - , i.regdictionary - , i.regnamespace - , i.regoperator - , i.regprocedure - , i.regtype , i.object_oid , s.* FROM _object_reference.object o @@ -324,13 +267,6 @@ CREATE VIEW _object_reference._object_v__for_update AS , i.classid , i.objid , i.objsubid - , i.regclass - , i.regconfig - , i.regdictionary - , i.regnamespace - , i.regoperator - , i.regprocedure - , i.regtype , i.object_oid , s.* FROM _object_reference.object o @@ -362,26 +298,9 @@ BEGIN WHERE o.object_id = _object_oid__add.object_id ; END IF; - DECLARE - c_reg_type name := cat_tools.object__reg_type(object_type); -- Verifies regtype is supported, if there is one - c_oid_field CONSTANT name := coalesce(c_reg_type, 'object_oid'); - - c_oid_insert CONSTANT text := format( - --USING object_id, classid, objid, objsubid - $$INSERT INTO _object_reference._object_oid(object_id, classid, objid, objsubid, %I) - SELECT $1, $2, $3, $4, $3::%I$$ - , c_oid_field - , coalesce(c_reg_type, 'oid') - ) - ; BEGIN - RAISE DEBUG E'%\n USING %, %, %, %' - , c_oid_insert - , object_id, classid, objid, objsubid - ; - EXECUTE c_oid_insert - USING object_id, classid, objid, objsubid - ; + INSERT INTO _object_reference._object_oid(object_id, classid, objid, objsubid, object_oid) + VALUES (object_id, classid, objid, objsubid, objid); SELECT INTO STRICT r_object_v -- Record better exist! * @@ -631,6 +550,24 @@ CREATE TABLE _object_reference.object_group__object( ); SELECT __object_reference.safe_dump('_object_reference.object_group__object'); +-- Trigger function for automatic object cleanup +SELECT __object_reference.create_function( + '_object_reference._object_group__object__cleanup_trigger' + , '' + , 'trigger LANGUAGE plpgsql' + , $body$ +BEGIN + PERFORM object_reference.object__cleanup(OLD.object_id); + RETURN OLD; +END +$body$ + , 'Trigger function to automatically attempt cleanup of objects when removed from groups.' +); +CREATE TRIGGER object_group__object__cleanup + AFTER DELETE ON _object_reference.object_group__object + FOR EACH ROW + EXECUTE FUNCTION _object_reference._object_group__object__cleanup_trigger(); + -- __get SELECT __object_reference.create_function( 'object_reference.object_group__get' @@ -835,6 +772,69 @@ $body$ , 'object_reference__dependency' ); +/* + * OBJECT INFO FUNCTIONS + */ +SELECT __object_reference.create_function( + 'object_reference.object__describe' + , $args$ + object_id int +$args$ + , 'text LANGUAGE sql' + , $body$ +SELECT pg_catalog.pg_describe_object( + o.classid + , o.objid + , o.objsubid +) +FROM _object_reference._object_oid o +WHERE o.object_id = $1 +$body$ + , 'Return a human-readable description of the object, matching pg_describe_object() format.' + , 'object_reference__usage' +); + +SELECT __object_reference.create_function( + 'object_reference.object__identity' + , $args$ + object_id int + , OUT type text + , OUT schema text + , OUT name text + , OUT identity text +$args$ + , 'record LANGUAGE sql' + , $body$ +SELECT + i.type::text + , i.schema::text + , i.name::text + , i.identity::text +FROM _object_reference._object_oid o + , LATERAL pg_catalog.pg_identify_object(o.classid, o.objid, o.objsubid) i +WHERE o.object_id = $1 +$body$ + , 'Return object identification information matching pg_identify_object() format.' + , 'object_reference__usage' +); +SELECT __object_reference.create_function( + 'object_reference.object__cleanup' + , $args$ + object_id int +$args$ + , 'void LANGUAGE plpgsql' + , $body$ +BEGIN + DELETE FROM _object_reference.object WHERE object.object_id = object__cleanup.object_id; +EXCEPTION WHEN foreign_key_violation THEN + -- Object is still referenced elsewhere, ignore the error + NULL; +END +$body$ + , 'Attempts to delete an object from the tracking system. Silently returns if the object is still referenced by other tables.' + , 'object_reference__usage' +); + /* * OBJECT GETSERT */ @@ -854,6 +854,7 @@ DECLARE r_object_v _object_reference._object_v; r_address record; + r_identity record; did_insert boolean := false; @@ -882,6 +883,15 @@ BEGIN ; END IF; + -- Refuse to track objects in temporary schemas + SELECT INTO r_identity * FROM pg_catalog.pg_identify_object(c_classid, objid, objsubid); + IF r_identity.schema IS NOT NULL AND (r_identity.schema LIKE 'pg_temp%' OR r_identity.schema LIKE 'pg_toast_temp%') THEN + RAISE 'cannot track temporary object' + USING DETAIL = format('object %s is in temporary schema %s', r_identity.identity, r_identity.schema) + , ERRCODE = 'feature_not_supported' + ; + END IF; + -- Ensure the object record exists SELECT INTO r_object_v * diff --git a/test/deps.sql b/test/deps.sql index b0ee45f..300c7a8 100644 --- a/test/deps.sql +++ b/test/deps.sql @@ -3,6 +3,5 @@ -- Add any test dependency statements here /* EXCLUDED CODE: normally these should be loaded by the cascade! -CREATE EXTENSION IF NOT EXISTS count_nulls; CREATE EXTENSION IF NOT EXISTS cat_tools; */ diff --git a/test/expected/base.out b/test/expected/base.out index 5e39f56..5357054 100644 --- a/test/expected/base.out +++ b/test/expected/base.out @@ -1,12 +1,13 @@ \set ECHO none -1..9 +1..10 ok 1 - Role object_reference__dependency should be granted USAGE on schema _object_reference ok 2 - Role object_reference__dependency should be granted REFERENCES on table _object_reference.object ok 3 - CREATE TEMP TABLE test_object AS SELECT object_reference.object__getsert('table', 'test_table') AS object_id; -ok 4 - Verify regclass field is correct -ok 5 - Existing object works, provides correct ID -ok 6 - secondary may not be specified for table objects -ok 7 - Verify count_nulls extension can not be relocated -ok 8 - Still works after moving the count_nulls extension -ok 9 - CREATE EXTENSION test_factory +ok 4 - Verify object_oid field is correct +ok 5 - object__describe returns same result as pg_describe_object +ok 6 - object__identity returns same result as pg_identify_object +ok 7 - Existing object works, provides correct ID +ok 8 - secondary may not be specified for table objects +ok 9 - temp objects are rejected +ok 10 - CREATE EXTENSION test_factory # TRANSACTION INTENTIONALLY LEFT OPEN! diff --git a/test/expected/zzz_build.out b/test/expected/zzz_build.out index d9612d3..99e1332 100644 --- a/test/expected/zzz_build.out +++ b/test/expected/zzz_build.out @@ -2,16 +2,26 @@ This extension must be loaded via CREATE EXTENSION object_reference; You really, REALLY do NOT want to try and load this via psql!!! -psql:test/temp_load.not_sql:187: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! +psql:test/temp_load.not_sql:176: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! -psql:test/temp_load.not_sql:188: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! +psql:test/temp_load.not_sql:177: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! +psql:test/temp_load.not_sql:323: NOTICE: type reference _object_reference._object_oid.object_id%TYPE converted to integer +psql:test/temp_load.not_sql:323: NOTICE: type reference _object_reference.object.object_type%TYPE converted to cat_tools.object_type +psql:test/temp_load.not_sql:323: NOTICE: type reference _object_reference._object_oid.classid%TYPE converted to oid +psql:test/temp_load.not_sql:323: NOTICE: type reference _object_reference._object_oid.objid%TYPE converted to oid +psql:test/temp_load.not_sql:323: NOTICE: type reference _object_reference._object_oid.objsubid%TYPE converted to integer +psql:test/temp_load.not_sql:323: NOTICE: type reference _object_reference._object_oid.object_id%TYPE converted to integer +psql:test/temp_load.not_sql:323: NOTICE: type reference _object_reference.object.object_type%TYPE converted to cat_tools.object_type +psql:test/temp_load.not_sql:323: NOTICE: type reference _object_reference._object_oid.classid%TYPE converted to oid +psql:test/temp_load.not_sql:323: NOTICE: type reference _object_reference._object_oid.objid%TYPE converted to oid +psql:test/temp_load.not_sql:323: NOTICE: type reference _object_reference._object_oid.objsubid%TYPE converted to integer -psql:test/temp_load.not_sql:512: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! +psql:test/temp_load.not_sql:431: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! @@ -21,28 +31,80 @@ psql:test/temp_load.not_sql:512: WARNING: I promise you will be sorry if you tr -psql:test/temp_load.not_sql:624: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! +psql:test/temp_load.not_sql:543: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! -psql:test/temp_load.not_sql:631: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! +psql:test/temp_load.not_sql:550: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! +psql:test/temp_load.not_sql:595: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying +psql:test/temp_load.not_sql:595: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying +psql:test/temp_load.not_sql:620: NOTICE: type reference _object_reference.object_group.object_group_id%TYPE converted to integer +psql:test/temp_load.not_sql:620: NOTICE: type reference _object_reference.object_group.object_group_id%TYPE converted to integer +psql:test/temp_load.not_sql:635: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying +psql:test/temp_load.not_sql:635: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying +psql:test/temp_load.not_sql:662: NOTICE: type reference _object_reference.object_group.object_group_id%TYPE converted to integer +psql:test/temp_load.not_sql:662: NOTICE: type reference _object_reference.object_group.object_group_id%TYPE converted to integer +psql:test/temp_load.not_sql:678: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying +psql:test/temp_load.not_sql:678: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying +psql:test/temp_load.not_sql:695: NOTICE: type reference _object_reference.object_group__object.object_group_id%TYPE converted to integer +psql:test/temp_load.not_sql:695: NOTICE: type reference _object_reference.object_group__object.object_id%TYPE converted to integer +psql:test/temp_load.not_sql:695: NOTICE: type reference _object_reference.object_group__object.object_group_id%TYPE converted to integer +psql:test/temp_load.not_sql:695: NOTICE: type reference _object_reference.object_group__object.object_id%TYPE converted to integer +psql:test/temp_load.not_sql:729: NOTICE: type reference _object_reference.object_group__object.object_group_id%TYPE converted to integer +psql:test/temp_load.not_sql:729: NOTICE: type reference _object_reference.object_group__object.object_id%TYPE converted to integer +psql:test/temp_load.not_sql:729: NOTICE: type reference _object_reference.object_group__object.object_group_id%TYPE converted to integer +psql:test/temp_load.not_sql:729: NOTICE: type reference _object_reference.object_group__object.object_id%TYPE converted to integer +psql:test/temp_load.not_sql:967: NOTICE: type reference _object_reference.object.object_type%TYPE converted to cat_tools.object_type +psql:test/temp_load.not_sql:967: NOTICE: type reference _object_reference._object_oid.objid%TYPE converted to oid +psql:test/temp_load.not_sql:967: NOTICE: type reference _object_reference._object_oid.objsubid%TYPE converted to integer +psql:test/temp_load.not_sql:967: NOTICE: type reference _object_reference.object.object_type%TYPE converted to cat_tools.object_type +psql:test/temp_load.not_sql:967: NOTICE: type reference _object_reference._object_oid.objid%TYPE converted to oid +psql:test/temp_load.not_sql:967: NOTICE: type reference _object_reference._object_oid.objsubid%TYPE converted to integer +psql:test/temp_load.not_sql:1194: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying +psql:test/temp_load.not_sql:1194: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying +psql:test/temp_load.not_sql:1208: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying +psql:test/temp_load.not_sql:1208: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying +psql:test/temp_load.not_sql:1235: NOTICE: type reference _object_reference.object_group.object_group_id%TYPE converted to integer +psql:test/temp_load.not_sql:1235: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying +psql:test/temp_load.not_sql:1235: NOTICE: type reference _object_reference.object_group.object_group_id%TYPE converted to integer +psql:test/temp_load.not_sql:1235: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying +psql:test/temp_load.not_sql:1235: NOTICE: type reference _object_reference.object_group.object_group_id%TYPE converted to integer +psql:test/temp_load.not_sql:1235: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying +psql:test/temp_load.not_sql:1249: NOTICE: type reference _object_reference.object_group.object_group_id%TYPE converted to integer +psql:test/temp_load.not_sql:1249: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying +psql:test/temp_load.not_sql:1249: NOTICE: type reference _object_reference.object_group.object_group_id%TYPE converted to integer +psql:test/temp_load.not_sql:1249: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying +psql:test/temp_load.not_sql:1249: NOTICE: type reference _object_reference.object_group.object_group_id%TYPE converted to integer +psql:test/temp_load.not_sql:1249: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying + +psql:test/temp_load.not_sql:1296: NOTICE: type reference _object_reference.object_group.object_group_id%TYPE converted to integer +psql:test/temp_load.not_sql:1296: NOTICE: type reference _object_reference.object_group.object_group_id%TYPE converted to integer + +psql:test/temp_load.not_sql:1310: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying +psql:test/temp_load.not_sql:1310: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying + +psql:test/temp_load.not_sql:1355: NOTICE: type reference _object_reference.object_group.object_group_id%TYPE converted to integer +psql:test/temp_load.not_sql:1355: NOTICE: type reference _object_reference.object_group.object_group_id%TYPE converted to integer + +psql:test/temp_load.not_sql:1369: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying +psql:test/temp_load.not_sql:1369: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying diff --git a/test/load.sql b/test/load.sql index 0f1c6be..f1b267f 100644 --- a/test/load.sql +++ b/test/load.sql @@ -1,6 +1,5 @@ \i test/pgxntool/setup.sql --- Need to add count_nulls back into the path SET search_path = tap, public; -- Don't use IF NOT EXISTS here; we want to ensure we always have the latest code diff --git a/test/sql/base.sql b/test/sql/base.sql index 202ae5f..10f48f7 100644 --- a/test/sql/base.sql +++ b/test/sql/base.sql @@ -8,8 +8,8 @@ SELECT plan( 0 +1 -- schema +3 -- initial - +2 -- errors - +2 -- move + +2 -- new functions + +3 -- errors (includes temp object test) +1 -- create extensions ); @@ -32,9 +32,23 @@ SELECT lives_ok( , $$CREATE TEMP TABLE test_object AS SELECT object_reference.object__getsert('table', 'test_table') AS object_id;$$ ); SELECT is( - (SELECT regclass FROM _object_reference._object_v WHERE object_id = (SELECT object_id FROM test_object)) - , 'test_table'::regclass - , 'Verify regclass field is correct' + (SELECT object_oid FROM _object_reference._object_v WHERE object_id = (SELECT object_id FROM test_object)) + , 'test_table'::regclass::oid + , 'Verify object_oid field is correct' +); + +-- Test object__describe function +SELECT is( + object_reference.object__describe((SELECT object_id FROM test_object)) + , pg_catalog.pg_describe_object('pg_class'::regclass, 'test_table'::regclass, 0) + , 'object__describe returns same result as pg_describe_object' +); + +-- Test object__identity function +SELECT results_eq( + $$SELECT * FROM object_reference.object__identity((SELECT object_id FROM test_object))$$ + , $$SELECT type, schema, name, identity FROM pg_catalog.pg_identify_object('pg_class'::regclass, 'test_table'::regclass, 0)$$ + , 'object__identity returns same result as pg_identify_object' ); SELECT is( object_reference.object__getsert('table', 'test_table') @@ -50,24 +64,13 @@ SELECT throws_ok( , 'secondary may not be specified for table objects' ); -/* - * I'm not sure if our extension would continue working if count_nulls was - * relocated. Currently a moot point since relocation isn't supported, but I'd - * already coded the second test so might as well leave it here in case it - * changes in the future. - */ -\set null_schema test_relocate_count_nulls -CREATE SCHEMA :null_schema; +-- Test temp object rejection +CREATE TEMP TABLE temp_test_table(); SELECT throws_ok( - $$ALTER EXTENSION count_nulls SET SCHEMA $$ || :'null_schema' - , '0A000' - , NULL - , 'Verify count_nulls extension can not be relocated' -); -SELECT is( - object_reference.object__getsert('table', 'test_table') - , (SELECT object_id FROM test_object) - , 'Still works after moving the count_nulls extension' + $$SELECT object_reference.object__getsert('table', 'temp_test_table')$$ + , '0A000' -- feature_not_supported + , 'cannot track temporary object' + , 'temp objects are rejected' ); -- Create extensions diff --git a/test/sql/object_group.sql b/test/sql/object_group.sql index 53b3303..299f05f 100644 --- a/test/sql/object_group.sql +++ b/test/sql/object_group.sql @@ -2,8 +2,8 @@ \i test/load.sql -CREATE TEMP TABLE test_table_1(col1 int, col2 int); -CREATE TEMP TABLE test_table_2(col1 int, col2 int); +CREATE TABLE object_group_test_table_1(col1 int, col2 int); +CREATE TABLE object_group_test_table_2(col1 int, col2 int); CREATE FUNCTION pg_temp.bogus_group( command_template text @@ -40,10 +40,12 @@ SELECT plan( +4 -- __object__remove +4 + 2 -- __remove + +4 -- cleanup tests + +1 -- final group removal (there was always an extra test) ); SELECT lives_ok( - $$CREATE TEMP TABLE test_table_1_id AS SELECT * FROM object_reference.object__getsert('table', 'test_table_1')$$ + $$CREATE TEMP TABLE test_table_1_id AS SELECT * FROM object_reference.object__getsert('table', 'object_group_test_table_1')$$ , 'Register test table 1' ); @@ -102,37 +104,37 @@ SELECT lives_ok( -- object__getsert SELECT throws_ok( -- Can't use helper here - $$CREATE TEMP TABLE col1_id AS SELECT * FROM object_reference.object__getsert('table column', 'test_table_1', 'col1', 'absurd group name used only for testing purposes ktxbye')$$ + $$CREATE TEMP TABLE col1_id AS SELECT * FROM object_reference.object__getsert('table column', 'object_group_test_table_1', 'col1', 'absurd group name used only for testing purposes ktxbye')$$ , 'P0002' , 'object group "absurd group name used only for testing purposes ktxbye" does not exist' , 'object__getsert with bogus group name' ); /* EXCLUDED CODE: TODO SELECT throws_ok( -- Can't use helper here - $$CREATE TEMP TABLE col1_id AS SELECT * FROM object_reference.object__getsert_w_group_id('table column', 'test_table_1', 'col1', -1)$$ + $$CREATE TEMP TABLE col1_id AS SELECT * FROM object_reference.object__getsert_w_group_id('table column', 'object_group_test_table_1', 'col1', -1)$$ , '' , '' , 'object__getsert with bogus group id' ); */ SELECT lives_ok( - $$CREATE TEMP TABLE col1_id AS SELECT * FROM object_reference.object__getsert('table column', 'test_table_1', 'col1', 'object reference test group')$$ + $$CREATE TEMP TABLE col1_id AS SELECT * FROM object_reference.object__getsert('table column', 'object_group_test_table_1', 'col1', 'object reference test group')$$ , 'Register test column' ); SELECT lives_ok( - $$CREATE TEMP TABLE test_table_2_id AS SELECT * FROM object_reference.object__getsert('table', 'test_table_2', object_group_name := 'object reference test group')$$ + $$CREATE TEMP TABLE test_table_2_id AS SELECT * FROM object_reference.object__getsert('table', 'object_group_test_table_2', object_group_name := 'object reference test group')$$ , 'Register test table 2' ); -- Drop tests SELECT throws_ok( - $$ALTER TABLE test_table_1 DROP COLUMN col1$$ + $$ALTER TABLE object_group_test_table_1 DROP COLUMN col1$$ , '23503' , NULL -- current error is crap anyway , 'Dropping col1 fails' ); SELECT throws_ok( - $$DROP TABLE test_table_2$$ + $$DROP TABLE object_group_test_table_2$$ , '23503' , NULL -- current error is crap anyway , 'Dropping test_table_2 fails' @@ -144,7 +146,7 @@ SELECT throws_ok( , 'Removing test group fails' ); SELECT lives_ok( - $$ALTER TABLE test_table_1 DROP COLUMN col2$$ + $$ALTER TABLE object_group_test_table_1 DROP COLUMN col2$$ , 'Dropping col2 works' ); @@ -178,7 +180,7 @@ SELECT lives_ok( , '__object__remove() for test_table_1 works' ); SELECT throws_ok( - $$DROP TABLE test_table_1$$ -- Should not work because column is still registered + $$DROP TABLE object_group_test_table_1$$ -- Should not work because column is still registered , '23503' , NULL -- current error is crap anyway , 'Dropping test_table_1 fails' @@ -211,6 +213,26 @@ SELECT lives_ok( ) , '__object__remove() for test_table_2 works' ); + +-- Test automatic cleanup via trigger +SELECT lives_ok( + $$CREATE TEMP TABLE cleanup_test_id AS SELECT * FROM object_reference.object__getsert('table', 'object_group_test_table_1', object_group_name := 'object reference test group')$$ + , 'Add test table back to group for cleanup test' +); +SELECT ok( + EXISTS(SELECT 1 FROM _object_reference.object WHERE object_id = (SELECT object__getsert FROM cleanup_test_id)) + , 'Object exists before cleanup test' +); +SELECT lives_ok( + $$DELETE FROM _object_reference.object_group__object WHERE object_id = (SELECT object__getsert FROM cleanup_test_id)$$ + , 'Remove from group triggers automatic cleanup attempt' +); +-- Object should be deleted because it's no longer in any group and trigger calls cleanup +SELECT ok( + NOT EXISTS(SELECT 1 FROM _object_reference.object WHERE object_id = (SELECT object__getsert FROM cleanup_test_id)) + , 'Object was automatically cleaned up after group removal' +); + SELECT lives_ok( $$SELECT object_reference.object_group__remove('object reference test group')$$ , 'Removing empty group works' diff --git a/test/sql/zzz_build.sql b/test/sql/zzz_build.sql index 4da65b4..4fc0628 100644 --- a/test/sql/zzz_build.sql +++ b/test/sql/zzz_build.sql @@ -6,7 +6,6 @@ -- Loads deps, but not extension itself \i test/pgxntool/setup.sql -CREATE EXTENSION IF NOT EXISTS count_nulls; CREATE EXTENSION IF NOT EXISTS cat_tools; CREATE SCHEMA object_reference; From 828281b0dac4d4869a51b18072946af4e1609505 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Mon, 10 Aug 2026 15:42:59 -0500 Subject: [PATCH 04/10] Fix object_group.sql's pgTAP plan count for the __remove section (6 -> 5) The __remove section runs 5 assertions (bogus_group=2, throws_ok=1, lives_ok=2) but the plan math counted +4 + 2 = 6. Confirmed via GitHub's own CI run for this branch: PostgreSQL 12 reported "planned 30 tests but ran 29" under make verify-results, the real pgtap-aware gate -- invisible to plain pg_regress diffing since the stale count was already baked into the committed expected-output file. test/expected/object_group.out regenerated via make results, not hand-edited. Co-Authored-By: Claude --- test/expected/object_group.out | 8 ++++++-- test/sql/object_group.sql | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/test/expected/object_group.out b/test/expected/object_group.out index c3f5eb8..e670106 100644 --- a/test/expected/object_group.out +++ b/test/expected/object_group.out @@ -1,5 +1,5 @@ \set ECHO none -1..25 +1..29 ok 1 - Register test table 1 ok 2 - object_group__create(...) for group name that is too long throws error ok 3 - object_group__create('object reference test group') @@ -24,5 +24,9 @@ ok 21 - object_group__object__add(...)for missing group throws error ok 22 - Removing group with items in it fails ok 23 - __object__remove() for col1 works ok 24 - __object__remove() for test_table_2 works -ok 25 - Removing empty group works +ok 25 - Add test table back to group for cleanup test +ok 26 - Object exists before cleanup test +ok 27 - Remove from group triggers automatic cleanup attempt +ok 28 - Object was automatically cleaned up after group removal +ok 29 - Removing empty group works # TRANSACTION INTENTIONALLY LEFT OPEN! diff --git a/test/sql/object_group.sql b/test/sql/object_group.sql index 299f05f..4839ff4 100644 --- a/test/sql/object_group.sql +++ b/test/sql/object_group.sql @@ -39,7 +39,7 @@ SELECT plan( +4 -- __object__remove - +4 + 2 -- __remove + +3 + 2 -- __remove +4 -- cleanup tests +1 -- final group removal (there was always an extra test) ); From b32fc80562d0dde23bf7c4eb8b2a4d259469b32d Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Mon, 10 Aug 2026 16:44:45 -0500 Subject: [PATCH 05/10] Suppress NOTICEs during CREATE EXTENSION via SET LOCAL client_min_messages sql/object_reference.sql's many %TYPE column references (e.g. _object_oid.objid%TYPE) each fire a NOTICE at parse time, spamming CREATE EXTENSION's output for no reason a normal install needs to see. SET LOCAL (not SET) so it reverts automatically at the end of CREATE EXTENSION's implicit transaction rather than leaking into the installing session. Co-Authored-By: Claude --- sql/object_reference.sql | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sql/object_reference.sql b/sql/object_reference.sql index 95567ae..b5f9c7f 100644 --- a/sql/object_reference.sql +++ b/sql/object_reference.sql @@ -2,6 +2,14 @@ \echo You really, REALLY do NOT want to try and load this via psql!!! \echo It will FAIL during pg_dump! \quit +-- SET LOCAL, not SET: this script runs inside CREATE EXTENSION's implicit +-- transaction, so LOCAL reverts automatically once it commits -- a plain SET +-- would leak into the installing session for the rest of its lifetime. +-- Suppresses NOTICEs like the %TYPE resolution messages this script's many +-- column-type references (e.g. _object_oid.objid%TYPE) would otherwise spam +-- CREATE EXTENSION's output with. +SET LOCAL client_min_messages = WARNING; + /* DO $$ DECLARE From 36005e50e03cb0d9fa2f39585883f6b210fabb5e Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Mon, 10 Aug 2026 16:45:05 -0500 Subject: [PATCH 06/10] Drop _object_oid.object_oid -- always identical to objid, never diverges Before this stack, _object_oid had six separate reg*-typed columns (one per object class) plus object_oid as a plain-oid projection of whichever one applied to a given row -- a real distinction, since the reg* columns bought per-class dump/rename safety and object_oid bought a uniform plain-oid value for cross-class queries. This PR already collapsed those six reg* columns down to the classid/objid/objsubid triple (mirroring pg_get_object_address()'s own output), which removed the only thing object_oid used to distill -- _object_oid__add's insert already just set it to objid unconditionally, and nothing else ever wrote to this table. classid/objid/objsubid live in the private _object_reference schema, so there's no external API stability concern with removing a column here. Drops the now-vacuous objid_must_match CHECK along with the column, and updates test/sql/base.sql's assertion to check objid directly. test/expected/base.out and test/expected/zzz_build.out regenerated via make results, not hand-edited. Co-Authored-By: Claude --- sql/object_reference.sql | 10 ++---- test/expected/base.out | 2 +- test/expected/zzz_build.out | 68 +++---------------------------------- test/sql/base.sql | 4 +-- 4 files changed, 10 insertions(+), 74 deletions(-) diff --git a/sql/object_reference.sql b/sql/object_reference.sql index b5f9c7f..a53e113 100644 --- a/sql/object_reference.sql +++ b/sql/object_reference.sql @@ -195,11 +195,7 @@ CREATE TABLE _object_reference._object_oid( */ , objid oid NOT NULL , objsubid int NOT NULL - CONSTRAINT objid_must_match CHECK( -- _object_reference._sanity() depends on this! - objid IS NOT DISTINCT FROM object_oid - ) , CONSTRAINT object__u_classid__objid__objsubid UNIQUE( classid, objid, objsubid ) - , object_oid oid NOT NULL ); SELECT __object_reference.create_function( @@ -260,7 +256,6 @@ CREATE VIEW _object_reference._object_v AS , i.classid , i.objid , i.objsubid - , i.object_oid , s.* FROM _object_reference.object o LEFT JOIN _object_reference._object_oid i USING(object_id) @@ -275,7 +270,6 @@ CREATE VIEW _object_reference._object_v__for_update AS , i.classid , i.objid , i.objsubid - , i.object_oid , s.* FROM _object_reference.object o LEFT JOIN _object_reference._object_oid i USING(object_id) @@ -307,8 +301,8 @@ BEGIN ; END IF; BEGIN - INSERT INTO _object_reference._object_oid(object_id, classid, objid, objsubid, object_oid) - VALUES (object_id, classid, objid, objsubid, objid); + INSERT INTO _object_reference._object_oid(object_id, classid, objid, objsubid) + VALUES (object_id, classid, objid, objsubid); SELECT INTO STRICT r_object_v -- Record better exist! * diff --git a/test/expected/base.out b/test/expected/base.out index 5357054..ac3edd2 100644 --- a/test/expected/base.out +++ b/test/expected/base.out @@ -3,7 +3,7 @@ ok 1 - Role object_reference__dependency should be granted USAGE on schema _object_reference ok 2 - Role object_reference__dependency should be granted REFERENCES on table _object_reference.object ok 3 - CREATE TEMP TABLE test_object AS SELECT object_reference.object__getsert('table', 'test_table') AS object_id; -ok 4 - Verify object_oid field is correct +ok 4 - Verify objid field is correct ok 5 - object__describe returns same result as pg_describe_object ok 6 - object__identity returns same result as pg_identify_object ok 7 - Existing object works, provides correct ID diff --git a/test/expected/zzz_build.out b/test/expected/zzz_build.out index 99e1332..7eb2a29 100644 --- a/test/expected/zzz_build.out +++ b/test/expected/zzz_build.out @@ -2,26 +2,16 @@ This extension must be loaded via CREATE EXTENSION object_reference; You really, REALLY do NOT want to try and load this via psql!!! -psql:test/temp_load.not_sql:176: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! +psql:test/temp_load.not_sql:184: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! -psql:test/temp_load.not_sql:177: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! +psql:test/temp_load.not_sql:185: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! -psql:test/temp_load.not_sql:323: NOTICE: type reference _object_reference._object_oid.object_id%TYPE converted to integer -psql:test/temp_load.not_sql:323: NOTICE: type reference _object_reference.object.object_type%TYPE converted to cat_tools.object_type -psql:test/temp_load.not_sql:323: NOTICE: type reference _object_reference._object_oid.classid%TYPE converted to oid -psql:test/temp_load.not_sql:323: NOTICE: type reference _object_reference._object_oid.objid%TYPE converted to oid -psql:test/temp_load.not_sql:323: NOTICE: type reference _object_reference._object_oid.objsubid%TYPE converted to integer -psql:test/temp_load.not_sql:323: NOTICE: type reference _object_reference._object_oid.object_id%TYPE converted to integer -psql:test/temp_load.not_sql:323: NOTICE: type reference _object_reference.object.object_type%TYPE converted to cat_tools.object_type -psql:test/temp_load.not_sql:323: NOTICE: type reference _object_reference._object_oid.classid%TYPE converted to oid -psql:test/temp_load.not_sql:323: NOTICE: type reference _object_reference._object_oid.objid%TYPE converted to oid -psql:test/temp_load.not_sql:323: NOTICE: type reference _object_reference._object_oid.objsubid%TYPE converted to integer -psql:test/temp_load.not_sql:431: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! +psql:test/temp_load.not_sql:433: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! @@ -31,80 +21,32 @@ psql:test/temp_load.not_sql:431: WARNING: I promise you will be sorry if you tr -psql:test/temp_load.not_sql:543: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! +psql:test/temp_load.not_sql:545: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! -psql:test/temp_load.not_sql:550: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! +psql:test/temp_load.not_sql:552: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! -psql:test/temp_load.not_sql:595: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying -psql:test/temp_load.not_sql:595: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying -psql:test/temp_load.not_sql:620: NOTICE: type reference _object_reference.object_group.object_group_id%TYPE converted to integer -psql:test/temp_load.not_sql:620: NOTICE: type reference _object_reference.object_group.object_group_id%TYPE converted to integer -psql:test/temp_load.not_sql:635: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying -psql:test/temp_load.not_sql:635: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying -psql:test/temp_load.not_sql:662: NOTICE: type reference _object_reference.object_group.object_group_id%TYPE converted to integer -psql:test/temp_load.not_sql:662: NOTICE: type reference _object_reference.object_group.object_group_id%TYPE converted to integer -psql:test/temp_load.not_sql:678: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying -psql:test/temp_load.not_sql:678: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying -psql:test/temp_load.not_sql:695: NOTICE: type reference _object_reference.object_group__object.object_group_id%TYPE converted to integer -psql:test/temp_load.not_sql:695: NOTICE: type reference _object_reference.object_group__object.object_id%TYPE converted to integer -psql:test/temp_load.not_sql:695: NOTICE: type reference _object_reference.object_group__object.object_group_id%TYPE converted to integer -psql:test/temp_load.not_sql:695: NOTICE: type reference _object_reference.object_group__object.object_id%TYPE converted to integer -psql:test/temp_load.not_sql:729: NOTICE: type reference _object_reference.object_group__object.object_group_id%TYPE converted to integer -psql:test/temp_load.not_sql:729: NOTICE: type reference _object_reference.object_group__object.object_id%TYPE converted to integer -psql:test/temp_load.not_sql:729: NOTICE: type reference _object_reference.object_group__object.object_group_id%TYPE converted to integer -psql:test/temp_load.not_sql:729: NOTICE: type reference _object_reference.object_group__object.object_id%TYPE converted to integer -psql:test/temp_load.not_sql:967: NOTICE: type reference _object_reference.object.object_type%TYPE converted to cat_tools.object_type -psql:test/temp_load.not_sql:967: NOTICE: type reference _object_reference._object_oid.objid%TYPE converted to oid -psql:test/temp_load.not_sql:967: NOTICE: type reference _object_reference._object_oid.objsubid%TYPE converted to integer -psql:test/temp_load.not_sql:967: NOTICE: type reference _object_reference.object.object_type%TYPE converted to cat_tools.object_type -psql:test/temp_load.not_sql:967: NOTICE: type reference _object_reference._object_oid.objid%TYPE converted to oid -psql:test/temp_load.not_sql:967: NOTICE: type reference _object_reference._object_oid.objsubid%TYPE converted to integer -psql:test/temp_load.not_sql:1194: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying -psql:test/temp_load.not_sql:1194: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying -psql:test/temp_load.not_sql:1208: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying -psql:test/temp_load.not_sql:1208: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying -psql:test/temp_load.not_sql:1235: NOTICE: type reference _object_reference.object_group.object_group_id%TYPE converted to integer -psql:test/temp_load.not_sql:1235: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying -psql:test/temp_load.not_sql:1235: NOTICE: type reference _object_reference.object_group.object_group_id%TYPE converted to integer -psql:test/temp_load.not_sql:1235: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying -psql:test/temp_load.not_sql:1235: NOTICE: type reference _object_reference.object_group.object_group_id%TYPE converted to integer -psql:test/temp_load.not_sql:1235: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying -psql:test/temp_load.not_sql:1249: NOTICE: type reference _object_reference.object_group.object_group_id%TYPE converted to integer -psql:test/temp_load.not_sql:1249: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying -psql:test/temp_load.not_sql:1249: NOTICE: type reference _object_reference.object_group.object_group_id%TYPE converted to integer -psql:test/temp_load.not_sql:1249: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying -psql:test/temp_load.not_sql:1249: NOTICE: type reference _object_reference.object_group.object_group_id%TYPE converted to integer -psql:test/temp_load.not_sql:1249: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying -psql:test/temp_load.not_sql:1296: NOTICE: type reference _object_reference.object_group.object_group_id%TYPE converted to integer -psql:test/temp_load.not_sql:1296: NOTICE: type reference _object_reference.object_group.object_group_id%TYPE converted to integer -psql:test/temp_load.not_sql:1310: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying -psql:test/temp_load.not_sql:1310: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying -psql:test/temp_load.not_sql:1355: NOTICE: type reference _object_reference.object_group.object_group_id%TYPE converted to integer -psql:test/temp_load.not_sql:1355: NOTICE: type reference _object_reference.object_group.object_group_id%TYPE converted to integer -psql:test/temp_load.not_sql:1369: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying -psql:test/temp_load.not_sql:1369: NOTICE: type reference _object_reference.object_group.object_group_name%TYPE converted to character varying diff --git a/test/sql/base.sql b/test/sql/base.sql index 10f48f7..fb07b76 100644 --- a/test/sql/base.sql +++ b/test/sql/base.sql @@ -32,9 +32,9 @@ SELECT lives_ok( , $$CREATE TEMP TABLE test_object AS SELECT object_reference.object__getsert('table', 'test_table') AS object_id;$$ ); SELECT is( - (SELECT object_oid FROM _object_reference._object_v WHERE object_id = (SELECT object_id FROM test_object)) + (SELECT objid FROM _object_reference._object_v WHERE object_id = (SELECT object_id FROM test_object)) , 'test_table'::regclass::oid - , 'Verify object_oid field is correct' + , 'Verify objid field is correct' ); -- Test object__describe function From 75abc99e94c5796e948462291b6f9372bb20ed73 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Mon, 10 Aug 2026 17:02:04 -0500 Subject: [PATCH 07/10] Fix multi-line comment to use block format, not consecutive -- lines CODE_STYLE.md requires /* ... */ for multi-line SQL comments, never multi-line -- line comments. Applies to the client_min_messages explanation. test/expected/zzz_build.out regenerated via make results to reflect the resulting line-number shift in temp_load.not_sql's WARNING output. Co-Authored-By: Claude --- sql/object_reference.sql | 14 ++++++++------ test/expected/zzz_build.out | 10 +++++----- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/sql/object_reference.sql b/sql/object_reference.sql index a53e113..fe1251f 100644 --- a/sql/object_reference.sql +++ b/sql/object_reference.sql @@ -2,12 +2,14 @@ \echo You really, REALLY do NOT want to try and load this via psql!!! \echo It will FAIL during pg_dump! \quit --- SET LOCAL, not SET: this script runs inside CREATE EXTENSION's implicit --- transaction, so LOCAL reverts automatically once it commits -- a plain SET --- would leak into the installing session for the rest of its lifetime. --- Suppresses NOTICEs like the %TYPE resolution messages this script's many --- column-type references (e.g. _object_oid.objid%TYPE) would otherwise spam --- CREATE EXTENSION's output with. +/* + * SET LOCAL, not SET: this script runs inside CREATE EXTENSION's implicit + * transaction, so LOCAL reverts automatically once it commits -- a plain SET + * would leak into the installing session for the rest of its lifetime. + * Suppresses NOTICEs like the %TYPE resolution messages this script's many + * column-type references (e.g. _object_oid.objid%TYPE) would otherwise spam + * CREATE EXTENSION's output with. + */ SET LOCAL client_min_messages = WARNING; /* diff --git a/test/expected/zzz_build.out b/test/expected/zzz_build.out index 7eb2a29..c5b269d 100644 --- a/test/expected/zzz_build.out +++ b/test/expected/zzz_build.out @@ -2,16 +2,16 @@ This extension must be loaded via CREATE EXTENSION object_reference; You really, REALLY do NOT want to try and load this via psql!!! -psql:test/temp_load.not_sql:184: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! +psql:test/temp_load.not_sql:186: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! -psql:test/temp_load.not_sql:185: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! +psql:test/temp_load.not_sql:187: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! -psql:test/temp_load.not_sql:433: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! +psql:test/temp_load.not_sql:435: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! @@ -21,9 +21,9 @@ psql:test/temp_load.not_sql:433: WARNING: I promise you will be sorry if you tr -psql:test/temp_load.not_sql:545: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! +psql:test/temp_load.not_sql:547: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! -psql:test/temp_load.not_sql:552: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! +psql:test/temp_load.not_sql:554: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! From 7f58f0ebbbb4ee1905b5e9d81df42967ba8d1b2d Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Mon, 10 Aug 2026 17:38:27 -0500 Subject: [PATCH 08/10] Add README.md and fix META packaging metadata (fixes #35) Adds a README documenting installation, roles, key concepts, and the full public API, carried forward from PR #2 and verified against the current schema (object__describe/identity/cleanup, capture__*, object group functions). Fixes META.in.json (source for the generated META.json): adds cat_tools to runtime.requires (a real, current dependency per object_reference.control), updates decibel/* org URLs to Postgres-Extensions/*, and bumps the stated PostgreSQL build requirement from 9.5.0 to 12.0.0, matching the floor already established by this repo's CI matrix (derived from cat_tools's own 12.0.0 build requirement). Co-Authored-By: Claude --- META.in.json | 15 +-- META.json | 15 +-- README.md | 333 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 349 insertions(+), 14 deletions(-) create mode 100644 README.md diff --git a/META.in.json b/META.in.json index d01dc70..129a40d 100644 --- a/META.in.json +++ b/META.in.json @@ -9,7 +9,7 @@ "X_comment": "The pgxntool Makefile will strip out empty keys to produce a working META.json, so it's fine to leave them here.", "X_comment": "NOTE: Don't get too cute with reformatting this file... some of the processing against it doesn't use a full JSON parser", "X_comment": "", - "X_comment": "I've tried to put things in a logical order. Comments welcome at https://github.com/decibel/pgxntool/issues!", + "X_comment": "I've tried to put things in a logical order. Comments welcome at https://github.com/Postgres-Extensions/pgxntool/issues!", "X_comment": "REQUIRED. Name of distribution.", "X_comment": "Note! Distribution names currently can't contain spaces.", @@ -58,13 +58,13 @@ "X_comment": "Optional. \"resources\": Web resources available for this distribution. http://pgxn.org/spec/#resources", "resources": { - "homepage": "http://github.com/decibel/object_reference/", + "homepage": "http://github.com/Postgres-Extensions/object_reference/", "bugtracker": { - "web": "http://github.com/decibel/object_reference/issues" + "web": "http://github.com/Postgres-Extensions/object_reference/issues" }, "repository": { - "url": "git://github.com/decibel/object_reference.git", - "web": "http://github.com/decibel/object_reference/", + "url": "git://github.com/Postgres-Extensions/object_reference.git", + "web": "http://github.com/Postgres-Extensions/object_reference/", "type": "git" } }, @@ -76,11 +76,12 @@ "build": { "requires": { - "PostgreSQL": "9.5.0" + "PostgreSQL": "12.0.0" } }, "runtime": { "requires": { + "cat_tools": 0, "plpgsql": 0 } }, @@ -95,7 +96,7 @@ "no_index": "", "X_comment": "Unusual. Package/tool used to generate this file.", - "generated_by": "pgxntool https://github.com/decibel/pgxntool", + "generated_by": "pgxntool https://github.com/Postgres-Extensions/pgxntool", "X_comment": "REQUIRED. Version info for this file. http://pgxn.org/spec/#meta-spec", "meta-spec": { diff --git a/META.json b/META.json index 0512fbb..cfaaca4 100644 --- a/META.json +++ b/META.json @@ -9,7 +9,7 @@ "X_comment": "Keys marked REQUIRED or Optional are what you'd expect. Keys marked Unusual are keys you don't normally need to use.", "X_comment": "The pgxntool Makefile will strip out empty keys to produce a working META.json, so it's fine to leave them here.", "X_comment": "NOTE: Don't get too cute with reformatting this file... some of the processing against it doesn't use a full JSON parser", - "X_comment": "I've tried to put things in a logical order. Comments welcome at https://github.com/decibel/pgxntool/issues!", + "X_comment": "I've tried to put things in a logical order. Comments welcome at https://github.com/Postgres-Extensions/pgxntool/issues!", "X_comment": "REQUIRED. Name of distribution.", "X_comment": "Note! Distribution names currently can't contain spaces.", @@ -56,13 +56,13 @@ "X_comment": "Optional. \"resources\": Web resources available for this distribution. http://pgxn.org/spec/#resources", "resources": { - "homepage": "http://github.com/decibel/object_reference/", + "homepage": "http://github.com/Postgres-Extensions/object_reference/", "bugtracker": { - "web": "http://github.com/decibel/object_reference/issues" + "web": "http://github.com/Postgres-Extensions/object_reference/issues" }, "repository": { - "url": "git://github.com/decibel/object_reference.git", - "web": "http://github.com/decibel/object_reference/", + "url": "git://github.com/Postgres-Extensions/object_reference.git", + "web": "http://github.com/Postgres-Extensions/object_reference/", "type": "git" } }, @@ -74,11 +74,12 @@ "build": { "requires": { - "PostgreSQL": "9.5.0" + "PostgreSQL": "12.0.0" } }, "runtime": { "requires": { + "cat_tools": 0, "plpgsql": 0 } }, @@ -92,7 +93,7 @@ "X_comment": "Unusual. \"no_index\": Files/directories that should not be indexed. http://pgxn.org/spec/#no_index", "X_comment": "Unusual. Package/tool used to generate this file.", - "generated_by": "pgxntool https://github.com/decibel/pgxntool", + "generated_by": "pgxntool https://github.com/Postgres-Extensions/pgxntool", "X_comment": "REQUIRED. Version info for this file. http://pgxn.org/spec/#meta-spec", "meta-spec": { diff --git a/README.md b/README.md new file mode 100644 index 0000000..ae69147 --- /dev/null +++ b/README.md @@ -0,0 +1,333 @@ +# Object Reference Framework + +This framework provides tracking and management of references to database objects. It's designed to maintain referential integrity for objects that may be created, dropped, or renamed, and provides facilities for automatically capturing newly created objects into organized groups. + +Key capabilities: +- Track references to database objects that may be created, dropped, or renamed +- Group related objects for organization and DDL capture +- Automatically capture DDL operations to track new objects +- Manage dependencies between objects and external tables +- Support for object lifecycle management + +# A word on documentation... + +Good documentation should be like good code comments - explain things concisely without being overly verbose. Towards that end, this doc does *not* provide definition for things that should be inherently obvious, other than mentioning their existence. For example, we never define what is meant by `object_type`. The name itself should provide enough information. + +# Installation + +This extension depends on the `cat_tools` extension. + +```sql +CREATE EXTENSION object_reference CASCADE; +``` + +The extension creates two schemas: +- `object_reference` - Contains the public API functions +- `_object_reference` - Contains internal implementation details (do not use directly) + +To grant users access to the extension: +```sql +GRANT object_reference__usage TO role1, role2, role3; +``` + +# Security + +There are two roles associated with the extension: + +- `object_reference__usage` - Allows using the extension's public API functions. Grant this to users who need to track and manage object references. +- `object_reference__dependency` - Special role for creating foreign key dependencies to the internal object table. Only grant this to schemas/applications that need to create referential integrity constraints against the object tracking system. See [Referring to Objects](#referring-to-objects) below. + +Most users will only need `object_reference__usage`. The `object_reference__dependency` role is only needed when using `object__dependency__add()` or `object_group__dependency__add()` functions. + +# Key Concepts + +## Objects vs OIDs + +The framework separates object metadata (names, types, arguments) from their actual database OIDs. This allows tracking objects that don't exist yet, or that may be recreated. OID resolution is performed lazily - only when actually needed. + +## Object Groups + +Objects can be organized into named groups for logical organization. This is particularly useful for tracking all objects created during a specific operation or time period, especially when combined with DDL capture. + +## DDL Capture + +The framework can automatically capture newly created objects during DDL operations and add them to a specified object group. This is implemented using PostgreSQL event triggers. + +## Referring to Objects + +The framework supports removing objects that are no longer referenced. Because of this, *it is critical that any tables that store an `object_id` are registered with `object__dependency__add()`*. + +# API + +Note that all API routines live in the `object_reference` schema. Objects in the `_object_reference` schema are considered internal-only and should not be accessed directly. + +Most routines work with the `cat_tools.object_type` enum for specifying object types. You can also pass object types as text strings which will be converted automatically. + +## Core Object Functions + +### `object__getsert(...) RETURNS int` + +```sql +object__getsert( + object_type text | cat_tools.object_type + , object_name text + , secondary text DEFAULT NULL + , object_group_name text DEFAULT NULL + , loose boolean DEFAULT false +) RETURNS int +``` + +Get or insert an object reference, returning the `object_id`. This is the primary function for tracking objects. + +Arguments: +- `object_type` - Type of object (table, function, index, etc.) +- `object_name` - Fully qualified name of the object +- `secondary` - Additional identifier for objects that need it (e.g., function arguments) +- `object_group_name` - Optional object group to add this object to +- `loose` - If true, allows creating references to objects that don't exist + +### `object__getsert_w_group_id(...) RETURNS int` + +```sql +object__getsert_w_group_id( + object_type cat_tools.object_type + , object_name text + , secondary text DEFAULT NULL + , object_group_id int DEFAULT NULL + , loose boolean DEFAULT false +) RETURNS int +``` + +Same as `object__getsert()` but accepts a numeric `object_group_id` instead of group name. + +### `object__describe(object_id int) RETURNS text` + +Returns a human-readable description of the object, matching the format of PostgreSQL's `pg_describe_object()` function. + +### `object__identity(object_id int) RETURNS record` + +Returns object identification information matching the format of PostgreSQL's `pg_identify_object()` function. Returns a record with columns: `type`, `schema`, `name`, `identity`. + + +## Object Group Functions + +### `object_group__create(...) RETURNS int` + +```sql +object_group__create( + object_group_name text +) RETURNS int +``` + +Create a new object group and return its ID. + +### `object_group__get(...) RETURNS object_group` + +```sql +object_group__get( + object_group_name text | object_group_id int +) RETURNS _object_reference.object_group +``` + +Retrieve an object group by name or ID. Throws an error if the group doesn't exist. + +### `object_group__remove(...) RETURNS void` + +```sql +object_group__remove( + object_group_name text | object_group_id int + , force boolean DEFAULT false +) RETURNS void +``` + +Remove an object group. This does not delete the objects themselves, only the grouping. + +### `object_group__object__add(...) RETURNS void` + +```sql +object_group__object__add( + object_group_id int + , object_id int +) RETURNS void +``` + +Add an existing object to an object group. + +### `object_group__object__remove(...) RETURNS void` + +```sql +object_group__object__remove( + object_group_id int + , object_id int +) RETURNS void +``` + +Remove an object from an object group. + +## Dependency Functions + +These functions create foreign key dependencies to the object tracking system. They require the `object_reference__dependency` role. + +### `object__dependency__add(...) RETURNS void` + +```sql +object__dependency__add( + table_name text + , field_name name +) RETURNS void +``` + +Create a foreign key dependency from the specified table to the object tracking system. + +Arguments: +- `table_name` - Name of table to add dependency to +- `field_name` - Name of the field to create the foreign key on + +### `object_group__dependency__add(...) RETURNS void` + +```sql +object_group__dependency__add( + table_name text + , field_name name +) RETURNS void +``` + +Create a foreign key dependency from the specified table to the object group system. + +## DDL Capture Functions + +DDL capture allows you to automatically track objects created during DDL operations. + +### `capture__start(...) RETURNS int` + +```sql +capture__start( + object_group_name text | object_group_id int +) RETURNS int +``` + +Begin capturing newly created objects to the specified group. The group must +already exist. Returns the capture level (for nested captures). + +### `capture__stop(...) RETURNS void` + +```sql +capture__stop( + object_group_name text | object_group_id int +) RETURNS void +``` + +Stop capturing objects to the specified group. + +### `capture__get_current(...) RETURNS record` + +```sql +capture__get_current( + OUT capture_level int + , OUT object_group_id int +) RETURNS record +``` + +Get information about the current capture state. + +### `capture__get_all(...) RETURNS SETOF record` + +```sql +capture__get_all( + OUT capture_level int + , OUT object_group_id int +) RETURNS SETOF record +``` + +Get information about all active capture levels. + +## Utility Functions + +### `post_restore() RETURNS void` + +```sql +post_restore() RETURNS void +``` + +Ensures all object references are correct after a database restore. Run this after restoring from backup to fix any OID mismatches. + +### `object__cleanup(object_id int) RETURNS void` + +```sql +object__cleanup(object_id int) RETURNS void +``` + +Attempts to delete an object from the tracking system. Silently returns if the object is still referenced by other tables (via foreign keys). This function is automatically called when objects are removed from object groups. + +### Object Type Information Functions + +**Get lists of unsupported/untested object types:** + +```sql +unsupported() RETURNS cat_tools.object_type[] +unsupported_srf() RETURNS SETOF cat_tools.object_type + +untested() RETURNS cat_tools.object_type[] +untested_srf() RETURNS SETOF cat_tools.object_type +``` + +**Check if specific object types are supported/tested:** + +```sql +unsupported(object_type text | cat_tools.object_type) RETURNS boolean +untested(object_type text | cat_tools.object_type) RETURNS boolean +``` + +These functions help determine which object types are supported by the framework. Unsupported types cannot be tracked, while untested types may work but haven't been fully validated. + +# Event Triggers + +The extension automatically installs several event triggers that: + +- Capture object creation when DDL capture is active +- Update object identity information when objects are renamed +- Clean up object references when objects are dropped + +These event triggers operate transparently and require no user intervention. However, be aware that they may add slight overhead to DDL operations. + +# Examples + +## Basic Object Tracking + +```sql +-- Track a table +SELECT object_reference.object__getsert('table', 'public.my_table'); + +-- Track a function with its signature +SELECT object_reference.object__getsert('function', 'public.my_func', 'integer, text'); +``` + +## Using Object Groups + +```sql +-- Create a group for related objects +SELECT object_reference.object_group__create('my_feature_objects'); + +-- Add objects to the group +SELECT object_reference.object__getsert('table', 'public.feature_table', NULL, 'my_feature_objects'); +SELECT object_reference.object__getsert('view', 'public.feature_view', NULL, 'my_feature_objects'); +``` + +## DDL Capture + +```sql +-- Create a group first +SELECT object_reference.object_group__create('migration_v2_objects'); + +-- Start capturing new objects to that group +SELECT object_reference.capture__start('migration_v2_objects'); + +-- Run your DDL commands +CREATE TABLE public.new_table (id int, name text); +CREATE INDEX idx_new_table_name ON public.new_table (name); + +-- Stop capturing +SELECT object_reference.capture__stop('migration_v2_objects'); + +-- All objects created between start/stop are now tracked in the 'migration_v2_objects' group +``` From f4bb2638dad07d7c6ad7e88998e7904009748ad8 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Mon, 10 Aug 2026 17:48:56 -0500 Subject: [PATCH 09/10] Restore EXCLUDED CODE marker lost during the linter-merge rebase conflict The rebase onto master (after #16's linter merge) resolved a conflict in the commented-out cat_tools schema-restriction block by keeping a plain /* instead of master's /* EXCLUDED CODE: ... -- without it, the linter demands " * " prefixes on every line of the commented-out DO block, producing 15 false findings. make lint is clean again. Co-Authored-By: Claude --- sql/object_reference.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/object_reference.sql b/sql/object_reference.sql index fe1251f..cdde58f 100644 --- a/sql/object_reference.sql +++ b/sql/object_reference.sql @@ -12,7 +12,7 @@ */ SET LOCAL client_min_messages = WARNING; -/* +/* EXCLUDED CODE: schema-restriction check below not currently enforced DO $$ DECLARE c_schema CONSTANT name := (SELECT extnamespace::regnamespace::text FROM pg_extension WHERE extname = 'cat_tools'); From fad854d2c5a59cc20d8cb543a9f8399cb751bc31 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Mon, 10 Aug 2026 18:14:48 -0500 Subject: [PATCH 10/10] Move client_min_messages suppression from the extension script to the test An extension's own install script shouldn't unilaterally change a session setting on its caller's behalf, even scoped via SET LOCAL -- that's the caller's call to make, not the extension's. Moved the NOTICE suppression out of sql/object_reference.sql entirely; it turns out only zzz_build's raw \i load (not the normal CREATE EXTENSION path every other test uses) ever surfaced the %TYPE resolution NOTICEs in the first place, so the fix belongs in that one test file, wrapped in the transaction setup.sql already opens. test/expected/zzz_build.out regenerated via make results (line-number shift only, from the extension script shrinking). Co-Authored-By: Claude --- sql/object_reference.sql | 10 ---------- test/expected/zzz_build.out | 10 +++++----- test/sql/zzz_build.sql | 13 +++++++++++-- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/sql/object_reference.sql b/sql/object_reference.sql index cdde58f..1e35ef3 100644 --- a/sql/object_reference.sql +++ b/sql/object_reference.sql @@ -2,16 +2,6 @@ \echo You really, REALLY do NOT want to try and load this via psql!!! \echo It will FAIL during pg_dump! \quit -/* - * SET LOCAL, not SET: this script runs inside CREATE EXTENSION's implicit - * transaction, so LOCAL reverts automatically once it commits -- a plain SET - * would leak into the installing session for the rest of its lifetime. - * Suppresses NOTICEs like the %TYPE resolution messages this script's many - * column-type references (e.g. _object_oid.objid%TYPE) would otherwise spam - * CREATE EXTENSION's output with. - */ -SET LOCAL client_min_messages = WARNING; - /* EXCLUDED CODE: schema-restriction check below not currently enforced DO $$ DECLARE diff --git a/test/expected/zzz_build.out b/test/expected/zzz_build.out index c5b269d..fadaba6 100644 --- a/test/expected/zzz_build.out +++ b/test/expected/zzz_build.out @@ -2,16 +2,16 @@ This extension must be loaded via CREATE EXTENSION object_reference; You really, REALLY do NOT want to try and load this via psql!!! -psql:test/temp_load.not_sql:186: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! +psql:test/temp_load.not_sql:176: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! -psql:test/temp_load.not_sql:187: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! +psql:test/temp_load.not_sql:177: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! -psql:test/temp_load.not_sql:435: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! +psql:test/temp_load.not_sql:425: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! @@ -21,9 +21,9 @@ psql:test/temp_load.not_sql:435: WARNING: I promise you will be sorry if you tr -psql:test/temp_load.not_sql:547: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! +psql:test/temp_load.not_sql:537: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! -psql:test/temp_load.not_sql:554: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! +psql:test/temp_load.not_sql:544: WARNING: I promise you will be sorry if you try to use this as anything other than an extension! diff --git a/test/sql/zzz_build.sql b/test/sql/zzz_build.sql index 4fc0628..9ead993 100644 --- a/test/sql/zzz_build.sql +++ b/test/sql/zzz_build.sql @@ -10,9 +10,18 @@ CREATE EXTENSION IF NOT EXISTS cat_tools; CREATE SCHEMA object_reference; --- doesn't work :/ SET client_min_messages = FATAL; -- Need to surpress WARNING or turn down verbosity. Suppressing WARNING seems the better idea... --- Need to do this instead so that results are stable across versions (no line #s from ereport messages) +-- Need this so that results are stable across versions (no line #s from ereport messages) \set VERBOSITY default + +/* + * setup.sql above opened an explicit transaction (see the "TRANSACTION + * INTENTIONALLY LEFT OPEN!" notice below), so SET LOCAL here reverts when + * that transaction ends rather than leaking into the rest of the session. + * Squelches NOTICEs like the %TYPE resolution messages this raw \i load + * would otherwise spam the test output with -- scoped to this test only, + * not the shipped extension script itself. + */ +SET LOCAL client_min_messages = WARNING; \i test/temp_load.not_sql \echo Loaded OK!