Skip to content

Commit aa60049

Browse files
jnasbyupgradeclaude
andcommitted
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 <noreply@anthropic.com>
1 parent 0f68d1a commit aa60049

12 files changed

Lines changed: 333 additions & 230 deletions

Makefile

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ include pgxntool/base.mk
33
testdeps: $(wildcard test/*.sql test/helpers/*.sql) # Be careful not to include directories in this
44
testdeps: test_factory
55

6-
install: cat_tools count_nulls
6+
install: cat_tools
77

88
# pgxntool's check-stale-expected target (added in pgxntool 2.2.0) depends on
99
# installcheck but is listed before install in TEST_DEPS, and Make evaluates a
@@ -22,11 +22,6 @@ cat_tools: $(DESTDIR)$(datadir)/extension/cat_tools.control
2222
$(DESTDIR)$(datadir)/extension/cat_tools.control:
2323
pgxn install --unstable cat_tools
2424

25-
.PHONY: count_nulls
26-
count_nulls: $(DESTDIR)$(datadir)/extension/count_nulls.control
27-
$(DESTDIR)$(datadir)/extension/count_nulls.control:
28-
pgxn install --unstable count_nulls
29-
3025
.PHONY: test_factory
3126
test_factory: $(DESTDIR)$(datadir)/extension/test_factory.control
3227
$(DESTDIR)$(datadir)/extension/test_factory.control:

object_reference.control

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ comment = 'Provides reference IDs for database objects'
22
default_version = 'stable'
33
relocatable = false
44
schema = 'object_reference'
5-
requires = 'cat_tools, count_nulls'
5+
requires = 'cat_tools'

sql/object_reference--stable.sql

Lines changed: 96 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,6 @@
33
\echo You really, REALLY do NOT want to try and load this via psql!!!
44
\echo It will FAIL during pg_dump! \quit
55

6-
-- This BS is because count_nulls is relocatable, so could be in any schema
7-
DO $$
8-
BEGIN
9-
RAISE DEBUG 'initial search_path = %', current_setting('search_path');
10-
PERFORM set_config('search_path', current_setting('search_path') || ', ' || extnamespace::regnamespace::text, true) -- true = local only
11-
FROM pg_extension
12-
WHERE extname = 'count_nulls'
13-
;
14-
RAISE DEBUG 'search_path changed to %', current_setting('search_path');
15-
END
16-
$$;
176
/*
187
DO $$
198
DECLARE
@@ -192,58 +181,19 @@ GRANT REFERENCES ON _object_reference.object TO object_reference__dependency;
192181

193182
CREATE TABLE _object_reference._object_oid(
194183
object_id int PRIMARY KEY REFERENCES _object_reference.object ON DELETE CASCADE ON UPDATE CASCADE
195-
, classid regclass NOT NULL
184+
, classid oid NOT NULL
196185
/* TODO: needs to be a trigger
197186
CONSTRAINT classid_must_match__object__address_classid
198187
CHECK( classid IS NOT DISTINCT FROM cat_tools.object__address_classid(object_type) )
199188
*/
200189
, objid oid NOT NULL
201190
, objsubid int NOT NULL
202191
CONSTRAINT objid_must_match CHECK( -- _object_reference._sanity() depends on this!
203-
objid IS NOT DISTINCT FROM coalesce(
204-
regclass::oid -- Need to cast first item to generic OID
205-
, regconfig
206-
, regdictionary
207-
, regnamespace -- SED: REQUIRES 9.5!
208-
, regoperator
209-
, regprocedure
210-
, regtype
211-
, object_oid
212-
)
192+
objid IS NOT DISTINCT FROM object_oid
213193
)
214194
, CONSTRAINT object__u_classid__objid__objsubid UNIQUE( classid, objid, objsubid )
215-
, regclass regclass
216-
CONSTRAINT regclass_classid CHECK( regclass IS NULL OR classid = cat_tools.object__reg_type_catalog('regclass') )
217-
, regconfig regconfig
218-
CONSTRAINT regconfig_classid CHECK( regconfig IS NULL OR classid = cat_tools.object__reg_type_catalog('regconfig') )
219-
, regdictionary regdictionary
220-
CONSTRAINT regdictionary_classid CHECK( regdictionary IS NULL OR classid = cat_tools.object__reg_type_catalog('regdictionary') )
221-
, regnamespace regnamespace -- SED: REQUIRES 9.5!
222-
CONSTRAINT regnamespace_classid CHECK( regnamespace IS NULL OR classid = cat_tools.object__reg_type_catalog('regnamespace') ) -- SED: REQUIRES 9.5!
223-
, regoperator regoperator
224-
CONSTRAINT regoperator_classid CHECK( regoperator IS NULL OR classid = cat_tools.object__reg_type_catalog('regoperator') )
225-
, regprocedure regprocedure
226-
CONSTRAINT regprocedure_classid CHECK( regprocedure IS NULL OR classid = cat_tools.object__reg_type_catalog('regprocedure') )
227-
-- I don't think we should ever have regrole since we can't create event triggers on it
228-
-- , regrole regrole
229-
, regtype regtype
230-
CONSTRAINT regtype_classid CHECK( regtype IS NULL OR classid = cat_tools.object__reg_type_catalog('regtype') )
231-
, object_oid oid
195+
, object_oid oid NOT NULL
232196
);
233-
CREATE TRIGGER null_count
234-
AFTER INSERT OR UPDATE
235-
ON _object_reference._object_oid
236-
FOR EACH ROW EXECUTE PROCEDURE not_null_count_trigger(
237-
5 -- First 4 fields, + 1
238-
, 'only one object reference field may be set'
239-
)
240-
;
241-
CREATE UNIQUE INDEX _object_oid__u_regclass ON _object_reference._object_oid(regclass) WHERE regclass IS NOT NULL;
242-
CREATE UNIQUE INDEX _object_oid__u_regconfig ON _object_reference._object_oid(regconfig) WHERE regconfig IS NOT NULL;
243-
CREATE UNIQUE INDEX _object_oid__u_regdictionary ON _object_reference._object_oid(regdictionary) WHERE regdictionary IS NOT NULL;
244-
CREATE UNIQUE INDEX _object_oid__u_regoperator ON _object_reference._object_oid(regoperator) WHERE regoperator IS NOT NULL;
245-
CREATE UNIQUE INDEX _object_oid__u_regprocedure ON _object_reference._object_oid(regprocedure) WHERE regprocedure IS NOT NULL;
246-
CREATE UNIQUE INDEX _object_oid__u_regtype ON _object_reference._object_oid(regtype) WHERE regtype IS NOT NULL;
247197

