From db98eb16d79ab1895369498b484ae288c4a72c2d Mon Sep 17 00:00:00 2001
From: Fujii Masao <fujii@postgresql.org>
Date: Fri, 24 Jul 2026 00:30:07 +0900
Subject: [PATCH v2 1/3] Collect ALTER PUBLICATION mapping drops for event
 triggers

ALTER PUBLICATION is expected to emit its own ddl_command_end entry
for event triggers. Previously, however, commands that only removed
publication membership entries did not do so.

As a result, sql_drop reported the removed membership entries, but
pg_event_trigger_ddl_commands() did not report the corresponding
ALTER PUBLICATION command. This affected
ALTER PUBLICATION ... DROP TABLE(S), DROP TABLES IN SCHEMA,
and SET forms whose net effect was only to remove publication memberships.

Fix this by collecting the publication itself as the
ALTER PUBLICATION command whenever the command removes table or
schema membership entries, while preserving the existing per-membership
entries for additions.

As a side-effect of this fix, SET commands that both add and remove memberships
may now produce both a publication-level ALTER PUBLICATION entry and
the existing per-membership addition entries. This is intentional, as
they represent different parts of the command.

Backpatch to all supported versions.
---
 src/backend/commands/publicationcmds.c      | 56 +++++++++++++++------
 src/test/regress/expected/event_trigger.out | 24 +++++++++
 src/test/regress/sql/event_trigger.sql      | 20 ++++++++
 3 files changed, 85 insertions(+), 15 deletions(-)

diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 440adb356ad..6fa354b170c 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -66,10 +66,10 @@ static void CloseTableList(List *rels);
 static void LockSchemaList(List *schemalist);
 static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
 								 AlterPublicationStmt *stmt);
-static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static bool PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
 static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
 								  AlterPublicationStmt *stmt);
-static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
+static bool PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
 static char defGetGeneratedColsOption(DefElem *def);
 
 
@@ -1238,7 +1238,7 @@ InvalidatePublicationRels(List *relids)
 /*
  * Add or remove table to/from publication.
  */
-static void
+static bool
 AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
 					   List *tables, const char *queryString,
 					   bool publish_schema)
@@ -1246,6 +1246,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
 	List	   *rels = NIL;
 	Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
 	Oid			pubid = pubform->oid;
+	bool		dropped = false;
 
 	/*
 	 * Nothing to do if no objects, except in SET: for that it is quite
@@ -1253,7 +1254,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
 	 * to remove all the existing tables.
 	 */
 	if (!tables && stmt->action != AP_SetObjects)
-		return;
+		return false;
 
 	rels = OpenTableList(tables);
 
@@ -1269,7 +1270,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
 		PublicationAddTables(pubid, rels, false, stmt);
 	}
 	else if (stmt->action == AP_DropObjects)
-		PublicationDropTables(pubid, rels, false);
+		dropped = PublicationDropTables(pubid, rels, false);
 	else						/* AP_SetObjects */
 	{
 		List	   *oldrelids = NIL;
@@ -1404,7 +1405,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
 		}
 
 		/* And drop them. */
-		PublicationDropTables(pubid, delrels, true);
+		dropped = PublicationDropTables(pubid, delrels, true);
 
 		/*
 		 * Don't bother calculating the difference for adding, we'll catch and
@@ -1416,6 +1417,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
 	}
 
 	CloseTableList(rels);
+
+	return dropped;
 }
 
 /*
@@ -1423,11 +1426,12 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
  *
  * Add or remove schemas to/from publication.
  */
