v52-0002-Report-error-for-ddls-on-conflict-log-tables.patch
application/octet-stream
Filename: v52-0002-Report-error-for-ddls-on-conflict-log-tables.patch
Type: application/octet-stream
Part: 0
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 v52-0002
Subject: Report error for ddls on conflict log tables
| File | + | − |
|---|---|---|
| src/backend/catalog/aclchk.c | 8 | 0 |
| src/backend/commands/lockcmds.c | 20 | 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 |
From 1d098166437f6fab383de428fd486ffa9cfd2b3b Mon Sep 17 00:00:00 2001
From: Dilip Kumar <dilipkumarb@google.com>
Date: Wed, 10 Jun 2026 16:52:46 +0530
Subject: [PATCH v52 2/6] Report error for ddls on conflict log tables
---
src/backend/catalog/aclchk.c | 8 ++++
src/backend/commands/lockcmds.c | 21 ++++++++-
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 +++++++++
7 files changed, 174 insertions(+), 1 deletion(-)
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 64e176652d1..ae58de452d6 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..128c11cc36f 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,13 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
relkind != RELKIND_VIEW)
continue;
+ /*
+ * Conflict log tables are managed by the system for logical
+ * and should not be locked explicitly.
+ */
+ 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 9af3b616bfa..ac0a5736700 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_WRONG_OBJECT_TYPE),
+ errmsg("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),
--
2.49.0