From 8cc4f8afffcaab5b00e098fad919688113f9f3da Mon Sep 17 00:00:00 2001 From: Phil Alger Date: Thu, 25 Sep 2025 20:38:19 -0500 Subject: [PATCH] Provide a function to emit DDL for CREATE EVENT TRIGGER This patch implements the pg_get_event_trigger_ddl() function, which emits the DDL for CREATE EVENT TRIGGER. It includes functionality comments in the code, as well as tests and documentation. The returned function will look like the following: postgres=# SELECT pg_get_event_trigger_ddl('foo_event_trigger'); pg_get_event_trigger_ddl --------------------------------------------------------------------------------------------------------------- CREATE EVENT TRIGGER foo_event_trigger ON ddl_command_start EXECUTE FUNCTION public.test_event_trigger(); (1 row) or with a TAG filter: postgres=# SELECT pg_get_event_trigger_ddl('foo_event_trigger2'); pg_get_event_trigger_ddl --------------------------------------------------------------------------------------------------------------------------------------------------------------- CREATE EVENT TRIGGER foo_event_trigger2 ON ddl_command_start WHEN TAG IN ('CREATE TABLE','CREATE FUNCTION') EXECUTE FUNCTION public.test_event_trigger(); PG-158 Author: Ian Barwick Co-authored-by: Phil Alger --- doc/src/sgml/func/func-info.sgml | 44 ++++++++ src/backend/utils/adt/ruleutils.c | 119 ++++++++++++++++++++ src/include/catalog/pg_proc.dat | 3 + src/test/regress/expected/event_trigger.out | 26 +++++ src/test/regress/sql/event_trigger.sql | 10 ++ 5 files changed, 202 insertions(+) diff --git a/doc/src/sgml/func/func-info.sgml b/doc/src/sgml/func/func-info.sgml index c393832d94c64..a853bcecdd32c 100644 --- a/doc/src/sgml/func/func-info.sgml +++ b/doc/src/sgml/func/func-info.sgml @@ -3797,4 +3797,48 @@ acl | {postgres=arwdDxtm/postgres,foo=r/postgres} + + DDL Retail Functions + + + lists functions that + extract information about .... + + + + DDL Retail Functions + + + + + Function + + + Description + + + + + + + + + pg_get_event_trigger_ddl + + pg_get_event_trigger_ddl + ( event_trigger_name name ) + text + + + Reconstructs the underlying DDL statement for a specified event trigger. + It returns the complete CREATE EVENT TRIGGER statement. If + the event trigger does not exist, an error is raised. + + + + +
+ +
+ diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 0408a95941dcb..4b49e8ea10b9e 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -29,6 +29,7 @@ #include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" #include "catalog/pg_depend.h" +#include "catalog/pg_event_trigger.h" #include "catalog/pg_language.h" #include "catalog/pg_opclass.h" #include "catalog/pg_operator.h" @@ -1162,6 +1163,124 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty) return buf.data; } +/* ---------- +* pg_get_event_trigger_ddl - Get the DDL statement for an event trigger +* ---------- +*/ +Datum +pg_get_event_trigger_ddl(PG_FUNCTION_ARGS) +{ + Name evtName; + HeapTuple evtTup; + HeapTuple procTup; + Form_pg_event_trigger evtForm; + char *evtevent; + Oid evtfoid; + Datum evttagsDatum; + bool evttagsIsNull; + Form_pg_proc proc; + char *proname; + char prokind; + StringInfoData buf; + + evtName = PG_GETARG_NAME(0); + + /* + * Check provided event trigger name exists + */ + evtTup = SearchSysCache1(EVENTTRIGGERNAME, NameGetDatum(evtName)); + + if (!HeapTupleIsValid(evtTup)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("event trigger \"%s\" does not exist", NameStr(*evtName)))); + + evtForm = (Form_pg_event_trigger) GETSTRUCT(evtTup); + evtevent = NameStr(evtForm->evtevent); + evtfoid = evtForm->evtfoid; + + /* + * Look up event trigger function. + */ + procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(evtfoid)); + + if (!HeapTupleIsValid(procTup)) + elog(ERROR, + "cache lookup failed for function %u", evtfoid); + + proc = (Form_pg_proc) GETSTRUCT(procTup); + proname = NameStr(proc->proname); + prokind = proc->prokind; + + Assert(prokind == PROKIND_FUNCTION || prokind == PROKIND_PROCEDURE); + + /* + * Assemble output. + */ + initStringInfo(&buf); + + appendStringInfo(&buf, "CREATE EVENT TRIGGER %s ON %s", + quote_identifier(NameStr(*evtName)), + quote_identifier(evtevent)); + + /* + * Generate WHEN clause, if any filter events were specified. + * + * Note that as of PostgreSQL 19, the only filter_variable supported + * is "TAG", which is not stored in pg_event_trigger, so is hard-coded + * in the generated output. This also means that by definition, the AND + * clause supported by the CREATE EVENT TRIGGER grammar cannot be actually + * be used in a valid statement, and thus will never be part of the + * generated output. + */ + evttagsDatum = SysCacheGetAttr(EVENTTRIGGEROID, evtTup, + Anum_pg_event_trigger_evttags, + &evttagsIsNull); + + if (!evttagsIsNull) + { + ArrayType *arr = DatumGetArrayTypeP(evttagsDatum); + Datum *elems; + int nelems; + int i; + + deconstruct_array_builtin(arr, TEXTOID, &elems, NULL, &nelems); + + appendStringInfoString(&buf, " WHEN TAG IN ("); + + for (i = 0; i < nelems; ++i) + { + char *str = TextDatumGetCString(elems[i]); + + if (i) + appendStringInfoChar(&buf, ','); + + appendStringInfo(&buf, "'%s'", str); + } + + appendStringInfoString(&buf, ")"); + } + + /* + * Finally, add EXECUTE clause. + */ + appendStringInfo(&buf, " EXECUTE %s %s.%s()", + prokind == PROKIND_FUNCTION ? "FUNCTION" : "PROCEDURE", + quote_identifier(get_namespace_name(proc->pronamespace)), + quote_identifier(proname)); + + /* + * Terminate query + */ + appendStringInfoChar(&buf, ';'); + + /* Clean up */ + ReleaseSysCache(evtTup); + ReleaseSysCache(procTup); + PG_RETURN_TEXT_P(string_to_text(buf.data)); +} + + /* ---------- * pg_get_indexdef - Get the definition of an index * diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 01eba3b5a1909..c52402c6bab0b 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8547,6 +8547,9 @@ { oid => '2730', descr => 'trigger description with pretty-print option', proname => 'pg_get_triggerdef', provolatile => 's', prorettype => 'text', proargtypes => 'oid bool', prosrc => 'pg_get_triggerdef_ext' }, +{ oid => '9067', descr => 'get CREATE statement for event trigger', + proname => 'pg_get_event_trigger_ddl', proisstrict => 't', + prorettype => 'text', proargtypes => 'name', prosrc => 'pg_get_event_trigger_ddl' }, # asynchronous notifications { oid => '3035', diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out index 16e4530708cc9..d142af9f9ac43 100644 --- a/src/test/regress/expected/event_trigger.out +++ b/src/test/regress/expected/event_trigger.out @@ -84,6 +84,32 @@ create event trigger regress_event_trigger2 on ddl_command_start execute procedure test_event_trigger(); -- OK comment on event trigger regress_event_trigger is 'test comment'; +-- these are all OK - checks the DDL output without a tag filter and with one +SELECT pg_get_event_trigger_ddl('regress_event_trigger'); + pg_get_event_trigger_ddl +--------------------------------------------------------------------------------------------------------------- + CREATE EVENT TRIGGER regress_event_trigger ON ddl_command_start EXECUTE FUNCTION public.test_event_trigger(); +(1 row) + +SELECT pg_get_event_trigger_ddl('regress_event_trigger2'); + pg_get_event_trigger_ddl +--------------------------------------------------------------------------------------------------------------------------------------------------------------- + CREATE EVENT TRIGGER regress_event_trigger2 ON ddl_command_start WHEN TAG IN ('CREATE TABLE','CREATE FUNCTION') EXECUTE FUNCTION public.test_event_trigger(); +(1 row) + +-- will return NULL +SELECT pg_get_event_trigger_ddl(NULL); + pg_get_event_trigger_ddl +-------------------------- + +(1 row) + +-- should fail, no argument +SELECT pg_get_event_trigger_ddl(); +ERROR: function pg_get_event_trigger_ddl() does not exist +LINE 1: SELECT pg_get_event_trigger_ddl(); + ^ +DETAIL: No function of that name accepts the given number of arguments. -- drop as non-superuser should fail create role regress_evt_user; set role regress_evt_user; diff --git a/src/test/regress/sql/event_trigger.sql b/src/test/regress/sql/event_trigger.sql index c613c0cfd439b..8208a563428dc 100644 --- a/src/test/regress/sql/event_trigger.sql +++ b/src/test/regress/sql/event_trigger.sql @@ -85,6 +85,16 @@ create event trigger regress_event_trigger2 on ddl_command_start -- OK comment on event trigger regress_event_trigger is 'test comment'; +-- these are all OK - checks the DDL output without a tag filter and with one +SELECT pg_get_event_trigger_ddl('regress_event_trigger'); +SELECT pg_get_event_trigger_ddl('regress_event_trigger2'); + +-- will return NULL +SELECT pg_get_event_trigger_ddl(NULL); + +-- should fail, no argument +SELECT pg_get_event_trigger_ddl(); + -- drop as non-superuser should fail create role regress_evt_user; set role regress_evt_user;