-static void
+static bool
 AlterPublicationSchemas(AlterPublicationStmt *stmt,
 						HeapTuple tup, List *schemaidlist)
 {
 	Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+	bool		dropped = false;
 
 	/*
 	 * Nothing to do if no objects, except in SET: for that it is quite
@@ -1435,7 +1439,7 @@ AlterPublicationSchemas(AlterPublicationStmt *stmt,
 	 * to remove all the existing schemas.
 	 */
 	if (!schemaidlist && stmt->action != AP_SetObjects)
-		return;
+		return false;
 
 	/*
 	 * Schema lock is held until the publication is altered to prevent
@@ -1478,7 +1482,7 @@ AlterPublicationSchemas(AlterPublicationStmt *stmt,
 		PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
 	}
 	else if (stmt->action == AP_DropObjects)
-		PublicationDropSchemas(pubform->oid, schemaidlist, false);
+		dropped = PublicationDropSchemas(pubform->oid, schemaidlist, false);
 	else						/* AP_SetObjects */
 	{
 		List	   *oldschemaids = GetPublicationSchemas(pubform->oid);
@@ -1494,7 +1498,7 @@ AlterPublicationSchemas(AlterPublicationStmt *stmt,
 		LockSchemaList(delschemas);
 
 		/* And drop them */
-		PublicationDropSchemas(pubform->oid, delschemas, true);
+		dropped = PublicationDropSchemas(pubform->oid, delschemas, true);
 
 		/*
 		 * Don't bother calculating the difference for adding, we'll catch and
@@ -1502,6 +1506,8 @@ AlterPublicationSchemas(AlterPublicationStmt *stmt,
 		 */
 		PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
 	}
+
+	return dropped;
 }
 
 /*
@@ -1686,6 +1692,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
 		List	   *exceptrelations = NIL;
 		List	   *schemaidlist = NIL;
 		Oid			pubid = pubform->oid;
+		bool		tables_dropped;
+		bool		schemas_dropped;
 
 		ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
 								   &exceptrelations, &schemaidlist);
@@ -1712,10 +1720,20 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
 						   stmt->pubname));
 
 		relations = list_concat(relations, exceptrelations);
-		AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
-							   schemaidlist != NIL);
-		AlterPublicationSchemas(stmt, tup, schemaidlist);
+		tables_dropped =
+			AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
+								   schemaidlist != NIL);
+		schemas_dropped = AlterPublicationSchemas(stmt, tup, schemaidlist);
 		AlterPublicationAllFlags(stmt, rel, tup);
+
+		if (tables_dropped || schemas_dropped)
+		{
+			ObjectAddress obj;
+
+			ObjectAddressSet(obj, PublicationRelationId, pubid);
+			EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+											 (Node *) stmt);
+		}
 	}
 
 	/* Cleanup. */
@@ -2066,12 +2084,13 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
 /*
  * Remove listed tables from the publication.
  */
-static void
+static bool
 PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
 {
 	ObjectAddress obj;
 	ListCell   *lc;
 	Oid			prid;
+	bool		dropped = false;
 
 	foreach(lc, rels)
 	{
@@ -2105,7 +2124,10 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
 
 		ObjectAddressSet(obj, PublicationRelRelationId, prid);
 		performDeletion(&obj, DROP_CASCADE, 0);
+		dropped = true;
 	}
+
+	return dropped;
 }
 
 /*
@@ -2137,12 +2159,13 @@ PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
 /*
  * Remove listed schemas from the publication.
  */
-static void
+static bool
 PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
 {
 	ObjectAddress obj;
 	ListCell   *lc;
 	Oid			psid;
+	bool		dropped = false;
 
 	foreach(lc, schemas)
 	{
@@ -2165,7 +2188,10 @@ PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
 
 		ObjectAddressSet(obj, PublicationNamespaceRelationId, psid);
 		performDeletion(&obj, DROP_CASCADE, 0);
+		dropped = true;
 	}
+
+	return dropped;
 }
 
 /*
diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
index 86ae50ce531..960f6d002e3 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -401,6 +401,14 @@ DROP ROLE regress_evt_user;
 DROP EVENT TRIGGER regress_event_trigger_drop_objects;
 DROP EVENT TRIGGER undroppable;
 -- Event triggers on relations.
+CREATE TABLE evttrig_pub_tbl (a int);
+CREATE TABLE evttrig_pub_tbl2 (a int);
+CREATE TABLE evttrig_pub_tbl3 (a int);
+CREATE SCHEMA evttrig_pub_schema;
+CREATE SCHEMA evttrig_pub_schema2;
+CREATE PUBLICATION evttrig_pub FOR TABLE evttrig_pub_tbl,
+  evttrig_pub_tbl2, evttrig_pub_tbl3, TABLES IN SCHEMA evttrig_pub_schema,
+  evttrig_pub_schema2;
 CREATE OR REPLACE FUNCTION event_trigger_report_dropped()
  RETURNS event_trigger
  LANGUAGE plpgsql
@@ -434,6 +442,16 @@ BEGIN
 END; $$;
 CREATE EVENT TRIGGER regress_event_trigger_report_end ON ddl_command_end
   EXECUTE PROCEDURE event_trigger_report_end();
+ALTER PUBLICATION evttrig_pub DROP TABLE evttrig_pub_tbl;
+NOTICE:  NORMAL: orig=t normal=f istemp=f type=publication relation identity=public.evttrig_pub_tbl in publication evttrig_pub schema=<NULL> name=<NULL> addr={public,evttrig_pub_tbl} args={evttrig_pub}
+NOTICE:  END: command_tag=ALTER PUBLICATION type=publication identity=evttrig_pub
+ALTER PUBLICATION evttrig_pub DROP TABLES IN SCHEMA evttrig_pub_schema;
+NOTICE:  NORMAL: orig=t normal=f istemp=f type=publication namespace identity=evttrig_pub_schema in publication evttrig_pub schema=<NULL> name=<NULL> addr={evttrig_pub_schema} args={evttrig_pub}
+NOTICE:  END: command_tag=ALTER PUBLICATION type=publication identity=evttrig_pub
+ALTER PUBLICATION evttrig_pub SET TABLE evttrig_pub_tbl2;
+NOTICE:  NORMAL: orig=t normal=f istemp=f type=publication namespace identity=evttrig_pub_schema2 in publication evttrig_pub schema=<NULL> name=<NULL> addr={evttrig_pub_schema2} args={evttrig_pub}
+NOTICE:  NORMAL: orig=t normal=f istemp=f type=publication relation identity=public.evttrig_pub_tbl3 in publication evttrig_pub schema=<NULL> name=<NULL> addr={public,evttrig_pub_tbl3} args={evttrig_pub}
+NOTICE:  END: command_tag=ALTER PUBLICATION type=publication identity=evttrig_pub
 CREATE SCHEMA evttrig
 	CREATE TABLE one (col_a SERIAL PRIMARY KEY, col_b text DEFAULT 'forty two', col_c SERIAL)
 	CREATE INDEX one_idx ON one (col_b)
@@ -590,6 +608,12 @@ NOTICE:  END: command_tag=CREATE OPERATOR FAMILY type=operator family identity=p
 NOTICE:  END: command_tag=CREATE OPERATOR CLASS type=operator class identity=public.evttrigopclass USING btree
 DROP EVENT TRIGGER regress_event_trigger_report_dropped;
 DROP EVENT TRIGGER regress_event_trigger_report_end;
+DROP PUBLICATION evttrig_pub;
+DROP TABLE evttrig_pub_tbl;
+DROP TABLE evttrig_pub_tbl2;
+DROP TABLE evttrig_pub_tbl3;
+DROP SCHEMA evttrig_pub_schema;
+DROP SCHEMA evttrig_pub_schema2;
 -- only allowed from within an event trigger function, should fail
 select pg_event_trigger_table_rewrite_oid();
 ERROR:  pg_event_trigger_table_rewrite_oid() can only be called in a table_rewrite event trigger function
diff --git a/src/test/regress/sql/event_trigger.sql b/src/test/regress/sql/event_trigger.sql
index d0e6ba295fe..028ab53e408 100644
--- a/src/test/regress/sql/event_trigger.sql
+++ b/src/test/regress/sql/event_trigger.sql
@@ -301,6 +301,15 @@ DROP EVENT TRIGGER regress_event_trigger_drop_objects;
 DROP EVENT TRIGGER undroppable;
 
 -- Event triggers on relations.
+CREATE TABLE evttrig_pub_tbl (a int);
+CREATE TABLE evttrig_pub_tbl2 (a int);
+CREATE TABLE evttrig_pub_tbl3 (a int);
+CREATE SCHEMA evttrig_pub_schema;
+CREATE SCHEMA evttrig_pub_schema2;
+CREATE PUBLICATION evttrig_pub FOR TABLE evttrig_pub_tbl,
+  evttrig_pub_tbl2, evttrig_pub_tbl3, TABLES IN SCHEMA evttrig_pub_schema,
+  evttrig_pub_schema2;
+
 CREATE OR REPLACE FUNCTION event_trigger_report_dropped()
  RETURNS event_trigger
  LANGUAGE plpgsql
@@ -335,6 +344,10 @@ END; $$;
 CREATE EVENT TRIGGER regress_event_trigger_report_end ON ddl_command_end
   EXECUTE PROCEDURE event_trigger_report_end();
 
+ALTER PUBLICATION evttrig_pub DROP TABLE evttrig_pub_tbl;
+ALTER PUBLICATION evttrig_pub DROP TABLES IN SCHEMA evttrig_pub_schema;
+ALTER PUBLICATION evttrig_pub SET TABLE evttrig_pub_tbl2;
+
 CREATE SCHEMA evttrig
 	CREATE TABLE one (col_a SERIAL PRIMARY KEY, col_b text DEFAULT 'forty two', col_c SERIAL)
 	CREATE INDEX one_idx ON one (col_b)
@@ -417,6 +430,13 @@ CREATE OPERATOR CLASS evttrigopclass FOR TYPE int USING btree AS STORAGE int;
 DROP EVENT TRIGGER regress_event_trigger_report_dropped;
 DROP EVENT TRIGGER regress_event_trigger_report_end;
 
+DROP PUBLICATION evttrig_pub;
+DROP TABLE evttrig_pub_tbl;
+DROP TABLE evttrig_pub_tbl2;
+DROP TABLE evttrig_pub_tbl3;
+DROP SCHEMA evttrig_pub_schema;
+DROP SCHEMA evttrig_pub_schema2;
+
 -- only allowed from within an event trigger function, should fail
 select pg_event_trigger_table_rewrite_oid();
 
-- 
2.55.0

