v59-0002-Report-error-for-ddls-on-conflict-log-tables.patch

application/octet-stream

Filename: v59-0002-Report-error-for-ddls-on-conflict-log-tables.patch
Type: application/octet-stream
Part: 1
Message: Re: Proposal: Conflict log history table for Logical Replication

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: format-patch
Series: patch v59-0002
Subject: Report error for ddls on conflict log tables
File+
src/backend/catalog/aclchk.c 8 0
src/backend/commands/lockcmds.c 23 1
src/backend/commands/policy.c 12 0
src/backend/commands/statscmds.c 14 0
src/backend/commands/tablecmds.c 73 0
src/backend/commands/trigger.c 25 0
src/backend/rewrite/rewriteDefine.c 22 0
src/test/regress/expected/subscription.out 270 0
src/test/regress/sql/subscription.sql 271 0
From c2c8735ba1f4a82c6beaa79b525261d0d75c40b5 Mon Sep 17 00:00:00 2001
From: Dilip Kumar <dilipkumarb@google.com>
Date: Thu, 25 Jun 2026 18:36:59 +0530
Subject: [PATCH v59 2/6] Report error for ddls on conflict log tables

---
 src/backend/catalog/aclchk.c               |   8 +
 src/backend/commands/lockcmds.c            |  24 +-
 src/backend/commands/policy.c              |  12 +
 src/backend/commands/statscmds.c           |  14 ++
 src/backend/commands/tablecmds.c           |  73 ++++++
 src/backend/commands/trigger.c             |  25 ++
 src/backend/rewrite/rewriteDefine.c        |  22 ++
 src/test/regress/expected/subscription.out | 270 ++++++++++++++++++++
 src/test/regress/sql/subscription.sql      | 271 +++++++++++++++++++++
 9 files changed, 718 insertions(+), 1 deletion(-)

diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 49680aaa832..66aca583c85 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3674,6 +3674,14 @@ pg_namespace_aclmask_ext(Oid nsp_oid, Oid roleid,
 	Acl		   *acl;
 	Oid			ownerId;
 
+	/*
+	 * Disallow creation in the conflict schema for everyone, including
+	 * superusers, unless in binary-upgrade mode.
+	 */
+	if (!IsBinaryUpgrade && (mask & ACL_CREATE) &&
+		IsConflictLogTableNamespace(nsp_oid))
+		return mask & ~ACL_CREATE;
+
 	/* Superusers bypass all permission checking. */
 	if (superuser_arg(roleid))
 		return mask;
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index f66b8f17b9b..85ffe7092ce 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -16,6 +16,7 @@
 
 #include "access/table.h"
 #include "access/xact.h"
+#include "catalog/catalog.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_inherits.h"
 #include "commands/lockcmds.h"
@@ -83,7 +84,18 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 		return;					/* woops, concurrently dropped; no permissions
 								 * check */
 
-	/* Currently, we only allow plain tables or views to be locked */
+	/*
+	 * Note: Conflict log tables are deliberately NOT blocked here, even though
+	 * other direct DDL on them is rejected elsewhere. pg_dump relies on being
+	 * able to take an ACCESS SHARE lock on these tables to safely dump their
+	 * definitions during a binary upgrade, so we permit LOCK commands on them
+	 * and treat them like ordinary tables here. It's true that a strong lock
+	 * (ShareLock or above) on such a table would conflict with the
+	 * RowExclusiveLock taken by the apply worker's inserts and could stall
+	 * conflict logging as well as the apply worker for as long as it is held.
+	 * But locking a system-managed conflict log table is an unusual thing to
+	 * do, and it doesn't seem worth the trouble of filtering by lock mode here.
+	 */
 	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW)
 		ereport(ERROR,
@@ -198,6 +210,16 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
 				relkind != RELKIND_VIEW)
 				continue;
 
+			/*
+			 * Conflict log tables only support SELECT, DELETE, and TRUNCATE.
+			 * A direct LOCK on them is permitted solely so that pg_dump can
+			 * lock them during a binary upgrade; locking one indirectly by
+			 * locking a view over it is not needed for that, so skip it here
+			 * rather than locking it.
+			 */
+			if (IsConflictLogTableNamespace(get_rel_namespace(relid)))
+				continue;
+
 			/*
 			 * We might be dealing with a self-referential view.  If so, we
 			 * can just stop recursing, since we already locked it.
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index 21b8eebe32d..10ea599bac0 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -79,6 +79,18 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
 	if (!object_ownercheck(RelationRelationId, relid, GetUserId()))
 		aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relid)), rv->relname);
 
+	/*
+	 * Conflict log tables are used internally for logical replication conflict
+	 * logging and should not be modified directly, as it could disrupt
+	 * conflict logging.
+	 */
+	if (IsConflictLogTableClass(classform))
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied: \"%s\" is a conflict log table",
+						rv->relname),
+				 errdetail("Conflict log tables are system-managed tables for logical replication conflicts.")));
+
 	/* No system table modifications unless explicitly allowed. */
 	if (!allowSystemTableMods && IsSystemClass(relid, classform))
 		ereport(ERROR,
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index b354723be44..e8fa81ef555 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -147,6 +147,20 @@ CreateStatistics(CreateStatsStmt *stmt, bool check_rights)
 			aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
 						   RelationGetRelationName(rel));
 
