@@ -105,32 +105,132 @@ END
105105$body$;
106106
107107/*
108- * 0.1.0 already installed this extension's own event triggers, and they
109- * stay active for the rest of THIS session while the structural changes
110- * below run. zzz__object_reference_drop in particular queries
111- * _object_reference._object_v inside its own body, so it would fire -- and
112- * error, since the view is momentarily gone -- the instant this script drops
113- * that view a few statements down. All three are default-enabled (origin),
114- * so setting session_replication_role = replica suppresses them for the
115- * structural section below.
108+ * New: _object_reference.exec(), the permanent counterpart to
109+ * __object_reference.exec() above, used by object__dependency__add() /
110+ * object_group__dependency__add() (unchanged since 0.1.0, but 0.1.0 never
111+ * created this permanent helper -- an existing gap this update closes) and
112+ * by internal_update__begin()/__end() below.
113+ */
114+ SELECT __object_reference .create_function (
115+ ' _object_reference.exec'
116+ , ' sql text'
117+ , ' void LANGUAGE plpgsql'
118+ , $body$
119+ BEGIN
120+ RAISE DEBUG ' sql = %' , sql;
121+ EXECUTE sql;
122+ END
123+ $body$
124+ , ' Execute arbitrary SQL with logging.'
125+ );
126+
127+ /*
128+ * New: refuse to track objects that are themselves members of the
129+ * object_reference extension (see the guard added to
130+ * _object_v__for_update() below).
131+ */
132+ SELECT __object_reference .create_function (
133+ ' _object_reference._is_own_object'
134+ , $args$
135+ classid oid
136+ , objid oid
137+ $args$
138+ , ' boolean LANGUAGE sql STABLE'
139+ , $body$
140+ SELECT EXISTS(
141+ SELECT 1
142+ FROM pg_catalog .pg_depend d
143+ WHERE d .classid = _is_own_object .classid
144+ AND d .objid = _is_own_object .objid
145+ AND d .deptype = ' e'
146+ AND d .refclassid = ' pg_catalog.pg_extension' ::regclass
147+ AND d .refobjid = (SELECT oid FROM pg_catalog .pg_extension WHERE extname = ' object_reference' )
148+ )
149+ $body$
150+ , ' Is the object a member of the object_reference extension itself? (pg_depend deptype = e membership, not just co-installation.)'
151+ );
152+
153+ /*
154+ * New: internal update-time disable/enable mechanism, replacing the
155+ * session_replication_role trick 0.1.0 had no equivalent of. 0.1.0 already
156+ * installed this extension's own event triggers, and they stay active for
157+ * the rest of THIS session while the structural changes below run.
158+ * zzz__object_reference_drop in particular queries _object_reference._object_v
159+ * inside its own body, so it would fire -- and error, since the view is
160+ * momentarily gone -- the instant this script drops that view a few
161+ * statements down. The other two event triggers self-recognize and skip our
162+ * own script's DDL instead (see _etg_fix_identity/_etg_capture below), so
163+ * only zzz__object_reference_drop needs to be disabled here.
116164 *
117- * This script is not necessarily the only thing running in its transaction
118- * -- ALTER EXTENSION UPDATE can be issued as one statement among several in
119- * a caller-managed transaction -- so session_replication_role cannot simply
120- * be left disturbed for "the rest of the transaction" to sort out, and
121- * whatever it's restored to afterward must be the caller's actual prior
122- * value, not an assumed 'origin' default (the caller may already have it set
123- * to something else for their own reasons). Stashed in a placeholder GUC
124- * (there's no other way to carry a value between separate top-level
125- * statements in a plain multi-statement SQL script -- this isn't a single
126- * PL/pgSQL block) and restored explicitly right after the cleanup at the end
127- * of this script, once every object the event triggers reference is back in
128- * its final, current-source shape. A fresh install never hits this: it
129- * creates these event triggers only at the very end, once nothing they
130- * reference is still being modified.
165+ * These are created now, ahead of the structural section, specifically so
166+ * this script itself can call internal_update__begin() below -- a fresh
167+ * install only ever needs these for FUTURE update scripts.
131168 */
132- SELECT set_config(' object_reference.saved_session_replication_role' , current_setting(' session_replication_role' ), true);
133- SET LOCAL session_replication_role = replica;
169+ SELECT __object_reference .create_function (
170+ ' _object_reference.internal_update__begin'
171+ , $args$
172+ event_trigger_names name[] DEFAULT ' {zzz__object_reference_drop}'
173+ $args$
174+ , ' void LANGUAGE plpgsql'
175+ , $body$
176+ DECLARE
177+ v_name name;
178+ BEGIN
179+ BEGIN
180+ CREATE TEMP TABLE __object_reference__internal_update(
181+ evtname name PRIMARY KEY
182+ , evtenabled " char" NOT NULL
183+ );
184+ EXCEPTION WHEN duplicate_table THEN
185+ RAISE ' internal_update__begin() called while already in an internal update'
186+ USING HINT = ' A previous internal_update__end() call may have been skipped.'
187+ ;
188+ END;
189+
190+ FOREACH v_name IN ARRAY event_trigger_names LOOP
191+ INSERT INTO pg_temp .__object_reference__internal_update (evtname, evtenabled)
192+ SELECT evtname, evtenabled FROM pg_catalog .pg_event_trigger WHERE evtname = v_name;
193+
194+ IF NOT FOUND THEN
195+ RAISE ' event trigger "%" does not exist' , v_name;
196+ END IF;
197+
198+ PERFORM _object_reference .exec (format(' ALTER EVENT TRIGGER %I DISABLE' , v_name));
199+ END LOOP;
200+ END
201+ $body$
202+ , ' Disable the given (or default) event triggers for the duration of this extension' ' s own internal update; pair with internal_update__end().'
203+ );
204+ SELECT __object_reference .create_function (
205+ ' _object_reference.internal_update__end'
206+ , ' '
207+ , ' void LANGUAGE plpgsql'
208+ , $body$
209+ DECLARE
210+ r record;
211+ BEGIN
212+ FOR r IN SELECT evtname, evtenabled FROM pg_temp .__object_reference__internal_update LOOP
213+ PERFORM _object_reference .exec (format(
214+ ' ALTER EVENT TRIGGER %I %s'
215+ , r .evtname
216+ , CASE r .evtenabled
217+ WHEN ' O' THEN ' ENABLE'
218+ WHEN ' R' THEN ' ENABLE REPLICA'
219+ WHEN ' A' THEN ' ENABLE ALWAYS'
220+ WHEN ' D' THEN ' DISABLE'
221+ END
222+ ));
223+ END LOOP;
224+
225+ DROP TABLE pg_temp .__object_reference__internal_update ;
226+ EXCEPTION WHEN undefined_table THEN
227+ RAISE ' internal_update__end() called without a matching internal_update__begin()' ;
228+ END
229+ $body$
230+ , ' Restore event triggers disabled by internal_update__begin() to their prior enabled state.'
231+ );
232+
233+ SELECT _object_reference .internal_update__begin ();
134234
135235/*
136236 * _object_reference.object: no column changes, just a missing
@@ -293,6 +393,14 @@ BEGIN
293393 ;
294394 END IF;
295395
396+ -- Refuse to track objects that are themselves members of this extension
397+ IF _object_reference ._is_own_object (c_classid, objid) THEN
398+ RAISE ' cannot track an object that is a member of the object_reference extension itself'
399+ USING DETAIL = format(' object %s belongs to the object_reference extension' , r_identity .identity )
400+ , ERRCODE = ' feature_not_supported'
401+ ;
402+ END IF;
403+
296404 -- Ensure the object record exists
297405 SELECT INTO r_object_v
298406 *
@@ -416,6 +524,120 @@ $body$
416524 , ' Check the sanity of object and _object_oid'
417525);
418526
527+ /*
528+ * _etg_fix_identity/_etg_capture: gain a self-recognition guard so they skip
529+ * DDL issued by any extension's own install/update script (ours included)
530+ * instead of reacting to it -- see internal_update__begin()/__end() above
531+ * for why zzz__object_reference_drop needs a different mechanism. Same
532+ * signatures as 0.1.0, so a plain CREATE OR REPLACE (via create_function) is
533+ * enough -- no DROP needed.
534+ */
535+ SELECT __object_reference .create_function (
536+ ' _object_reference._etg_fix_identity'
537+ , ' '
538+ , ' event_trigger SECURITY DEFINER LANGUAGE plpgsql'
539+ , $body$
540+ DECLARE
541+ r_ddl record;
542+ r record;
543+ BEGIN
544+ /*
545+ * Self-recognition: skip DDL issued by any extension's own install/update
546+ * script (ours included); pg_event_trigger_ddl_commands() marks this via
547+ * in_extension, unlike pg_event_trigger_dropped_objects() (see _etg_drop).
548+ */
549+ IF EXISTS(SELECT 1 FROM pg_catalog .pg_event_trigger_ddl_commands () WHERE in_extension) THEN
550+ RETURN;
551+ END IF;
552+
553+ /*
554+ * It's tempting to use pg_event_trigger_ddl_commands() to find exactly what
555+ * items have changed and worry about only those. That won't work because an
556+ * object_names array can depend on multiple names (ie: a column depends on
557+ * the name of it's table, as well as the name of the schema the table is in.
558+ * You might think we could simply recurse through pg_depend to handle this,
559+ * but not every name dependency gets enumerated that way. For example,
560+ * columns are not marked as dependent on their table.
561+ *
562+ * Rather than trying to be cute about this, we just do a brute-force check
563+ * for any names that have changed.
564+ */
565+
566+ /*
567+ * Presumably there's no way for an objects type/classid to change, but be
568+ * safe and attempt the update to object_type. If it actually does change the
569+ * constraint on the table should catch it.
570+ */
571+ FOR r IN
572+ UPDATE _object_reference .object
573+ SET object_type = (pg_catalog .pg_identify_object_as_address (classid, objid, objsubid)).type::cat_tools .object_type
574+ , object_names = (pg_catalog .pg_identify_object_as_address (classid, objid, objsubid)).object_names
575+ , object_args = (pg_catalog .pg_identify_object_as_address (classid, objid, objsubid)).object_args
576+ FROM _object_reference ._object_oid oo
577+ WHERE
578+ oo .object_id = object .object_id
579+ AND (object_type::text , object_names, object_args) IS DISTINCT FROM
580+ (pg_catalog .pg_identify_object_as_address (classid, objid, objsubid))
581+ RETURNING *
582+ LOOP
583+ RAISE DEBUG ' modified_objects(): %' , r;
584+ END LOOP;
585+ END
586+ $body$
587+ , ' Event trigger function to update any records with object names or args that have changed.'
588+ );
589+ SELECT __object_reference .create_function (
590+ ' _object_reference._etg_capture'
591+ , ' '
592+ , ' event_trigger SECURITY DEFINER LANGUAGE plpgsql'
593+ , $body$
594+ DECLARE
595+ c_group_id CONSTANT int := object_group_id FROM object_reference .capture__get_current ();
596+ r record;
597+ BEGIN
598+
599+ IF c_group_id IS NOT NULL THEN -- Would be NULL if table is empty
600+ RAISE DEBUG E' \n\n *** START ***' ;
601+ BEGIN
602+ FOR r IN
603+ SELECT classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension
604+ -- Have to manually exclude command field :/
605+ FROM pg_catalog .pg_event_trigger_ddl_commands ()
606+ LOOP
607+ RAISE DEBUG ' ddl: %' , row_to_json(r);
608+ END LOOP;
609+ END;
610+
611+ FOR r IN SELECT
612+ _object_reference ._object_v__for_update (
613+ object_type::cat_tools .object_type
614+ , objid, objsubid
615+ , c_group_id
616+ , classid
617+ )
618+ , classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension
619+ FROM pg_catalog .pg_event_trigger_ddl_commands ()
620+ WHERE command_tag ~ ' ^CREATE' -- '^(ALTER|CREATE)'
621+ AND NOT object_reference .unsupported (object_type::cat_tools .object_type )
622+ AND (schema_name IS NULL
623+ OR schema_name NOT LIKE ' pg_temp%' -- pg_my_temp_schema() doesn't seem worth it...
624+ )
625+ /*
626+ * Self-recognition: skip DDL issued by any extension's own
627+ * install/update script (ours included) rather than trying to
628+ * capture it.
629+ */
630+ AND NOT in_extension
631+ LOOP
632+ RAISE DEBUG ' registered %' , row_to_json(r);
633+ END LOOP;
634+ RAISE DEBUG E' *** END ***\n\n ' ;
635+ END IF;
636+ END
637+ $body$
638+ , ' Event trigger function to capture newly created objects in an object group.'
639+ );
640+
419641/*
420642 * object_reference.unsupported(): additionally exclude "partitioned
421643 * table"/"partitioned index" (pg_get_object_address() only recognizes the
@@ -679,10 +901,10 @@ DROP FUNCTION __object_reference.exec(
679901DROP SCHEMA __object_reference;
680902
681903/*
682- * Restore session_replication_role to the caller's actual prior value
683- * (saved near the top of this script), now that the structural section and
684- * its cleanup are both done.
904+ * Re-enable zzz__object_reference_drop ( to its actual prior state, saved by
905+ * internal_update__begin() near the top of this script), now that the
906+ * structural section and its cleanup are both done.
685907 */
686- SELECT set_config( ' session_replication_role ' , current_setting( ' object_reference.saved_session_replication_role ' ), true );
908+ SELECT _object_reference . internal_update__end ( );
687909
688910-- vi: expandtab sw=2 ts=2
0 commit comments