From fd096c23b45f06d649cf127224c2d8fa599bb081 Mon Sep 17 00:00:00 2001 From: Dilip Kumar Date: Wed, 10 Jun 2026 16:52:46 +0530 Subject: [PATCH v56 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 ++ src/test/regress/expected/subscription.out | 224 ++++++++++++++++++++ src/test/regress/sql/subscription.sql | 227 +++++++++++++++++++++ 9 files changed, 625 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..1353d067573 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 6fa0e8d16fa..e20ccd365bf 100644 --- a/src/test/regress/expected/subscription.out +++ b/src/test/regress/expected/subscription.out @@ -914,6 +914,230 @@ 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. +-- 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 e3b20730154..c557a74977e 100644 --- a/src/test/regress/sql/subscription.sql +++ b/src/test/regress/sql/subscription.sql @@ -711,6 +711,233 @@ 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 $$; + +-- Clean up trigger function +DROP FUNCTION public.dummy_trigger_func(); + + SET client_min_messages = WARNING; -- Clean up remaining test subscription -- 2.49.0