+		/*
+		 * Conflict log tables are system-managed tables used internally for
+		 * logical replication conflict logging. Unlike user tables, they are
+		 * not expected to have complex query usage, so to keep things simple,
+		 * user-defined extended statistics are not required or supported at
+		 * present.
+		 */
+		if (IsConflictLogTableClass(rel->rd_rel))
+			ereport(ERROR,
+					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+					 errmsg("permission denied: \"%s\" is a conflict log table",
+							RelationGetRelationName(rel)),
+					 errdetail("Conflict log tables are system-managed tables for logical replication conflicts.")));
+
 		/* Creating statistics on system catalogs is not allowed */
 		if (!allowSystemTableMods && IsSystemRelation(rel))
 			ereport(ERROR,
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 43be9c7eb4e..95ef9a14c39 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2750,6 +2750,18 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 					 errmsg("cannot inherit from partition \"%s\"",
 							RelationGetRelationName(relation))));
 
+		/*
+		 * Conflict log tables are managed by the system for logical
+		 * replication and should not be used as parent tables, as
+		 * inheritance could interfere with the logging behavior.
+		 */
+		if (IsConflictLogTableNamespace(relation->rd_rel->relnamespace))
+			ereport(ERROR,
+					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+					 errmsg("permission denied: cannot inherit from conflict log table \"%s\"",
+							RelationGetRelationName(relation)),
+					 errdetail("Conflict log tables are system-managed tables for logical replication conflicts.")));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
 			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
 			relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
@@ -3887,6 +3899,19 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 	if (!object_ownercheck(RelationRelationId, myrelid, GetUserId()))
 		aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(myrelid)),
 					   NameStr(classform->relname));
+
+	/*
+	 * Conflict log tables are used internally for logical replication conflict
+	 * logging and should not be modified directly, as it could disrupt
+	 * conflict logging.
+	 */
+	if (IsConflictLogTableClass(classform))
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied: \"%s\" is a conflict log table",
+						NameStr(classform->relname)),
+				 errdetail("Conflict log tables are system-managed tables for logical replication conflicts.")));
+
 	if (!allowSystemTableMods && IsSystemClass(myrelid, classform))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -6889,6 +6914,18 @@ ATSimplePermissions(AlterTableType cmdtype, Relation rel, int allowed_targets)
 		aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
 					   RelationGetRelationName(rel));
 
+	/*
+	 * Conflict log tables are used internally for logical replication conflict
+	 * logging and should not be altered directly, as it could disrupt conflict
+	 * logging.
+	 */
+	if (IsConflictLogTableClass(rel->rd_rel))
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied: \"%s\" is a conflict log table",
+						RelationGetRelationName(rel)),
+				 errdetail("Conflict log tables are system-managed tables for logical replication conflicts.")));
+
 	if (!allowSystemTableMods && IsSystemRelation(rel))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -10198,6 +10235,18 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 				 errmsg("referenced relation \"%s\" is not a table",
 						RelationGetRelationName(pkrel))));
 
+	/*
+	 * Conflict log tables are used internally for logical replication conflict
+	 * logging and should not be referenced by foreign keys, as it could
+	 * disrupt conflict logging.
+	 */
+	if (IsConflictLogTableClass(pkrel->rd_rel))
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied: \"%s\" is a conflict log table",
+						RelationGetRelationName(pkrel)),
+				 errdetail("Conflict log tables are system-managed tables for logical replication conflicts.")));
+
 	if (!allowSystemTableMods && IsSystemRelation(pkrel))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -19818,6 +19867,18 @@ RangeVarCallbackOwnsRelation(const RangeVar *relation,
 		aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relId)),
 					   relation->relname);
 
+	/*
+	 * Conflict log tables are used internally for logical replication conflict
+	 * logging and should not be modified directly, as it could disrupt
+	 * conflict logging.
+	 */
+	if (IsConflictLogTableClass((Form_pg_class) GETSTRUCT(tuple)))
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied: \"%s\" is a conflict log table",
+						relation->relname),
+				 errdetail("Conflict log tables are system-managed tables for logical replication conflicts.")));
+
 	if (!allowSystemTableMods &&
 		IsSystemClass(relId, (Form_pg_class) GETSTRUCT(tuple)))
 		ereport(ERROR,
@@ -19853,6 +19914,18 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 	if (!object_ownercheck(RelationRelationId, relid, GetUserId()))
 		aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relid)), rv->relname);
 
