mirror of https://github.com/citusdata/citus.git
187 lines
5.3 KiB
C
187 lines
5.3 KiB
C
/*-------------------------------------------------------------------------
|
|
* trigger.c
|
|
*
|
|
* This file contains functions to create and process trigger objects on
|
|
* citus tables.
|
|
*
|
|
* Copyright (c) Citus Data, Inc.
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
#include "distributed/pg_version_constants.h"
|
|
|
|
#include "access/genam.h"
|
|
#if PG_VERSION_NUM >= PG_VERSION_12
|
|
#include "access/table.h"
|
|
#else
|
|
#include "access/heapam.h"
|
|
#include "access/htup_details.h"
|
|
#endif
|
|
#include "catalog/indexing.h"
|
|
#include "catalog/namespace.h"
|
|
#include "catalog/pg_trigger.h"
|
|
#include "distributed/citus_ruleutils.h"
|
|
#include "distributed/commands.h"
|
|
#include "distributed/listutils.h"
|
|
#include "distributed/metadata_cache.h"
|
|
#include "utils/fmgroids.h"
|
|
#include "utils/lsyscache.h"
|
|
|
|
/*
|
|
* GetExplicitTriggerCommandList returns the list of DDL commands to create
|
|
* triggers that are explicitly created for the table with relationId. See
|
|
* comment of GetExplicitTriggerIdList function.
|
|
*/
|
|
List *
|
|
GetExplicitTriggerCommandList(Oid relationId)
|
|
{
|
|
List *createTriggerCommandList = NIL;
|
|
|
|
/*
|
|
* Set search_path to NIL so that all objects outside of pg_catalog will be
|
|
* schema-prefixed. pg_catalog will be added automatically when we call
|
|
* PushOverrideSearchPath(), since we set addCatalog to true;
|
|
*/
|
|
OverrideSearchPath *overridePath = GetOverrideSearchPath(CurrentMemoryContext);
|
|
overridePath->schemas = NIL;
|
|
overridePath->addCatalog = true;
|
|
PushOverrideSearchPath(overridePath);
|
|
|
|
List *triggerIdList = GetExplicitTriggerIdList(relationId);
|
|
|
|
Oid triggerId = InvalidOid;
|
|
foreach_oid(triggerId, triggerIdList)
|
|
{
|
|
char *createTriggerCommand = pg_get_triggerdef_command(triggerId);
|
|
|
|
createTriggerCommandList = lappend(createTriggerCommandList,
|
|
createTriggerCommand);
|
|
}
|
|
|
|
/* revert back to original search_path */
|
|
PopOverrideSearchPath();
|
|
|
|
return createTriggerCommandList;
|
|
}
|
|
|
|
|
|
/*
|
|
* GetExplicitTriggerIdList returns a list of OIDs corresponding to the triggers
|
|
* that are explicitly created on the relation with relationId. That means,
|
|
* this function discards internal triggers implicitly created by postgres for
|
|
* foreign key constraint validation and the citus_truncate_trigger.
|
|
*/
|
|
List *
|
|
GetExplicitTriggerIdList(Oid relationId)
|
|
{
|
|
List *triggerIdList = NIL;
|
|
|
|
Relation pgTrigger = heap_open(TriggerRelationId, AccessShareLock);
|
|
|
|
int scanKeyCount = 1;
|
|
ScanKeyData scanKey[1];
|
|
|
|
ScanKeyInit(&scanKey[0], Anum_pg_trigger_tgrelid,
|
|
BTEqualStrategyNumber, F_OIDEQ, relationId);
|
|
|
|
bool useIndex = true;
|
|
SysScanDesc scanDescriptor = systable_beginscan(pgTrigger, TriggerRelidNameIndexId,
|
|
useIndex, NULL, scanKeyCount,
|
|
scanKey);
|
|
|
|
HeapTuple heapTuple = systable_getnext(scanDescriptor);
|
|
while (HeapTupleIsValid(heapTuple))
|
|
{
|
|
Form_pg_trigger triggerForm = (Form_pg_trigger) GETSTRUCT(heapTuple);
|
|
|
|
/*
|
|
* Note that we mark truncate trigger that we create on citus tables as
|
|
* internal. Hence, below we discard citus_truncate_trigger as well as
|
|
* the implicit triggers created by postgres for foreign key validation.
|
|
*/
|
|
if (!triggerForm->tgisinternal)
|
|
{
|
|
Oid triggerId = get_relation_trigger_oid_compat(heapTuple);
|
|
triggerIdList = lappend_oid(triggerIdList, triggerId);
|
|
}
|
|
|
|
heapTuple = systable_getnext(scanDescriptor);
|
|
}
|
|
|
|
systable_endscan(scanDescriptor);
|
|
heap_close(pgTrigger, NoLock);
|
|
|
|
return triggerIdList;
|
|
}
|
|
|
|
|
|
/*
|
|
* get_relation_trigger_oid_compat returns OID of the trigger represented
|
|
* by the constraintForm, which is passed as an heapTuple. OID of the
|
|
* trigger is already stored in the triggerForm struct if major PostgreSQL
|
|
* version is 12. However, in the older versions, we should utilize
|
|
* HeapTupleGetOid to deduce that OID with no cost.
|
|
*/
|
|
Oid
|
|
get_relation_trigger_oid_compat(HeapTuple heapTuple)
|
|
{
|
|
Assert(HeapTupleIsValid(heapTuple));
|
|
|
|
Oid triggerOid = InvalidOid;
|
|
|
|
#if PG_VERSION_NUM >= PG_VERSION_12
|
|
Form_pg_trigger triggerForm = (Form_pg_trigger) GETSTRUCT(heapTuple);
|
|
triggerOid = triggerForm->oid;
|
|
#else
|
|
triggerOid = HeapTupleGetOid(heapTuple);
|
|
#endif
|
|
|
|
return triggerOid;
|
|
}
|
|
|
|
|
|
/*
|
|
* ErrorIfUnsupportedCreateTriggerCommand errors out for the CREATE TRIGGER
|
|
* command that is run for a citus table if it is not citus_truncate_trigger.
|
|
*
|
|
* Note that internal triggers that are created implicitly by postgres for
|
|
* foreign key validation already wouldn't be executed via process utility,
|
|
* hence there is no need to check that case here.
|
|
*/
|
|
void
|
|
ErrorIfUnsupportedCreateTriggerCommand(CreateTrigStmt *createTriggerStmt)
|
|
{
|
|
RangeVar *triggerRelation = createTriggerStmt->relation;
|
|
|
|
bool missingOk = true;
|
|
Oid relationId = RangeVarGetRelid(triggerRelation, AccessShareLock, missingOk);
|
|
|
|
if (!OidIsValid(relationId))
|
|
{
|
|
/*
|
|
* standard_ProcessUtility would already error out if the given table
|
|
* does not exist
|
|
*/
|
|
return;
|
|
}
|
|
|
|
if (!IsCitusTable(relationId))
|
|
{
|
|
return;
|
|
}
|
|
|
|
char *functionName = makeRangeVarFromNameList(createTriggerStmt->funcname)->relname;
|
|
if (strncmp(functionName, CITUS_TRUNCATE_TRIGGER_NAME, NAMEDATALEN) == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
char *relationName = triggerRelation->relname;
|
|
|
|
Assert(relationName != NULL);
|
|
ereport(ERROR, (errmsg("cannot create trigger on relation \"%s\" because it "
|
|
"is either a distributed table or a reference table",
|
|
relationName)));
|
|
}
|