From 34c5ecc5755c9b82c8a27b2d7dd6731502809af6 Mon Sep 17 00:00:00 2001 From: naisila Date: Thu, 4 Aug 2022 17:39:31 +0300 Subject: [PATCH] Prevent creating child triggers on partitions when adding new node Pre PG15, tgisinternal is true for a "child" trigger on a partition cloned from the trigger on the parent. In PG15, tgisinternal is false in that case. However, we don't want to create this trigger on the partition since it will create a conflict when we try to attach the partition to the parent table: ERROR: trigger "..." for relation "{partition_name}" already exists Relevant PG commit: f4566345cf40b068368cb5617e61318da60676ec --- src/backend/distributed/commands/trigger.c | 76 ++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/src/backend/distributed/commands/trigger.c b/src/backend/distributed/commands/trigger.c index 299ffcc32..f517e7e84 100644 --- a/src/backend/distributed/commands/trigger.c +++ b/src/backend/distributed/commands/trigger.c @@ -26,6 +26,9 @@ #include "distributed/metadata/distobject.h" #include "distributed/metadata_cache.h" #include "distributed/metadata_sync.h" +#if PG_VERSION_NUM >= PG_VERSION_15 +#include "distributed/multi_partitioning_utils.h" +#endif #include "distributed/namespace_utils.h" #include "distributed/shard_utils.h" #include "distributed/worker_protocol.h" @@ -53,6 +56,9 @@ static void ExtractDropStmtTriggerAndRelationName(DropStmt *dropTriggerStmt, char **relationName); static void ErrorIfDropStmtDropsMultipleTriggers(DropStmt *dropTriggerStmt); static int16 GetTriggerTypeById(Oid triggerId); +#if PG_VERSION_NUM >= PG_VERSION_15 +static bool TriggerExists(Oid relationId, char *triggerName); +#endif /* GUC that overrides trigger checks for distributed tables and reference tables */ @@ -158,6 +164,15 @@ GetTriggerTupleById(Oid triggerId, bool missingOk) List * GetExplicitTriggerIdList(Oid relationId) { +#if PG_VERSION_NUM >= PG_VERSION_15 + bool partitionTable = false; + Oid parentRelationId = InvalidOid; + if (PartitionTable(relationId)) + { + partitionTable = true; + parentRelationId = PartitionParentOid(relationId); + } +#endif List *triggerIdList = NIL; Relation pgTrigger = table_open(TriggerRelationId, AccessShareLock); @@ -183,7 +198,22 @@ GetExplicitTriggerIdList(Oid relationId) * internal. Hence, below we discard citus_truncate_trigger as well as * the implicit triggers created by postgres for foreign key validation. */ +#if PG_VERSION_NUM >= PG_VERSION_15 + + /* + * Pre PG15, tgisinternal is true for a "child" trigger on a partition + * cloned from the trigger on the parent. + * In PG15, tgisinternal is false in that case. However, we don't want to + * create this trigger on the partition since it will create a conflict + * when we try to attach the partition to the parent table: + * ERROR: trigger "..." for relation "{partition_name}" already exists + */ + bool isChildTrigger = partitionTable && TriggerExists(parentRelationId, NameStr( + triggerForm->tgname)); + if (!triggerForm->tgisinternal && !isChildTrigger) +#else if (!triggerForm->tgisinternal) +#endif { triggerIdList = lappend_oid(triggerIdList, triggerForm->oid); } @@ -198,6 +228,52 @@ GetExplicitTriggerIdList(Oid relationId) } +#if PG_VERSION_NUM >= PG_VERSION_15 + +/* + * ChildTrigger returns + * true: if the given trigger is a child trigger on the given partition + * cloned from the trigger on the parent + * false: otherwise + * It makes use of pg_trigger_tgrelid_tgname_index index on pg_trigger + */ +static bool +TriggerExists(Oid relationId, char *triggerName) +{ + bool relationTriggerExists = false; + Relation pgTrigger = table_open(TriggerRelationId, AccessShareLock); + + int scanKeyCount = 2; + ScanKeyData scanKey[2]; + + ScanKeyInit(&scanKey[0], Anum_pg_trigger_tgrelid, + BTEqualStrategyNumber, F_OIDEQ, relationId); + + ScanKeyInit(&scanKey[1], Anum_pg_trigger_tgname, + BTEqualStrategyNumber, F_NAMEEQ, CStringGetDatum(triggerName)); + + bool useIndex = true; + SysScanDesc scanDescriptor = systable_beginscan(pgTrigger, TriggerRelidNameIndexId, + useIndex, NULL, scanKeyCount, + scanKey); + + HeapTuple heapTuple = systable_getnext(scanDescriptor); + while (HeapTupleIsValid(heapTuple)) + { + relationTriggerExists = true; + heapTuple = systable_getnext(scanDescriptor); + } + + systable_endscan(scanDescriptor); + table_close(pgTrigger, NoLock); + + return relationTriggerExists; +} + + +#endif + + /* * PostprocessCreateTriggerStmt is called after a CREATE TRIGGER command has * been executed by standard process utility. This function errors out for