+	/*
+	 * Conflict log tables are used internally for logical replication conflict
+	 * logging and should not be altered directly, as it could disrupt conflict
+	 * logging.
+	 */
+	if (IsConflictLogTableClass(classform))
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied: \"%s\" is a conflict log table",
+						rv->relname),
+				 errdetail("Conflict log tables are system-managed tables for logical replication conflicts.")));
+
 	/* No system table modifications unless explicitly allowed. */
 	if (!allowSystemTableMods && IsSystemClass(relid, classform))
 		ereport(ERROR,
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index b87b4b40d07..de7957a15f7 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -314,6 +314,18 @@ CreateTriggerFiringOn(const CreateTrigStmt *stmt, const char *queryString,
 						RelationGetRelationName(rel)),
 				 errdetail_relkind_not_supported(rel->rd_rel->relkind)));
 
+	/*
+	 * Conflict log tables are used internally for logical replication conflict
+	 * logging and should not have triggers, as it could disrupt conflict
+	 * logging.
+	 */
+	if (IsConflictLogTableClass(rel->rd_rel))
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied: \"%s\" is a conflict log table",
+						RelationGetRelationName(rel)),
+				 errdetail("Conflict log tables are system-managed tables for logical replication conflicts.")));
+
 	if (!allowSystemTableMods && IsSystemRelation(rel))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -1443,6 +1455,19 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
 	/* you must own the table to rename one of its triggers */
 	if (!object_ownercheck(RelationRelationId, relid, GetUserId()))
 		aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relid)), rv->relname);
+
+	/*
+	 * Conflict log tables are used internally for logical replication conflict
+	 * logging and should not have triggers, as it could disrupt conflict
+	 * logging.
+	 */
+	if (IsConflictLogTableClass(form))
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied: \"%s\" is a conflict log table",
+						rv->relname),
+				 errdetail("Conflict log tables are system-managed tables for logical replication conflicts.")));
+
 	if (!allowSystemTableMods && IsSystemClass(relid, form))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 6a223fbeaa4..d00d76613a1 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -262,6 +262,17 @@ DefineQueryRewrite(const char *rulename,
 						RelationGetRelationName(event_relation)),
 				 errdetail_relkind_not_supported(event_relation->rd_rel->relkind)));
 
+	/*
+	 * Conflict log tables are used internally for logical replication conflict
+	 * logging and should not have rules, as it could disrupt conflict logging.
+	 */
+	if (IsConflictLogTableClass(event_relation->rd_rel))
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied: \"%s\" is a conflict log table",
+						RelationGetRelationName(event_relation)),
+				 errdetail("Conflict log tables are system-managed tables for logical replication conflicts.")));
+
 	if (!allowSystemTableMods && IsSystemRelation(event_relation))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -772,6 +783,17 @@ RangeVarCallbackForRenameRule(const RangeVar *rv, Oid relid, Oid oldrelid,
 				 errmsg("relation \"%s\" cannot have rules", rv->relname),
 				 errdetail_relkind_not_supported(form->relkind)));
 
+	/*
+	 * Conflict log tables are used internally for logical replication conflict
+	 * logging and should not have rules, as it could disrupt conflict logging.
+	 */
+	if (IsConflictLogTableClass(form))
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied: \"%s\" is a conflict log table",
+						rv->relname),
+				 errdetail("Conflict log tables are system-managed tables for logical replication conflicts.")));
+
 	if (!allowSystemTableMods && IsSystemClass(relid, form))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index ea050578de7..6f1b2c47a6d 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -896,6 +896,276 @@ JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
 WHERE s.subname = 'regress_conflict_protection_test' \gset
 TRUNCATE :conflict_tab;
 DELETE FROM :conflict_tab;
