From 7f7037ff8013072040aae80565f19ae992520fa5 Mon Sep 17 00:00:00 2001
From: Dilip Kumar <dilipkumarb@google.com>
Date: Mon, 6 Jul 2026 12:57:05 +0530
Subject: [PATCH v1] doc: Add catalog documentation for logical replication
 conflict log settings

Document the pg_subscription catalog columns subconflictlogrelid and
subconflictlogdest in catalogs.sgml, which were introduced as part of the
configurable conflict log table feature.

Also update ddl.sgml to use proper <firstterm> markup for the term "conflict
schema".
---
 doc/src/sgml/catalogs.sgml          | 26 ++++++++++++++++++++++++++
 doc/src/sgml/ddl.sgml               |  2 +-
 src/backend/catalog/catalog.c       | 13 ++++++++++++-
 src/backend/commands/policy.c       |  5 -----
 src/backend/commands/tablecmds.c    | 20 --------------------
 src/backend/commands/trigger.c      | 10 ----------
 src/backend/rewrite/rewriteDefine.c | 10 ----------
 7 files changed, 39 insertions(+), 47 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4b474c13917..7cdd9bd564a 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -8737,6 +8737,32 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>subconflictlogrelid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       OID of the subscription conflict log table created in the
+       <literal>pg_conflict</literal> namespace, or zero if
+       <link linkend="sql-createsubscription-params-with-conflict-log-destination"><literal>conflict_log_destination</literal></link>
+       is set to <literal>log</literal>.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>subconflictlogdest</structfield> <type>text</type>
+      </para>
+      <para>
+       The destination for conflict logging: <literal>log</literal> (server
+       log only), <literal>table</literal> (conflict log table only), or
+       <literal>all</literal> (both). See
+       <link linkend="sql-createsubscription-params-with-conflict-log-destination"><literal>conflict_log_destination</literal></link>
+       for details.
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>subconninfo</structfield> <type>text</type>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 2b08b54edf5..4d50957bd77 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -3771,7 +3771,7 @@ REVOKE CREATE ON SCHEMA public FROM PUBLIC;
 
    <para>
     The <literal>pg_conflict</literal> schema (sometimes referred to as the
-    <emphasis>conflict schema</emphasis>) contains system-managed conflict
+    <firstterm>conflict schema</firstterm>) contains system-managed conflict
     log tables used for logical replication conflict tracking.
     These tables are created and maintained by the system and are not intended for
     direct user manipulation. Unlike <literal>pg_catalog</literal>, the
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index be8791af875..3f59e59ef9c 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -236,7 +236,9 @@ IsToastClass(Form_pg_class reltuple)
  * IsConflictLogTableClass
  *		True iff pg_class tuple represents a Conflict Log Table.
  *
- *		Does not perform any catalog accesses.
+ *		Does not perform any catalog accesses.  See
+ * 		IsConflictLogTableNamespace() for why operations on these tables are
+ * 		restricted.
  */
 bool
 IsConflictLogTableClass(Form_pg_class reltuple)
@@ -285,6 +287,15 @@ IsToastNamespace(Oid namespaceId)
  *		True iff namespace is pg_conflict.
  *
  *		Does not perform any catalog accesses.
+ *
+ * Tables in the pg_conflict namespace are conflict log tables, created and
+ * maintained by the system to record logical replication conflicts. The
+ * apply worker's inserts depend on their fixed structure, so they are treated
+ * as system-managed: call sites across the backend use this (and
+ * IsConflictLogTableClass()) to reject operations that could disrupt logging,
+ * such as DDL, manual INSERT/UPDATE/MERGE, and row locking. DELETE and
+ * TRUNCATE are the only data operations allowed, so that users can still
+ * prune old conflict rows.
  */
 bool
 IsConflictLogTableNamespace(Oid namespaceId)
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index 4ca086bf4bb..f4bc29b9eca 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -79,11 +79,6 @@ 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_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d2cb0376ac3 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -3908,11 +3908,6 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 		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_WRONG_OBJECT_TYPE),
@@ -10254,11 +10249,6 @@ 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_WRONG_OBJECT_TYPE),
@@ -20271,11 +20261,6 @@ 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_WRONG_OBJECT_TYPE),
@@ -20318,11 +20303,6 @@ 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_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 401baddbfc6..fb99a017304 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -314,11 +314,6 @@ 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_WRONG_OBJECT_TYPE),
@@ -1456,11 +1451,6 @@ RangeVarCallbackForRenameTrigger(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 have triggers, as it could disrupt
-	 * conflict logging.
-	 */
 	if (IsConflictLogTableClass(form))
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 46de580cbe6..ec09a6aadeb 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -262,11 +262,6 @@ 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_WRONG_OBJECT_TYPE),
@@ -769,11 +764,6 @@ 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_WRONG_OBJECT_TYPE),
-- 
2.49.0