248198
SELECT __object_reference.create_function(
249199
'_object_reference._sanity'
@@ -303,13 +253,6 @@ CREATE VIEW _object_reference._object_v AS
303253
, i.classid
304254
, i.objid
305255
, i.objsubid
306-
, i.regclass
307-
, i.regconfig
308-
, i.regdictionary
309-
, i.regnamespace
310-
, i.regoperator
311-
, i.regprocedure
312-
, i.regtype
313256
, i.object_oid
314257
, s.*
315258
FROM _object_reference.object o
@@ -325,13 +268,6 @@ CREATE VIEW _object_reference._object_v__for_update AS
325268
, i.classid
326269
, i.objid
327270
, i.objsubid
328-
, i.regclass
329-
, i.regconfig
330-
, i.regdictionary
331-
, i.regnamespace
332-
, i.regoperator
333-
, i.regprocedure
334-
, i.regtype
335271
, i.object_oid
336272
, s.*
337273
FROM _object_reference.object o
@@ -363,26 +299,9 @@ BEGIN
363299
WHERE o.object_id = _object_oid__add.object_id
364300
;
365301
END IF;
366-
DECLARE
367-
c_reg_type name := cat_tools.object__reg_type(object_type); -- Verifies regtype is supported, if there is one
368-
c_oid_field CONSTANT name := coalesce(c_reg_type, 'object_oid');
369-
370-
c_oid_insert CONSTANT text := format(
371-
--USING object_id, classid, objid, objsubid
372-
$$INSERT INTO _object_reference._object_oid(object_id, classid, objid, objsubid, %I)
373-
SELECT $1, $2, $3, $4, $3::%I$$
374-
, c_oid_field
375-
, coalesce(c_reg_type, 'oid')
376-
)
377-
;
378302
BEGIN
379-
RAISE DEBUG E'%\n USING %, %, %, %'
380-
, c_oid_insert
381-
, object_id, classid, objid, objsubid
382-
;
383-
EXECUTE c_oid_insert
384-
USING object_id, classid, objid, objsubid
385-
;
303+
INSERT INTO _object_reference._object_oid(object_id, classid, objid, objsubid, object_oid)
304+
VALUES (object_id, classid, objid, objsubid, objid);
386305

387306
SELECT INTO STRICT r_object_v -- Record better exist!
388307
*
@@ -632,6 +551,24 @@ CREATE TABLE _object_reference.object_group__object(
632551
);
633552
SELECT __object_reference.safe_dump('_object_reference.object_group__object');
634553

554+
-- Trigger function for automatic object cleanup
555+
SELECT __object_reference.create_function(
556+
'_object_reference._object_group__object__cleanup_trigger'
557+
, ''
558+
, 'trigger LANGUAGE plpgsql'
559+
, $body$
560+
BEGIN
561+
PERFORM object_reference.object__cleanup(OLD.object_id);
562+
RETURN OLD;
563+
END
564+
$body$
565+
, 'Trigger function to automatically attempt cleanup of objects when removed from groups.'
566+
);
567+
CREATE TRIGGER object_group__object__cleanup
568+
AFTER DELETE ON _object_reference.object_group__object
569+
FOR EACH ROW
570+
EXECUTE FUNCTION _object_reference._object_group__object__cleanup_trigger();
571+
635572
-- __get
636573
SELECT __object_reference.create_function(
637574
'object_reference.object_group__get'
@@ -836,6 +773,69 @@ $body$
836773
, 'object_reference__dependency'
837774
);
838775

776+
/*
777+
* OBJECT INFO FUNCTIONS
778+
*/
779+
SELECT __object_reference.create_function(
780+
'object_reference.object__describe'
781+
, $args$
782+
object_id int
783+
$args$
784+
, 'text LANGUAGE sql'
785+
, $body$
786+
SELECT pg_catalog.pg_describe_object(
787+
o.classid
788+
, o.objid
789+
, o.objsubid
790+
)
791+
FROM _object_reference._object_oid o
792+
WHERE o.object_id = $1
793+
$body$
794+
, 'Return a human-readable description of the object, matching pg_describe_object() format.'
795+
, 'object_reference__usage'
796+
);
797+
798+
SELECT __object_reference.create_function(
799+
'object_reference.object__identity'
800+
, $args$
801+
object_id int
802+
, OUT type text
803+
, OUT schema text
804+
, OUT name text
805+
, OUT identity text
806+
$args$
807+
, 'record LANGUAGE sql'
808+
, $body$
809+
SELECT
810+
i.type::text
811+
, i.schema::text
812+
, i.name::text
813+
, i.identity::text
814+
FROM _object_reference._object_oid o
815+
, LATERAL pg_catalog.pg_identify_object(o.classid, o.objid, o.objsubid) i
816+
WHERE o.object_id = $1
817+
$body$
818+
, 'Return object identification information matching pg_identify_object() format.'
819+
, 'object_reference__usage'
820+
);
821+
SELECT __object_reference.create_function(
822+
'object_reference.object__cleanup'
823+
, $args$
824+
object_id int
825+
$args$
826+
, 'void LANGUAGE plpgsql'
827+
, $body$
828+
BEGIN
829+
DELETE FROM _object_reference.object WHERE object.object_id = object__cleanup.object_id;
830+
EXCEPTION WHEN foreign_key_violation THEN
831+
-- Object is still referenced elsewhere, ignore the error
832+
NULL;
833+
END
834+
$body$
835+
, 'Attempts to delete an object from the tracking system. Silently returns if the object is still referenced by other tables.'
836+
, 'object_reference__usage'
837+
);
838+
839839
/*
840840
* OBJECT GETSERT
841841
*/
@@ -855,6 +855,7 @@ DECLARE
855855

856856
r_object_v _object_reference._object_v;
857857
r_address record;
858+
r_identity record;
858859

859860
did_insert boolean := false;
860861

@@ -883,6 +884,15 @@ BEGIN
883884
;
884885
END IF;
885886

887+
-- Refuse to track objects in temporary schemas
888+
SELECT INTO r_identity * FROM pg_catalog.pg_identify_object(c_classid, objid, objsubid);
889+
IF r_identity.schema IS NOT NULL AND (r_identity.schema LIKE 'pg_temp%' OR r_identity.schema LIKE 'pg_toast_temp%') THEN
890+
RAISE 'cannot track temporary object'
891+
USING DETAIL = format('object %s is in temporary schema %s', r_identity.identity, r_identity.schema)
892+
, ERRCODE = 'feature_not_supported'
893+
;
894+
END IF;
895+
886896
-- Ensure the object record exists
887897
SELECT INTO r_object_v
888898
*

0 commit comments

Comments
 (0)