+-- Trying to create a new table manually in the pg_conflict namespace
+-- This should fail as the namespace is reserved for conflict log tables
+CREATE TABLE pg_conflict.manual_table (id int);
+ERROR:  permission denied for schema pg_conflict
+LINE 1: CREATE TABLE pg_conflict.manual_table (id int);
+                     ^
+-- Moving an existing table into the pg_conflict namespace
+-- Users should not be able to move their own tables within this namespace
+CREATE TABLE public.test_move (id int);
+ALTER TABLE public.test_move SET SCHEMA pg_conflict;
+ERROR:  permission denied for schema pg_conflict
+DROP TABLE public.test_move;
+-- Setup dummy trigger function for trigger tests
+CREATE FUNCTION public.dummy_trigger_func() RETURNS trigger AS $$
+BEGIN
+    RETURN NEW;
+END;
+$$ LANGUAGE plpgsql;
+-- Policy on conflict log table
+DO $$
+DECLARE
+    tab_name text;
+    err_msg text;
+    err_detail text;
+BEGIN
+    SELECT 'pg_conflict.' || relname INTO tab_name
+    FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+    WHERE s.subname = 'regress_conflict_protection_test';
+    EXECUTE 'CREATE POLICY p1 ON ' || tab_name || ' USING (true)';
+EXCEPTION WHEN insufficient_privilege THEN
+    GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT, err_detail = PG_EXCEPTION_DETAIL;
+    RAISE NOTICE 'Policy error: %, detail: %',
+        regexp_replace(err_msg, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx'),
+        regexp_replace(err_detail, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx');
+END $$;
+NOTICE:  Policy error: permission denied: "pg_conflict_log_xxx" is a conflict log table, detail: Conflict log tables are system-managed tables for logical replication conflicts.
+-- Statistics on conflict log table
+DO $$
+DECLARE
+    tab_name text;
+    err_msg text;
+    err_detail text;
+BEGIN
+    SELECT 'pg_conflict.' || relname INTO tab_name
+    FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+    WHERE s.subname = 'regress_conflict_protection_test';
+    EXECUTE 'CREATE STATISTICS s1 ON relname, schemaname FROM ' || tab_name;
+EXCEPTION WHEN insufficient_privilege THEN
+    GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT, err_detail = PG_EXCEPTION_DETAIL;
+    RAISE NOTICE 'Statistics error: %, detail: %',
+        regexp_replace(err_msg, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx'),
+        regexp_replace(err_detail, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx');
+END $$;
+NOTICE:  Statistics error: permission denied: "pg_conflict_log_xxx" is a conflict log table, detail: Conflict log tables are system-managed tables for logical replication conflicts.
+-- Inheritance from conflict log table
+DO $$
+DECLARE
+    tab_name text;
+    err_msg text;
+    err_detail text;
+BEGIN
+    SELECT 'pg_conflict.' || relname INTO tab_name
+    FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+    WHERE s.subname = 'regress_conflict_protection_test';
+    EXECUTE 'CREATE TABLE public.conflict_child () INHERITS (' || tab_name || ')';
+EXCEPTION WHEN insufficient_privilege THEN
+    GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT, err_detail = PG_EXCEPTION_DETAIL;
+    RAISE NOTICE 'Inheritance error: %, detail: %',
+        regexp_replace(err_msg, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx'),
+        regexp_replace(err_detail, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx');
+END $$;
+NOTICE:  Inheritance error: permission denied: cannot inherit from conflict log table "pg_conflict_log_xxx", detail: Conflict log tables are system-managed tables for logical replication conflicts.
+-- Column rename on conflict log table
+DO $$
+DECLARE
+    tab_name text;
+    err_msg text;
+    err_detail text;
+BEGIN
+    SELECT 'pg_conflict.' || relname INTO tab_name
+    FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+    WHERE s.subname = 'regress_conflict_protection_test';
+    EXECUTE 'ALTER TABLE ' || tab_name || ' RENAME COLUMN relname TO new_relname';
+EXCEPTION WHEN insufficient_privilege THEN
+    GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT, err_detail = PG_EXCEPTION_DETAIL;
+    RAISE NOTICE 'Column rename error: %, detail: %',
+        regexp_replace(err_msg, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx'),
+        regexp_replace(err_detail, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx');
+END $$;
+NOTICE:  Column rename error: permission denied: "pg_conflict_log_xxx" is a conflict log table, detail: Conflict log tables are system-managed tables for logical replication conflicts.
+-- Foreign key reference to conflict log table
+DO $$
+DECLARE
+    tab_name text;
+    err_msg text;
+    err_detail text;
+BEGIN
+    SELECT 'pg_conflict.' || relname INTO tab_name
+    FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+    WHERE s.subname = 'regress_conflict_protection_test';
+    EXECUTE 'CREATE TABLE public.conflict_fk (relname text REFERENCES ' || tab_name || '(relname))';
+EXCEPTION WHEN insufficient_privilege THEN
+    GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT, err_detail = PG_EXCEPTION_DETAIL;
+    RAISE NOTICE 'Foreign key error: %, detail: %',
+        regexp_replace(err_msg, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx'),
+        regexp_replace(err_detail, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx');
+END $$;
+NOTICE:  Foreign key error: permission denied: "pg_conflict_log_xxx" is a conflict log table, detail: Conflict log tables are system-managed tables for logical replication conflicts.
+-- Alter table owner
+DO $$
+DECLARE
+    tab_name text;
+    err_msg text;
+    err_detail text;
+BEGIN
+    SELECT 'pg_conflict.' || relname INTO tab_name
+    FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+    WHERE s.subname = 'regress_conflict_protection_test';
+    EXECUTE 'ALTER TABLE ' || tab_name || ' OWNER TO regress_subscription_user_dummy';
+EXCEPTION WHEN insufficient_privilege THEN
+    GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT, err_detail = PG_EXCEPTION_DETAIL;
+    RAISE NOTICE 'Alter owner error: %, detail: %',
+        regexp_replace(err_msg, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx'),
+        regexp_replace(err_detail, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx');
+END $$;
+NOTICE:  Alter owner error: permission denied: "pg_conflict_log_xxx" is a conflict log table, detail: Conflict log tables are system-managed tables for logical replication conflicts.
+-- Alter table schema
+DO $$
+DECLARE
+    tab_name text;
+    err_msg text;
+    err_detail text;
+BEGIN
+    SELECT 'pg_conflict.' || relname INTO tab_name
+    FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+    WHERE s.subname = 'regress_conflict_protection_test';
+    EXECUTE 'ALTER TABLE ' || tab_name || ' SET SCHEMA public';
+EXCEPTION WHEN insufficient_privilege THEN
+    GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT, err_detail = PG_EXCEPTION_DETAIL;
+    RAISE NOTICE 'Alter schema error: %, detail: %',
+        regexp_replace(err_msg, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx'),
+        regexp_replace(err_detail, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx');
+END $$;
+NOTICE:  Alter schema error: permission denied: "pg_conflict_log_xxx" is a conflict log table, detail: Conflict log tables are system-managed tables for logical replication conflicts.
+-- Create trigger on conflict log table
+DO $$
+DECLARE
+    tab_name text;
+    err_msg text;
+    err_detail text;
+BEGIN
+    SELECT 'pg_conflict.' || relname INTO tab_name
+    FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+    WHERE s.subname = 'regress_conflict_protection_test';
+    EXECUTE 'CREATE TRIGGER t1 BEFORE INSERT ON ' || tab_name || ' FOR EACH ROW EXECUTE FUNCTION public.dummy_trigger_func()';
+EXCEPTION WHEN insufficient_privilege THEN
+    GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT, err_detail = PG_EXCEPTION_DETAIL;
+    RAISE NOTICE 'Create trigger error: %, detail: %',
+        regexp_replace(err_msg, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx'),
+        regexp_replace(err_detail, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx');
+END $$;
+NOTICE:  Create trigger error: permission denied: "pg_conflict_log_xxx" is a conflict log table, detail: Conflict log tables are system-managed tables for logical replication conflicts.
+-- Rename trigger on conflict log table
+DO $$
+DECLARE
+    tab_name text;
+    err_msg text;
+    err_detail text;
+BEGIN
+    SELECT 'pg_conflict.' || relname INTO tab_name
+    FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+    WHERE s.subname = 'regress_conflict_protection_test';
+    EXECUTE 'ALTER TRIGGER non_existent_trigger ON ' || tab_name || ' RENAME TO new_trigger';
+EXCEPTION WHEN insufficient_privilege THEN
+    GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT, err_detail = PG_EXCEPTION_DETAIL;
+    RAISE NOTICE 'Rename trigger error: %, detail: %',
+        regexp_replace(err_msg, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx'),
+        regexp_replace(err_detail, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx');
+END $$;
+NOTICE:  Rename trigger error: permission denied: "pg_conflict_log_xxx" is a conflict log table, detail: Conflict log tables are system-managed tables for logical replication conflicts.
+-- Create rule on conflict log table
+DO $$
+DECLARE
+    tab_name text;
+    err_msg text;
+    err_detail text;
+BEGIN
+    SELECT 'pg_conflict.' || relname INTO tab_name
+    FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+    WHERE s.subname = 'regress_conflict_protection_test';
+    EXECUTE 'CREATE RULE r1 AS ON INSERT TO ' || tab_name || ' DO INSTEAD NOTHING';
+EXCEPTION WHEN insufficient_privilege THEN
+    GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT, err_detail = PG_EXCEPTION_DETAIL;
+    RAISE NOTICE 'Create rule error: %, detail: %',
+        regexp_replace(err_msg, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx'),
+        regexp_replace(err_detail, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx');
+END $$;
+NOTICE:  Create rule error: permission denied: "pg_conflict_log_xxx" is a conflict log table, detail: Conflict log tables are system-managed tables for logical replication conflicts.
+-- Rename rule on conflict log table
+DO $$
+DECLARE
+    tab_name text;
+    err_msg text;
+    err_detail text;
+BEGIN
+    SELECT 'pg_conflict.' || relname INTO tab_name
+    FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+    WHERE s.subname = 'regress_conflict_protection_test';
+    EXECUTE 'ALTER RULE non_existent_rule ON ' || tab_name || ' RENAME TO new_rule';
+EXCEPTION WHEN insufficient_privilege THEN
+    GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT, err_detail = PG_EXCEPTION_DETAIL;
+    RAISE NOTICE 'Rename rule error: %, detail: %',
+        regexp_replace(err_msg, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx'),
+        regexp_replace(err_detail, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx');
+END $$;
+NOTICE:  Rename rule error: permission denied: "pg_conflict_log_xxx" is a conflict log table, detail: Conflict log tables are system-managed tables for logical replication conflicts.
+-- Create index on conflict log table
+DO $$
+DECLARE
+    tab_name text;
+    err_msg text;
+    err_detail text;
+BEGIN
+    SELECT 'pg_conflict.' || relname INTO tab_name
+    FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+    WHERE s.subname = 'regress_conflict_protection_test';
+    EXECUTE 'CREATE INDEX idx1 ON ' || tab_name || ' (relname)';
+EXCEPTION WHEN insufficient_privilege THEN
+    GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT, err_detail = PG_EXCEPTION_DETAIL;
+    RAISE NOTICE 'Create index error: %, detail: %',
+        regexp_replace(err_msg, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx'),
+        regexp_replace(err_detail, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx');
+END $$;
+NOTICE:  Create index error: permission denied: "pg_conflict_log_xxx" is a conflict log table, detail: Conflict log tables are system-managed tables for logical replication conflicts.
+-- fail - conflict log table cannot be added to a publication
+DO $$
+DECLARE
+    tab_name text;
+    err_msg text;
+    err_detail text;
+BEGIN
+    SELECT 'pg_conflict.' || relname INTO tab_name
+    FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+    WHERE s.subname = 'regress_conflict_protection_test';
+    EXECUTE 'CREATE PUBLICATION testpub_for_clt FOR TABLE ' || tab_name;
+EXCEPTION WHEN invalid_parameter_value THEN
+    GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT, err_detail = PG_EXCEPTION_DETAIL;
+    RAISE NOTICE 'Publication error: %, detail: %',
+        regexp_replace(err_msg, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx'),
+        err_detail;
+END $$;
+NOTICE:  Publication error: cannot add relation "pg_conflict_log_xxx" to publication, detail: This operation is not supported for conflict log tables.
+-- fail - cannot lock rows in conflict log table
+DO $$
+DECLARE
+    tab_name text;
+    err_msg text;
+BEGIN
+    SELECT 'pg_conflict.' || relname INTO tab_name
+    FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+    WHERE s.subname = 'regress_conflict_protection_test';
+    EXECUTE 'SELECT 1 FROM ' || tab_name || ' FOR UPDATE';
+EXCEPTION WHEN wrong_object_type THEN
+    GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT;
+    RAISE NOTICE 'Row lock error: %',
+        regexp_replace(err_msg, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx');
+END $$;
+NOTICE:  Row lock error: cannot lock rows in the conflict log table "pg_conflict_log_xxx"
+-- Clean up trigger function
+DROP FUNCTION public.dummy_trigger_func();
 SET client_min_messages = WARNING;
 -- Clean up remaining test subscription
 ALTER SUBSCRIPTION regress_conflict_log_default DISABLE;
diff --git a/src/test/regress/sql/subscription.sql b/src/test/regress/sql/subscription.sql
index 76dfd574d8a..084e5e7e78f 100644
--- a/src/test/regress/sql/subscription.sql
+++ b/src/test/regress/sql/subscription.sql
@@ -699,6 +699,277 @@ WHERE s.subname = 'regress_conflict_protection_test' \gset
 TRUNCATE :conflict_tab;
 DELETE FROM :conflict_tab;
 
+-- Trying to create a new table manually in the pg_conflict namespace
+-- This should fail as the namespace is reserved for conflict log tables
+CREATE TABLE pg_conflict.manual_table (id int);
+
+-- Moving an existing table into the pg_conflict namespace
+-- Users should not be able to move their own tables within this namespace
+CREATE TABLE public.test_move (id int);
+ALTER TABLE public.test_move SET SCHEMA pg_conflict;
+DROP TABLE public.test_move;
+
+-- Setup dummy trigger function for trigger tests
+CREATE FUNCTION public.dummy_trigger_func() RETURNS trigger AS $$
+BEGIN
+    RETURN NEW;
+END;
+$$ LANGUAGE plpgsql;
+
+-- Policy on conflict log table
+DO $$
+DECLARE
+    tab_name text;
+    err_msg text;
+    err_detail text;
+BEGIN
+    SELECT 'pg_conflict.' || relname INTO tab_name
+    FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+    WHERE s.subname = 'regress_conflict_protection_test';
+    EXECUTE 'CREATE POLICY p1 ON ' || tab_name || ' USING (true)';
+EXCEPTION WHEN insufficient_privilege THEN
+    GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT, err_detail = PG_EXCEPTION_DETAIL;
+    RAISE NOTICE 'Policy error: %, detail: %',
+        regexp_replace(err_msg, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx'),
+        regexp_replace(err_detail, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx');
+END $$;
+
+-- Statistics on conflict log table
+DO $$
+DECLARE
+    tab_name text;
+    err_msg text;
+    err_detail text;
+BEGIN
+    SELECT 'pg_conflict.' || relname INTO tab_name
+    FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+    WHERE s.subname = 'regress_conflict_protection_test';
+    EXECUTE 'CREATE STATISTICS s1 ON relname, schemaname FROM ' || tab_name;
+EXCEPTION WHEN insufficient_privilege THEN
+    GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT, err_detail = PG_EXCEPTION_DETAIL;
+    RAISE NOTICE 'Statistics error: %, detail: %',
+        regexp_replace(err_msg, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx'),
+        regexp_replace(err_detail, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx');
+END $$;
+
+-- Inheritance from conflict log table
+DO $$
+DECLARE
+    tab_name text;
+    err_msg text;
+    err_detail text;
+BEGIN
+    SELECT 'pg_conflict.' || relname INTO tab_name
+    FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+    WHERE s.subname = 'regress_conflict_protection_test';
+    EXECUTE 'CREATE TABLE public.conflict_child () INHERITS (' || tab_name || ')';
+EXCEPTION WHEN insufficient_privilege THEN
+    GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT, err_detail = PG_EXCEPTION_DETAIL;
+    RAISE NOTICE 'Inheritance error: %, detail: %',
+        regexp_replace(err_msg, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx'),
+        regexp_replace(err_detail, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx');
+END $$;
+
+-- Column rename on conflict log table
+DO $$
+DECLARE
+    tab_name text;
+    err_msg text;
+    err_detail text;
+BEGIN
+    SELECT 'pg_conflict.' || relname INTO tab_name
+    FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+    WHERE s.subname = 'regress_conflict_protection_test';
+    EXECUTE 'ALTER TABLE ' || tab_name || ' RENAME COLUMN relname TO new_relname';
+EXCEPTION WHEN insufficient_privilege THEN
+    GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT, err_detail = PG_EXCEPTION_DETAIL;
+    RAISE NOTICE 'Column rename error: %, detail: %',
+        regexp_replace(err_msg, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx'),
+        regexp_replace(err_detail, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx');
+END $$;
+
+-- Foreign key reference to conflict log table
+DO $$
+DECLARE
+    tab_name text;
+    err_msg text;
+    err_detail text;
+BEGIN
+    SELECT 'pg_conflict.' || relname INTO tab_name
+    FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+    WHERE s.subname = 'regress_conflict_protection_test';
+    EXECUTE 'CREATE TABLE public.conflict_fk (relname text REFERENCES ' || tab_name || '(relname))';
+EXCEPTION WHEN insufficient_privilege THEN
+    GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT, err_detail = PG_EXCEPTION_DETAIL;
+    RAISE NOTICE 'Foreign key error: %, detail: %',
+        regexp_replace(err_msg, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx'),
+        regexp_replace(err_detail, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx');
+END $$;
+
+-- Alter table owner
+DO $$
+DECLARE
+    tab_name text;
+    err_msg text;
+    err_detail text;
+BEGIN
+    SELECT 'pg_conflict.' || relname INTO tab_name
+    FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+    WHERE s.subname = 'regress_conflict_protection_test';
+    EXECUTE 'ALTER TABLE ' || tab_name || ' OWNER TO regress_subscription_user_dummy';
+EXCEPTION WHEN insufficient_privilege THEN
+    GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT, err_detail = PG_EXCEPTION_DETAIL;
+    RAISE NOTICE 'Alter owner error: %, detail: %',
+        regexp_replace(err_msg, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx'),
+        regexp_replace(err_detail, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx');
+END $$;
+
+-- Alter table schema
+DO $$
+DECLARE
+    tab_name text;
+    err_msg text;
+    err_detail text;
+BEGIN
+    SELECT 'pg_conflict.' || relname INTO tab_name
+    FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+    WHERE s.subname = 'regress_conflict_protection_test';
+    EXECUTE 'ALTER TABLE ' || tab_name || ' SET SCHEMA public';
+EXCEPTION WHEN insufficient_privilege THEN
+    GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT, err_detail = PG_EXCEPTION_DETAIL;
+    RAISE NOTICE 'Alter schema error: %, detail: %',
+        regexp_replace(err_msg, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx'),
+        regexp_replace(err_detail, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx');
+END $$;
+
+-- Create trigger on conflict log table
+DO $$
+DECLARE
+    tab_name text;
+    err_msg text;
+    err_detail text;
+BEGIN
+    SELECT 'pg_conflict.' || relname INTO tab_name
+    FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+    WHERE s.subname = 'regress_conflict_protection_test';
+    EXECUTE 'CREATE TRIGGER t1 BEFORE INSERT ON ' || tab_name || ' FOR EACH ROW EXECUTE FUNCTION public.dummy_trigger_func()';
+EXCEPTION WHEN insufficient_privilege THEN
+    GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT, err_detail = PG_EXCEPTION_DETAIL;
+    RAISE NOTICE 'Create trigger error: %, detail: %',
+        regexp_replace(err_msg, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx'),
+        regexp_replace(err_detail, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx');
+END $$;
+
+-- Rename trigger on conflict log table
+DO $$
+DECLARE
+    tab_name text;
+    err_msg text;
+    err_detail text;
+BEGIN
+    SELECT 'pg_conflict.' || relname INTO tab_name
+    FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+    WHERE s.subname = 'regress_conflict_protection_test';
+    EXECUTE 'ALTER TRIGGER non_existent_trigger ON ' || tab_name || ' RENAME TO new_trigger';
+EXCEPTION WHEN insufficient_privilege THEN
+    GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT, err_detail = PG_EXCEPTION_DETAIL;
+    RAISE NOTICE 'Rename trigger error: %, detail: %',
+        regexp_replace(err_msg, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx'),
+        regexp_replace(err_detail, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx');
+END $$;
+
+-- Create rule on conflict log table
+DO $$
+DECLARE
+    tab_name text;
+    err_msg text;
+    err_detail text;
+BEGIN
+    SELECT 'pg_conflict.' || relname INTO tab_name
+    FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+    WHERE s.subname = 'regress_conflict_protection_test';
+    EXECUTE 'CREATE RULE r1 AS ON INSERT TO ' || tab_name || ' DO INSTEAD NOTHING';
+EXCEPTION WHEN insufficient_privilege THEN
+    GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT, err_detail = PG_EXCEPTION_DETAIL;
+    RAISE NOTICE 'Create rule error: %, detail: %',
+        regexp_replace(err_msg, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx'),
+        regexp_replace(err_detail, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx');
+END $$;
+
+-- Rename rule on conflict log table
+DO $$
+DECLARE
+    tab_name text;
+    err_msg text;
+    err_detail text;
+BEGIN
+    SELECT 'pg_conflict.' || relname INTO tab_name
+    FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+    WHERE s.subname = 'regress_conflict_protection_test';
+    EXECUTE 'ALTER RULE non_existent_rule ON ' || tab_name || ' RENAME TO new_rule';
+EXCEPTION WHEN insufficient_privilege THEN
+    GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT, err_detail = PG_EXCEPTION_DETAIL;
+    RAISE NOTICE 'Rename rule error: %, detail: %',
+        regexp_replace(err_msg, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx'),
+        regexp_replace(err_detail, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx');
+END $$;
+
+-- Create index on conflict log table
+DO $$
+DECLARE
+    tab_name text;
+    err_msg text;
+    err_detail text;
+BEGIN
+    SELECT 'pg_conflict.' || relname INTO tab_name
+    FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+    WHERE s.subname = 'regress_conflict_protection_test';
+    EXECUTE 'CREATE INDEX idx1 ON ' || tab_name || ' (relname)';
+EXCEPTION WHEN insufficient_privilege THEN
+    GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT, err_detail = PG_EXCEPTION_DETAIL;
+    RAISE NOTICE 'Create index error: %, detail: %',
+        regexp_replace(err_msg, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx'),
+        regexp_replace(err_detail, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx');
+END $$;
+
+-- fail - conflict log table cannot be added to a publication
+DO $$
+DECLARE
+    tab_name text;
+    err_msg text;
+    err_detail text;
+BEGIN
+    SELECT 'pg_conflict.' || relname INTO tab_name
+    FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+    WHERE s.subname = 'regress_conflict_protection_test';
+    EXECUTE 'CREATE PUBLICATION testpub_for_clt FOR TABLE ' || tab_name;
+EXCEPTION WHEN invalid_parameter_value THEN
+    GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT, err_detail = PG_EXCEPTION_DETAIL;
+    RAISE NOTICE 'Publication error: %, detail: %',
+        regexp_replace(err_msg, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx'),
+        err_detail;
+END $$;
+
+-- fail - cannot lock rows in conflict log table
+DO $$
+DECLARE
+    tab_name text;
+    err_msg text;
+BEGIN
+    SELECT 'pg_conflict.' || relname INTO tab_name
+    FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+    WHERE s.subname = 'regress_conflict_protection_test';
+    EXECUTE 'SELECT 1 FROM ' || tab_name || ' FOR UPDATE';
+EXCEPTION WHEN wrong_object_type THEN
+    GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT;
+    RAISE NOTICE 'Row lock error: %',
+        regexp_replace(err_msg, 'pg_conflict_log_\d+', 'pg_conflict_log_xxx');
+END $$;
+
+-- Clean up trigger function
+DROP FUNCTION public.dummy_trigger_func();
+
+
 SET client_min_messages = WARNING;
 
 -- Clean up remaining test subscription
-- 
2.49.0