From 613bdb92636237e1f0fc7bd30c109d7ba3c83cf4 Mon Sep 17 00:00:00 2001
From: Ian Barwick <ian.barwick@enterprisedb.com>
Date: Fri, 17 Jul 2026 11:18:40 +0900
Subject: [PATCH v1] Add pg_get_event_trigger_ddl() function

Add a new SQL-callable function that returns the DDL statement needed
to recreate an event trigger. It takes a name argument and an optional
boolean argument for formatted output.
---
 doc/src/sgml/func/func-info.sgml            |  18 ++++
 src/backend/utils/adt/ddlutils.c            | 105 ++++++++++++++++++++
 src/include/catalog/pg_proc.dat             |   5 +
 src/test/regress/expected/event_trigger.out |  35 +++++++
 src/test/regress/sql/event_trigger.sql      |  13 +++
 5 files changed, 176 insertions(+)

diff --git a/doc/src/sgml/func/func-info.sgml b/doc/src/sgml/func/func-info.sgml
index 69ef3857cfa..285a4b8c9c1 100644
--- a/doc/src/sgml/func/func-info.sgml
+++ b/doc/src/sgml/func/func-info.sgml
@@ -3902,6 +3902,24 @@ acl      | {postgres=arwdDxtm/postgres,foo=r/postgres}
         <literal>TABLESPACE</literal> clause is omitted.
        </para></entry>
       </row>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_get_event_trigger_ddl</primary>
+        </indexterm>
+        <function>pg_get_event_trigger_ddl</function>
+        ( <parameter>event_trigger</parameter> <type>name</type>
+        <optional>, <parameter>pretty</parameter> <type>boolean</type>
+        <literal>DEFAULT</literal> false</optional> )
+        <returnvalue>text</returnvalue>
+       </para>
+       <para>
+        Reconstructs the <link linkend="sql-createeventtrigger"><command>
+        CREATE EVENT TRIGGER</command></link> statement for the specified
+        event trigger. When <parameter>pretty</parameter> is true, the output
+        is pretty-printed.
+       </para></entry>
+      </row>
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/utils/adt/ddlutils.c b/src/backend/utils/adt/ddlutils.c
index a70f1c28655..db273356e40 100644
--- a/src/backend/utils/adt/ddlutils.c
+++ b/src/backend/utils/adt/ddlutils.c
@@ -26,6 +26,8 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
+#include "catalog/pg_event_trigger.h"
+#include "catalog/pg_proc.h"
 #include "catalog/pg_tablespace.h"
 #include "commands/tablespace.h"
 #include "common/relpath.h"
@@ -975,3 +977,106 @@ pg_get_database_ddl(PG_FUNCTION_ARGS)
 		SRF_RETURN_DONE(funcctx);
 	}
 }
+
+/*
+ * pg_get_event_trigger_ddl
+ *		Get the DDL statement for an event trigger
+ */
+Datum
+pg_get_event_trigger_ddl(PG_FUNCTION_ARGS)
+{
+	Name		evtName;
+	bool		pretty;
+	HeapTuple	evtTup;
+	HeapTuple	procTup;
+	Form_pg_event_trigger evtForm;
+	char		*evtevent;
+	Datum		evttagsDatum;
+	bool		evttagsIsNull;
+	Form_pg_proc proc;
+	char		*proname;
+	StringInfoData buf;
+
+	evtName = PG_GETARG_NAME(0);
+	pretty = PG_GETARG_BOOL(1);
+
+	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);
+
+	procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(evtForm->evtfoid));
+
+	if (!HeapTupleIsValid(procTup))
+		elog(ERROR,
+			 "cache lookup failed for function %u", evtForm->evtfoid);
+
+	proc = (Form_pg_proc) GETSTRUCT(procTup);
+	proname = NameStr(proc->proname);
+
+	initStringInfo(&buf);
+
+	appendStringInfo(&buf, "CREATE EVENT TRIGGER %s",
+					 quote_identifier(NameStr(*evtName)));
+	append_ddl_option(&buf, pretty, 4, "ON %s",
+					  quote_identifier(evtevent));
+
+	/*
+	 * Generate WHEN clause, if any filter events were specified.
+	 *
+	 * Note that as of PostgreSQL 20, 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;
+		StringInfoData filters;
+
+		initStringInfo(&filters);
+
+		deconstruct_array_builtin(arr, TEXTOID, &elems, NULL, &nelems);
+
+		for (i = 0; i < nelems; ++i)
+		{
+			char	   *str = TextDatumGetCString(elems[i]);
+
+			if (i)
+				appendStringInfoString(&filters, ", ");
+
+			appendStringInfo(&filters, "'%s'", str);
+		}
+
+		append_ddl_option(&buf, pretty, 4, "WHEN TAG IN (%s)",
+						  filters.data);
+	}
+
+	/*
+	 * Finally, add the EXECUTE clause. Note that although the command syntax
+	 * accepts the keyword PROCEDURE for historical reasons, only an actual
+	 * FUNCTION can be referenced, so we don't check the prokind.
+	 */
+	append_ddl_option(&buf, pretty, 4, "EXECUTE FUNCTION %s.%s();",
+					  quote_identifier(get_namespace_name(proc->pronamespace)),
+					  quote_identifier(proname));
+
+	ReleaseSysCache(evtTup);
+	ReleaseSysCache(procTup);
+
+	PG_RETURN_TEXT_P(cstring_to_text(buf.data));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 1c55a4dea34..d8c2ea59f87 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8631,6 +8631,11 @@
   proargtypes => 'regdatabase bool bool bool',
   proargnames => '{database,pretty,owner,tablespace}',
   proargdefaults => '{false,true,true}', prosrc => 'pg_get_database_ddl' },
+{ oid => '9067', descr => 'get CREATE statement for event trigger',
+  proname => 'pg_get_event_trigger_ddl', proisstrict => 't',
+  provolatile => 's', prorettype => 'text',
+  proargtypes => 'name bool', proargnames => '{event_trigger,pretty}',
+  proargdefaults => '{false}', prosrc => 'pg_get_event_trigger_ddl' },
 { oid => '2509',
   descr => 'deparse an encoded expression with pretty-print option',
   proname => 'pg_get_expr', provolatile => 's', prorettype => 'text',
diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
index 86ae50ce531..4be8628b1ba 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -84,6 +84,41 @@ 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)
+
+-- check formatted output
+SELECT pg_get_event_trigger_ddl('regress_event_trigger', true);
+             pg_get_event_trigger_ddl              
+---------------------------------------------------
+ CREATE EVENT TRIGGER regress_event_trigger       +
+     ON ddl_command_start                         +
+     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 d0e6ba295fe..77fc7f662f2 100644
--- a/src/test/regress/sql/event_trigger.sql
+++ b/src/test/regress/sql/event_trigger.sql
@@ -85,6 +85,19 @@ 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');
+
+-- check formatted output
+SELECT pg_get_event_trigger_ddl('regress_event_trigger', true);
+
+-- 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;
-- 
2.52.0

