mirror of https://github.com/citusdata/citus.git
Merge pull request #3858 from citusdata/copy_paste_pg_get_triggerdef
* Error out if creating a citus table from a table having triggers already. * Error out for CREATE TRIGGER commands that are run on citus tables. * Introduce the ability to deparse CREATE TRIGGER commands needed to recreate triggers on a table.pull/3863/head
commit
738a4ddb58
|
@ -99,6 +99,7 @@ static void EnsureTableCanBeColocatedWith(Oid relationId, char replicationModel,
|
||||||
Oid sourceRelationId);
|
Oid sourceRelationId);
|
||||||
static void EnsureLocalTableEmpty(Oid relationId);
|
static void EnsureLocalTableEmpty(Oid relationId);
|
||||||
static void EnsureTableNotDistributed(Oid relationId);
|
static void EnsureTableNotDistributed(Oid relationId);
|
||||||
|
static void EnsureRelationHasNoTriggers(Oid relationId);
|
||||||
static Oid SupportFunctionForColumn(Var *partitionColumn, Oid accessMethodId,
|
static Oid SupportFunctionForColumn(Var *partitionColumn, Oid accessMethodId,
|
||||||
int16 supportFunctionNumber);
|
int16 supportFunctionNumber);
|
||||||
static void EnsureLocalTableEmptyIfNecessary(Oid relationId, char distributionMethod,
|
static void EnsureLocalTableEmptyIfNecessary(Oid relationId, char distributionMethod,
|
||||||
|
@ -650,6 +651,7 @@ EnsureRelationCanBeDistributed(Oid relationId, Var *distributionColumn,
|
||||||
EnsureTableNotDistributed(relationId);
|
EnsureTableNotDistributed(relationId);
|
||||||
EnsureLocalTableEmptyIfNecessary(relationId, distributionMethod, viaDeprecatedAPI);
|
EnsureLocalTableEmptyIfNecessary(relationId, distributionMethod, viaDeprecatedAPI);
|
||||||
EnsureReplicationSettings(InvalidOid, replicationModel);
|
EnsureReplicationSettings(InvalidOid, replicationModel);
|
||||||
|
EnsureRelationHasNoTriggers(relationId);
|
||||||
|
|
||||||
/* we assume callers took necessary locks */
|
/* we assume callers took necessary locks */
|
||||||
Relation relation = relation_open(relationId, NoLock);
|
Relation relation = relation_open(relationId, NoLock);
|
||||||
|
@ -972,6 +974,31 @@ EnsureReplicationSettings(Oid relationId, char replicationModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* EnsureRelationHasNoTriggers errors out if the given table has triggers on
|
||||||
|
* it. See also GetExplicitTriggerIdList function's comment for the triggers this
|
||||||
|
* function errors out.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
EnsureRelationHasNoTriggers(Oid relationId)
|
||||||
|
{
|
||||||
|
List *explicitTriggerIds = GetExplicitTriggerIdList(relationId);
|
||||||
|
|
||||||
|
if (list_length(explicitTriggerIds) > 0)
|
||||||
|
{
|
||||||
|
char *relationName = get_rel_name(relationId);
|
||||||
|
|
||||||
|
Assert(relationName != NULL);
|
||||||
|
ereport(ERROR, (errmsg("cannot distribute relation \"%s\" because it has "
|
||||||
|
"triggers ", relationName),
|
||||||
|
errdetail("Citus does not support distributing tables with "
|
||||||
|
"triggers."),
|
||||||
|
errhint("Drop all the triggers on \"%s\" and retry.",
|
||||||
|
relationName)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* LookupDistributionMethod maps the oids of citus.distribution_type enum
|
* LookupDistributionMethod maps the oids of citus.distribution_type enum
|
||||||
* values to pg_dist_partition.partmethod values.
|
* values to pg_dist_partition.partmethod values.
|
||||||
|
@ -1176,7 +1203,7 @@ CreateTruncateTrigger(Oid relationId)
|
||||||
CreateTrigStmt *trigger = makeNode(CreateTrigStmt);
|
CreateTrigStmt *trigger = makeNode(CreateTrigStmt);
|
||||||
trigger->trigname = triggerName->data;
|
trigger->trigname = triggerName->data;
|
||||||
trigger->relation = NULL;
|
trigger->relation = NULL;
|
||||||
trigger->funcname = SystemFuncName("citus_truncate_trigger");
|
trigger->funcname = SystemFuncName(CITUS_TRUNCATE_TRIGGER_NAME);
|
||||||
trigger->args = NIL;
|
trigger->args = NIL;
|
||||||
trigger->row = false;
|
trigger->row = false;
|
||||||
trigger->timing = TRIGGER_TYPE_AFTER;
|
trigger->timing = TRIGGER_TYPE_AFTER;
|
||||||
|
|
|
@ -0,0 +1,186 @@
|
||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
* 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)));
|
||||||
|
}
|
|
@ -208,6 +208,13 @@ multi_ProcessUtility(PlannedStmt *pstmt,
|
||||||
parsetree = ProcessCreateSubscriptionStmt(createSubStmt);
|
parsetree = ProcessCreateSubscriptionStmt(createSubStmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IsA(parsetree, CreateTrigStmt))
|
||||||
|
{
|
||||||
|
CreateTrigStmt *createTriggerStmt = (CreateTrigStmt *) parsetree;
|
||||||
|
|
||||||
|
ErrorIfUnsupportedCreateTriggerCommand(createTriggerStmt);
|
||||||
|
}
|
||||||
|
|
||||||
if (IsA(parsetree, CallStmt))
|
if (IsA(parsetree, CallStmt))
|
||||||
{
|
{
|
||||||
CallStmt *callStmt = (CallStmt *) parsetree;
|
CallStmt *callStmt = (CallStmt *) parsetree;
|
||||||
|
|
|
@ -100,6 +100,7 @@
|
||||||
/* Pretty flags */
|
/* Pretty flags */
|
||||||
#define PRETTYFLAG_PAREN 0x0001
|
#define PRETTYFLAG_PAREN 0x0001
|
||||||
#define PRETTYFLAG_INDENT 0x0002
|
#define PRETTYFLAG_INDENT 0x0002
|
||||||
|
#define PRETTYFLAG_SCHEMA 0x0004
|
||||||
|
|
||||||
/* Default line length for pretty-print wrapping: 0 means wrap always */
|
/* Default line length for pretty-print wrapping: 0 means wrap always */
|
||||||
#define WRAP_COLUMN_DEFAULT 0
|
#define WRAP_COLUMN_DEFAULT 0
|
||||||
|
@ -107,6 +108,7 @@
|
||||||
/* macros to test if pretty action needed */
|
/* macros to test if pretty action needed */
|
||||||
#define PRETTY_PAREN(context) ((context)->prettyFlags & PRETTYFLAG_PAREN)
|
#define PRETTY_PAREN(context) ((context)->prettyFlags & PRETTYFLAG_PAREN)
|
||||||
#define PRETTY_INDENT(context) ((context)->prettyFlags & PRETTYFLAG_INDENT)
|
#define PRETTY_INDENT(context) ((context)->prettyFlags & PRETTYFLAG_INDENT)
|
||||||
|
#define PRETTY_SCHEMA(context) ((context)->prettyFlags & PRETTYFLAG_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
/* ----------
|
/* ----------
|
||||||
|
@ -424,6 +426,8 @@ static void get_from_clause_coldeflist(RangeTblFunction *rtfunc,
|
||||||
deparse_context *context);
|
deparse_context *context);
|
||||||
static void get_tablesample_def(TableSampleClause *tablesample,
|
static void get_tablesample_def(TableSampleClause *tablesample,
|
||||||
deparse_context *context);
|
deparse_context *context);
|
||||||
|
static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
|
||||||
|
static void set_simple_column_names(deparse_namespace *dpns);
|
||||||
static void get_opclass_name(Oid opclass, Oid actual_datatype,
|
static void get_opclass_name(Oid opclass, Oid actual_datatype,
|
||||||
StringInfo buf);
|
StringInfo buf);
|
||||||
static Node *processIndirection(Node *node, deparse_context *context);
|
static Node *processIndirection(Node *node, deparse_context *context);
|
||||||
|
@ -7481,6 +7485,305 @@ get_tablesample_def(TableSampleClause *tablesample, deparse_context *context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
pg_get_triggerdef_command(Oid triggerId)
|
||||||
|
{
|
||||||
|
Assert(OidIsValid(triggerId));
|
||||||
|
|
||||||
|
/* no need to have pretty SQL command */
|
||||||
|
bool prettyOutput = false;
|
||||||
|
return pg_get_triggerdef_worker(triggerId, prettyOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
pg_get_triggerdef_worker(Oid trigid, bool pretty)
|
||||||
|
{
|
||||||
|
HeapTuple ht_trig;
|
||||||
|
Form_pg_trigger trigrec;
|
||||||
|
StringInfoData buf;
|
||||||
|
Relation tgrel;
|
||||||
|
ScanKeyData skey[1];
|
||||||
|
SysScanDesc tgscan;
|
||||||
|
int findx = 0;
|
||||||
|
char *tgname;
|
||||||
|
char *tgoldtable;
|
||||||
|
char *tgnewtable;
|
||||||
|
Oid argtypes[1]; /* dummy */
|
||||||
|
Datum value;
|
||||||
|
bool isnull;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fetch the pg_trigger tuple by the Oid of the trigger
|
||||||
|
*/
|
||||||
|
tgrel = heap_open(TriggerRelationId, AccessShareLock);
|
||||||
|
|
||||||
|
ScanKeyInit(&skey[0],
|
||||||
|
ObjectIdAttributeNumber,
|
||||||
|
BTEqualStrategyNumber, F_OIDEQ,
|
||||||
|
ObjectIdGetDatum(trigid));
|
||||||
|
|
||||||
|
tgscan = systable_beginscan(tgrel, TriggerOidIndexId, true,
|
||||||
|
NULL, 1, skey);
|
||||||
|
|
||||||
|
ht_trig = systable_getnext(tgscan);
|
||||||
|
|
||||||
|
if (!HeapTupleIsValid(ht_trig))
|
||||||
|
{
|
||||||
|
systable_endscan(tgscan);
|
||||||
|
heap_close(tgrel, AccessShareLock);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
trigrec = (Form_pg_trigger) GETSTRUCT(ht_trig);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Start the trigger definition. Note that the trigger's name should never
|
||||||
|
* be schema-qualified, but the trigger rel's name may be.
|
||||||
|
*/
|
||||||
|
initStringInfo(&buf);
|
||||||
|
|
||||||
|
tgname = NameStr(trigrec->tgname);
|
||||||
|
appendStringInfo(&buf, "CREATE %sTRIGGER %s ",
|
||||||
|
OidIsValid(trigrec->tgconstraint) ? "CONSTRAINT " : "",
|
||||||
|
quote_identifier(tgname));
|
||||||
|
|
||||||
|
if (TRIGGER_FOR_BEFORE(trigrec->tgtype))
|
||||||
|
appendStringInfoString(&buf, "BEFORE");
|
||||||
|
else if (TRIGGER_FOR_AFTER(trigrec->tgtype))
|
||||||
|
appendStringInfoString(&buf, "AFTER");
|
||||||
|
else if (TRIGGER_FOR_INSTEAD(trigrec->tgtype))
|
||||||
|
appendStringInfoString(&buf, "INSTEAD OF");
|
||||||
|
else
|
||||||
|
elog(ERROR, "unexpected tgtype value: %d", trigrec->tgtype);
|
||||||
|
|
||||||
|
if (TRIGGER_FOR_INSERT(trigrec->tgtype))
|
||||||
|
{
|
||||||
|
appendStringInfoString(&buf, " INSERT");
|
||||||
|
findx++;
|
||||||
|
}
|
||||||
|
if (TRIGGER_FOR_DELETE(trigrec->tgtype))
|
||||||
|
{
|
||||||
|
if (findx > 0)
|
||||||
|
appendStringInfoString(&buf, " OR DELETE");
|
||||||
|
else
|
||||||
|
appendStringInfoString(&buf, " DELETE");
|
||||||
|
findx++;
|
||||||
|
}
|
||||||
|
if (TRIGGER_FOR_UPDATE(trigrec->tgtype))
|
||||||
|
{
|
||||||
|
if (findx > 0)
|
||||||
|
appendStringInfoString(&buf, " OR UPDATE");
|
||||||
|
else
|
||||||
|
appendStringInfoString(&buf, " UPDATE");
|
||||||
|
findx++;
|
||||||
|
/* tgattr is first var-width field, so OK to access directly */
|
||||||
|
if (trigrec->tgattr.dim1 > 0)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
appendStringInfoString(&buf, " OF ");
|
||||||
|
for (i = 0; i < trigrec->tgattr.dim1; i++)
|
||||||
|
{
|
||||||
|
char *attname;
|
||||||
|
|
||||||
|
if (i > 0)
|
||||||
|
appendStringInfoString(&buf, ", ");
|
||||||
|
attname = get_attname(trigrec->tgrelid,
|
||||||
|
trigrec->tgattr.values[i], false);
|
||||||
|
appendStringInfoString(&buf, quote_identifier(attname));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (TRIGGER_FOR_TRUNCATE(trigrec->tgtype))
|
||||||
|
{
|
||||||
|
if (findx > 0)
|
||||||
|
appendStringInfoString(&buf, " OR TRUNCATE");
|
||||||
|
else
|
||||||
|
appendStringInfoString(&buf, " TRUNCATE");
|
||||||
|
findx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In non-pretty mode, always schema-qualify the target table name for
|
||||||
|
* safety. In pretty mode, schema-qualify only if not visible.
|
||||||
|
*/
|
||||||
|
appendStringInfo(&buf, " ON %s ",
|
||||||
|
pretty ?
|
||||||
|
generate_relation_name(trigrec->tgrelid, NIL) :
|
||||||
|
generate_qualified_relation_name(trigrec->tgrelid));
|
||||||
|
|
||||||
|
if (OidIsValid(trigrec->tgconstraint))
|
||||||
|
{
|
||||||
|
if (OidIsValid(trigrec->tgconstrrelid))
|
||||||
|
appendStringInfo(&buf, "FROM %s ",
|
||||||
|
generate_relation_name(trigrec->tgconstrrelid, NIL));
|
||||||
|
if (!trigrec->tgdeferrable)
|
||||||
|
appendStringInfoString(&buf, "NOT ");
|
||||||
|
appendStringInfoString(&buf, "DEFERRABLE INITIALLY ");
|
||||||
|
if (trigrec->tginitdeferred)
|
||||||
|
appendStringInfoString(&buf, "DEFERRED ");
|
||||||
|
else
|
||||||
|
appendStringInfoString(&buf, "IMMEDIATE ");
|
||||||
|
}
|
||||||
|
|
||||||
|
value = fastgetattr(ht_trig, Anum_pg_trigger_tgoldtable,
|
||||||
|
tgrel->rd_att, &isnull);
|
||||||
|
if (!isnull)
|
||||||
|
tgoldtable = NameStr(*DatumGetName(value));
|
||||||
|
else
|
||||||
|
tgoldtable = NULL;
|
||||||
|
value = fastgetattr(ht_trig, Anum_pg_trigger_tgnewtable,
|
||||||
|
tgrel->rd_att, &isnull);
|
||||||
|
if (!isnull)
|
||||||
|
tgnewtable = NameStr(*DatumGetName(value));
|
||||||
|
else
|
||||||
|
tgnewtable = NULL;
|
||||||
|
if (tgoldtable != NULL || tgnewtable != NULL)
|
||||||
|
{
|
||||||
|
appendStringInfoString(&buf, "REFERENCING ");
|
||||||
|
if (tgoldtable != NULL)
|
||||||
|
appendStringInfo(&buf, "OLD TABLE AS %s ",
|
||||||
|
quote_identifier(tgoldtable));
|
||||||
|
if (tgnewtable != NULL)
|
||||||
|
appendStringInfo(&buf, "NEW TABLE AS %s ",
|
||||||
|
quote_identifier(tgnewtable));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TRIGGER_FOR_ROW(trigrec->tgtype))
|
||||||
|
appendStringInfoString(&buf, "FOR EACH ROW ");
|
||||||
|
else
|
||||||
|
appendStringInfoString(&buf, "FOR EACH STATEMENT ");
|
||||||
|
|
||||||
|
/* If the trigger has a WHEN qualification, add that */
|
||||||
|
value = fastgetattr(ht_trig, Anum_pg_trigger_tgqual,
|
||||||
|
tgrel->rd_att, &isnull);
|
||||||
|
if (!isnull)
|
||||||
|
{
|
||||||
|
Node *qual;
|
||||||
|
char relkind;
|
||||||
|
deparse_context context;
|
||||||
|
deparse_namespace dpns;
|
||||||
|
RangeTblEntry *oldrte;
|
||||||
|
RangeTblEntry *newrte;
|
||||||
|
|
||||||
|
appendStringInfoString(&buf, "WHEN (");
|
||||||
|
|
||||||
|
qual = stringToNode(TextDatumGetCString(value));
|
||||||
|
|
||||||
|
relkind = get_rel_relkind(trigrec->tgrelid);
|
||||||
|
|
||||||
|
/* Build minimal OLD and NEW RTEs for the rel */
|
||||||
|
oldrte = makeNode(RangeTblEntry);
|
||||||
|
oldrte->rtekind = RTE_RELATION;
|
||||||
|
oldrte->relid = trigrec->tgrelid;
|
||||||
|
oldrte->relkind = relkind;
|
||||||
|
oldrte->alias = makeAlias("old", NIL);
|
||||||
|
oldrte->eref = oldrte->alias;
|
||||||
|
oldrte->lateral = false;
|
||||||
|
oldrte->inh = false;
|
||||||
|
oldrte->inFromCl = true;
|
||||||
|
|
||||||
|
newrte = makeNode(RangeTblEntry);
|
||||||
|
newrte->rtekind = RTE_RELATION;
|
||||||
|
newrte->relid = trigrec->tgrelid;
|
||||||
|
newrte->relkind = relkind;
|
||||||
|
newrte->alias = makeAlias("new", NIL);
|
||||||
|
newrte->eref = newrte->alias;
|
||||||
|
newrte->lateral = false;
|
||||||
|
newrte->inh = false;
|
||||||
|
newrte->inFromCl = true;
|
||||||
|
|
||||||
|
/* Build two-element rtable */
|
||||||
|
memset(&dpns, 0, sizeof(dpns));
|
||||||
|
dpns.rtable = list_make2(oldrte, newrte);
|
||||||
|
dpns.ctes = NIL;
|
||||||
|
set_rtable_names(&dpns, NIL, NULL);
|
||||||
|
set_simple_column_names(&dpns);
|
||||||
|
|
||||||
|
/* Set up context with one-deep namespace stack */
|
||||||
|
context.buf = &buf;
|
||||||
|
context.namespaces = list_make1(&dpns);
|
||||||
|
context.windowClause = NIL;
|
||||||
|
context.windowTList = NIL;
|
||||||
|
context.varprefix = true;
|
||||||
|
context.prettyFlags = pretty ? (PRETTYFLAG_PAREN | PRETTYFLAG_INDENT | PRETTYFLAG_SCHEMA) : PRETTYFLAG_INDENT;
|
||||||
|
context.wrapColumn = WRAP_COLUMN_DEFAULT;
|
||||||
|
context.indentLevel = PRETTYINDENT_STD;
|
||||||
|
context.special_exprkind = EXPR_KIND_NONE;
|
||||||
|
|
||||||
|
get_rule_expr(qual, &context, false);
|
||||||
|
|
||||||
|
appendStringInfoString(&buf, ") ");
|
||||||
|
}
|
||||||
|
|
||||||
|
appendStringInfo(&buf, "EXECUTE PROCEDURE %s(",
|
||||||
|
generate_function_name(trigrec->tgfoid, 0,
|
||||||
|
NIL, argtypes,
|
||||||
|
false, NULL, EXPR_KIND_NONE));
|
||||||
|
|
||||||
|
if (trigrec->tgnargs > 0)
|
||||||
|
{
|
||||||
|
char *p;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
value = fastgetattr(ht_trig, Anum_pg_trigger_tgargs,
|
||||||
|
tgrel->rd_att, &isnull);
|
||||||
|
if (isnull)
|
||||||
|
elog(ERROR, "tgargs is null for trigger %u", trigid);
|
||||||
|
p = (char *) VARDATA_ANY(DatumGetByteaPP(value));
|
||||||
|
for (i = 0; i < trigrec->tgnargs; i++)
|
||||||
|
{
|
||||||
|
if (i > 0)
|
||||||
|
appendStringInfoString(&buf, ", ");
|
||||||
|
simple_quote_literal(&buf, p);
|
||||||
|
/* advance p to next string embedded in tgargs */
|
||||||
|
while (*p)
|
||||||
|
p++;
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We deliberately do not put semi-colon at end */
|
||||||
|
appendStringInfoChar(&buf, ')');
|
||||||
|
|
||||||
|
/* Clean up */
|
||||||
|
systable_endscan(tgscan);
|
||||||
|
|
||||||
|
heap_close(tgrel, AccessShareLock);
|
||||||
|
|
||||||
|
return buf.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* set_simple_column_names: fill in column aliases for non-query situations
|
||||||
|
*
|
||||||
|
* This handles EXPLAIN and cases where we only have relation RTEs. Without
|
||||||
|
* a join tree, we can't do anything smart about join RTEs, but we don't
|
||||||
|
* need to (note that EXPLAIN should never see join alias Vars anyway).
|
||||||
|
* If we do hit a join RTE we'll just process it like a non-table base RTE.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
set_simple_column_names(deparse_namespace *dpns)
|
||||||
|
{
|
||||||
|
ListCell *lc;
|
||||||
|
ListCell *lc2;
|
||||||
|
|
||||||
|
/* Initialize dpns->rtable_columns to contain zeroed structs */
|
||||||
|
dpns->rtable_columns = NIL;
|
||||||
|
while (list_length(dpns->rtable_columns) < list_length(dpns->rtable))
|
||||||
|
dpns->rtable_columns = lappend(dpns->rtable_columns,
|
||||||
|
palloc0(sizeof(deparse_columns)));
|
||||||
|
|
||||||
|
/* Assign unique column aliases within each RTE */
|
||||||
|
forboth(lc, dpns->rtable, lc2, dpns->rtable_columns)
|
||||||
|
{
|
||||||
|
RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
|
||||||
|
deparse_columns *colinfo = (deparse_columns *) lfirst(lc2);
|
||||||
|
|
||||||
|
set_relation_column_names(dpns, rte, colinfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* get_opclass_name - fetch name of an index operator class
|
* get_opclass_name - fetch name of an index operator class
|
||||||
*
|
*
|
||||||
|
|
|
@ -100,6 +100,7 @@
|
||||||
/* Pretty flags */
|
/* Pretty flags */
|
||||||
#define PRETTYFLAG_PAREN 0x0001
|
#define PRETTYFLAG_PAREN 0x0001
|
||||||
#define PRETTYFLAG_INDENT 0x0002
|
#define PRETTYFLAG_INDENT 0x0002
|
||||||
|
#define PRETTYFLAG_SCHEMA 0x0004
|
||||||
|
|
||||||
/* Default line length for pretty-print wrapping: 0 means wrap always */
|
/* Default line length for pretty-print wrapping: 0 means wrap always */
|
||||||
#define WRAP_COLUMN_DEFAULT 0
|
#define WRAP_COLUMN_DEFAULT 0
|
||||||
|
@ -107,6 +108,7 @@
|
||||||
/* macros to test if pretty action needed */
|
/* macros to test if pretty action needed */
|
||||||
#define PRETTY_PAREN(context) ((context)->prettyFlags & PRETTYFLAG_PAREN)
|
#define PRETTY_PAREN(context) ((context)->prettyFlags & PRETTYFLAG_PAREN)
|
||||||
#define PRETTY_INDENT(context) ((context)->prettyFlags & PRETTYFLAG_INDENT)
|
#define PRETTY_INDENT(context) ((context)->prettyFlags & PRETTYFLAG_INDENT)
|
||||||
|
#define PRETTY_SCHEMA(context) ((context)->prettyFlags & PRETTYFLAG_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
/* ----------
|
/* ----------
|
||||||
|
@ -424,6 +426,8 @@ static void get_from_clause_coldeflist(RangeTblFunction *rtfunc,
|
||||||
deparse_context *context);
|
deparse_context *context);
|
||||||
static void get_tablesample_def(TableSampleClause *tablesample,
|
static void get_tablesample_def(TableSampleClause *tablesample,
|
||||||
deparse_context *context);
|
deparse_context *context);
|
||||||
|
static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
|
||||||
|
static void set_simple_column_names(deparse_namespace *dpns);
|
||||||
static void get_opclass_name(Oid opclass, Oid actual_datatype,
|
static void get_opclass_name(Oid opclass, Oid actual_datatype,
|
||||||
StringInfo buf);
|
StringInfo buf);
|
||||||
static Node *processIndirection(Node *node, deparse_context *context);
|
static Node *processIndirection(Node *node, deparse_context *context);
|
||||||
|
@ -7481,6 +7485,307 @@ get_tablesample_def(TableSampleClause *tablesample, deparse_context *context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
pg_get_triggerdef_command(Oid triggerId)
|
||||||
|
{
|
||||||
|
Assert(OidIsValid(triggerId));
|
||||||
|
|
||||||
|
/* no need to have pretty SQL command */
|
||||||
|
bool prettyOutput = false;
|
||||||
|
return pg_get_triggerdef_worker(triggerId, prettyOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
pg_get_triggerdef_worker(Oid trigid, bool pretty)
|
||||||
|
{
|
||||||
|
HeapTuple ht_trig;
|
||||||
|
Form_pg_trigger trigrec;
|
||||||
|
StringInfoData buf;
|
||||||
|
Relation tgrel;
|
||||||
|
ScanKeyData skey[1];
|
||||||
|
SysScanDesc tgscan;
|
||||||
|
int findx = 0;
|
||||||
|
char *tgname;
|
||||||
|
char *tgoldtable;
|
||||||
|
char *tgnewtable;
|
||||||
|
Oid argtypes[1]; /* dummy */
|
||||||
|
Datum value;
|
||||||
|
bool isnull;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fetch the pg_trigger tuple by the Oid of the trigger
|
||||||
|
*/
|
||||||
|
tgrel = table_open(TriggerRelationId, AccessShareLock);
|
||||||
|
|
||||||
|
ScanKeyInit(&skey[0],
|
||||||
|
Anum_pg_trigger_oid,
|
||||||
|
BTEqualStrategyNumber, F_OIDEQ,
|
||||||
|
ObjectIdGetDatum(trigid));
|
||||||
|
|
||||||
|
tgscan = systable_beginscan(tgrel, TriggerOidIndexId, true,
|
||||||
|
NULL, 1, skey);
|
||||||
|
|
||||||
|
ht_trig = systable_getnext(tgscan);
|
||||||
|
|
||||||
|
if (!HeapTupleIsValid(ht_trig))
|
||||||
|
{
|
||||||
|
systable_endscan(tgscan);
|
||||||
|
table_close(tgrel, AccessShareLock);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
trigrec = (Form_pg_trigger) GETSTRUCT(ht_trig);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Start the trigger definition. Note that the trigger's name should never
|
||||||
|
* be schema-qualified, but the trigger rel's name may be.
|
||||||
|
*/
|
||||||
|
initStringInfo(&buf);
|
||||||
|
|
||||||
|
tgname = NameStr(trigrec->tgname);
|
||||||
|
appendStringInfo(&buf, "CREATE %sTRIGGER %s ",
|
||||||
|
OidIsValid(trigrec->tgconstraint) ? "CONSTRAINT " : "",
|
||||||
|
quote_identifier(tgname));
|
||||||
|
|
||||||
|
if (TRIGGER_FOR_BEFORE(trigrec->tgtype))
|
||||||
|
appendStringInfoString(&buf, "BEFORE");
|
||||||
|
else if (TRIGGER_FOR_AFTER(trigrec->tgtype))
|
||||||
|
appendStringInfoString(&buf, "AFTER");
|
||||||
|
else if (TRIGGER_FOR_INSTEAD(trigrec->tgtype))
|
||||||
|
appendStringInfoString(&buf, "INSTEAD OF");
|
||||||
|
else
|
||||||
|
elog(ERROR, "unexpected tgtype value: %d", trigrec->tgtype);
|
||||||
|
|
||||||
|
if (TRIGGER_FOR_INSERT(trigrec->tgtype))
|
||||||
|
{
|
||||||
|
appendStringInfoString(&buf, " INSERT");
|
||||||
|
findx++;
|
||||||
|
}
|
||||||
|
if (TRIGGER_FOR_DELETE(trigrec->tgtype))
|
||||||
|
{
|
||||||
|
if (findx > 0)
|
||||||
|
appendStringInfoString(&buf, " OR DELETE");
|
||||||
|
else
|
||||||
|
appendStringInfoString(&buf, " DELETE");
|
||||||
|
findx++;
|
||||||
|
}
|
||||||
|
if (TRIGGER_FOR_UPDATE(trigrec->tgtype))
|
||||||
|
{
|
||||||
|
if (findx > 0)
|
||||||
|
appendStringInfoString(&buf, " OR UPDATE");
|
||||||
|
else
|
||||||
|
appendStringInfoString(&buf, " UPDATE");
|
||||||
|
findx++;
|
||||||
|
/* tgattr is first var-width field, so OK to access directly */
|
||||||
|
if (trigrec->tgattr.dim1 > 0)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
appendStringInfoString(&buf, " OF ");
|
||||||
|
for (i = 0; i < trigrec->tgattr.dim1; i++)
|
||||||
|
{
|
||||||
|
char *attname;
|
||||||
|
|
||||||
|
if (i > 0)
|
||||||
|
appendStringInfoString(&buf, ", ");
|
||||||
|
attname = get_attname(trigrec->tgrelid,
|
||||||
|
trigrec->tgattr.values[i], false);
|
||||||
|
appendStringInfoString(&buf, quote_identifier(attname));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (TRIGGER_FOR_TRUNCATE(trigrec->tgtype))
|
||||||
|
{
|
||||||
|
if (findx > 0)
|
||||||
|
appendStringInfoString(&buf, " OR TRUNCATE");
|
||||||
|
else
|
||||||
|
appendStringInfoString(&buf, " TRUNCATE");
|
||||||
|
findx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In non-pretty mode, always schema-qualify the target table name for
|
||||||
|
* safety. In pretty mode, schema-qualify only if not visible.
|
||||||
|
*/
|
||||||
|
appendStringInfo(&buf, " ON %s ",
|
||||||
|
pretty ?
|
||||||
|
generate_relation_name(trigrec->tgrelid, NIL) :
|
||||||
|
generate_qualified_relation_name(trigrec->tgrelid));
|
||||||
|
|
||||||
|
if (OidIsValid(trigrec->tgconstraint))
|
||||||
|
{
|
||||||
|
if (OidIsValid(trigrec->tgconstrrelid))
|
||||||
|
appendStringInfo(&buf, "FROM %s ",
|
||||||
|
generate_relation_name(trigrec->tgconstrrelid, NIL));
|
||||||
|
if (!trigrec->tgdeferrable)
|
||||||
|
appendStringInfoString(&buf, "NOT ");
|
||||||
|
appendStringInfoString(&buf, "DEFERRABLE INITIALLY ");
|
||||||
|
if (trigrec->tginitdeferred)
|
||||||
|
appendStringInfoString(&buf, "DEFERRED ");
|
||||||
|
else
|
||||||
|
appendStringInfoString(&buf, "IMMEDIATE ");
|
||||||
|
}
|
||||||
|
|
||||||
|
value = fastgetattr(ht_trig, Anum_pg_trigger_tgoldtable,
|
||||||
|
tgrel->rd_att, &isnull);
|
||||||
|
if (!isnull)
|
||||||
|
tgoldtable = NameStr(*DatumGetName(value));
|
||||||
|
else
|
||||||
|
tgoldtable = NULL;
|
||||||
|
value = fastgetattr(ht_trig, Anum_pg_trigger_tgnewtable,
|
||||||
|
tgrel->rd_att, &isnull);
|
||||||
|
if (!isnull)
|
||||||
|
tgnewtable = NameStr(*DatumGetName(value));
|
||||||
|
else
|
||||||
|
tgnewtable = NULL;
|
||||||
|
if (tgoldtable != NULL || tgnewtable != NULL)
|
||||||
|
{
|
||||||
|
appendStringInfoString(&buf, "REFERENCING ");
|
||||||
|
if (tgoldtable != NULL)
|
||||||
|
appendStringInfo(&buf, "OLD TABLE AS %s ",
|
||||||
|
quote_identifier(tgoldtable));
|
||||||
|
if (tgnewtable != NULL)
|
||||||
|
appendStringInfo(&buf, "NEW TABLE AS %s ",
|
||||||
|
quote_identifier(tgnewtable));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TRIGGER_FOR_ROW(trigrec->tgtype))
|
||||||
|
appendStringInfoString(&buf, "FOR EACH ROW ");
|
||||||
|
else
|
||||||
|
appendStringInfoString(&buf, "FOR EACH STATEMENT ");
|
||||||
|
|
||||||
|
/* If the trigger has a WHEN qualification, add that */
|
||||||
|
value = fastgetattr(ht_trig, Anum_pg_trigger_tgqual,
|
||||||
|
tgrel->rd_att, &isnull);
|
||||||
|
if (!isnull)
|
||||||
|
{
|
||||||
|
Node *qual;
|
||||||
|
char relkind;
|
||||||
|
deparse_context context;
|
||||||
|
deparse_namespace dpns;
|
||||||
|
RangeTblEntry *oldrte;
|
||||||
|
RangeTblEntry *newrte;
|
||||||
|
|
||||||
|
appendStringInfoString(&buf, "WHEN (");
|
||||||
|
|
||||||
|
qual = stringToNode(TextDatumGetCString(value));
|
||||||
|
|
||||||
|
relkind = get_rel_relkind(trigrec->tgrelid);
|
||||||
|
|
||||||
|
/* Build minimal OLD and NEW RTEs for the rel */
|
||||||
|
oldrte = makeNode(RangeTblEntry);
|
||||||
|
oldrte->rtekind = RTE_RELATION;
|
||||||
|
oldrte->relid = trigrec->tgrelid;
|
||||||
|
oldrte->relkind = relkind;
|
||||||
|
oldrte->rellockmode = AccessShareLock;
|
||||||
|
oldrte->alias = makeAlias("old", NIL);
|
||||||
|
oldrte->eref = oldrte->alias;
|
||||||
|
oldrte->lateral = false;
|
||||||
|
oldrte->inh = false;
|
||||||
|
oldrte->inFromCl = true;
|
||||||
|
|
||||||
|
newrte = makeNode(RangeTblEntry);
|
||||||
|
newrte->rtekind = RTE_RELATION;
|
||||||
|
newrte->relid = trigrec->tgrelid;
|
||||||
|
newrte->relkind = relkind;
|
||||||
|
newrte->rellockmode = AccessShareLock;
|
||||||
|
newrte->alias = makeAlias("new", NIL);
|
||||||
|
newrte->eref = newrte->alias;
|
||||||
|
newrte->lateral = false;
|
||||||
|
newrte->inh = false;
|
||||||
|
newrte->inFromCl = true;
|
||||||
|
|
||||||
|
/* Build two-element rtable */
|
||||||
|
memset(&dpns, 0, sizeof(dpns));
|
||||||
|
dpns.rtable = list_make2(oldrte, newrte);
|
||||||
|
dpns.ctes = NIL;
|
||||||
|
set_rtable_names(&dpns, NIL, NULL);
|
||||||
|
set_simple_column_names(&dpns);
|
||||||
|
|
||||||
|
/* Set up context with one-deep namespace stack */
|
||||||
|
context.buf = &buf;
|
||||||
|
context.namespaces = list_make1(&dpns);
|
||||||
|
context.windowClause = NIL;
|
||||||
|
context.windowTList = NIL;
|
||||||
|
context.varprefix = true;
|
||||||
|
context.prettyFlags = pretty ? (PRETTYFLAG_PAREN | PRETTYFLAG_INDENT | PRETTYFLAG_SCHEMA) : PRETTYFLAG_INDENT;
|
||||||
|
context.wrapColumn = WRAP_COLUMN_DEFAULT;
|
||||||
|
context.indentLevel = PRETTYINDENT_STD;
|
||||||
|
context.special_exprkind = EXPR_KIND_NONE;
|
||||||
|
|
||||||
|
get_rule_expr(qual, &context, false);
|
||||||
|
|
||||||
|
appendStringInfoString(&buf, ") ");
|
||||||
|
}
|
||||||
|
|
||||||
|
appendStringInfo(&buf, "EXECUTE FUNCTION %s(",
|
||||||
|
generate_function_name(trigrec->tgfoid, 0,
|
||||||
|
NIL, argtypes,
|
||||||
|
false, NULL, EXPR_KIND_NONE));
|
||||||
|
|
||||||
|
if (trigrec->tgnargs > 0)
|
||||||
|
{
|
||||||
|
char *p;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
value = fastgetattr(ht_trig, Anum_pg_trigger_tgargs,
|
||||||
|
tgrel->rd_att, &isnull);
|
||||||
|
if (isnull)
|
||||||
|
elog(ERROR, "tgargs is null for trigger %u", trigid);
|
||||||
|
p = (char *) VARDATA_ANY(DatumGetByteaPP(value));
|
||||||
|
for (i = 0; i < trigrec->tgnargs; i++)
|
||||||
|
{
|
||||||
|
if (i > 0)
|
||||||
|
appendStringInfoString(&buf, ", ");
|
||||||
|
simple_quote_literal(&buf, p);
|
||||||
|
/* advance p to next string embedded in tgargs */
|
||||||
|
while (*p)
|
||||||
|
p++;
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We deliberately do not put semi-colon at end */
|
||||||
|
appendStringInfoChar(&buf, ')');
|
||||||
|
|
||||||
|
/* Clean up */
|
||||||
|
systable_endscan(tgscan);
|
||||||
|
|
||||||
|
table_close(tgrel, AccessShareLock);
|
||||||
|
|
||||||
|
return buf.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* set_simple_column_names: fill in column aliases for non-query situations
|
||||||
|
*
|
||||||
|
* This handles EXPLAIN and cases where we only have relation RTEs. Without
|
||||||
|
* a join tree, we can't do anything smart about join RTEs, but we don't
|
||||||
|
* need to (note that EXPLAIN should never see join alias Vars anyway).
|
||||||
|
* If we do hit a join RTE we'll just process it like a non-table base RTE.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
set_simple_column_names(deparse_namespace *dpns)
|
||||||
|
{
|
||||||
|
ListCell *lc;
|
||||||
|
ListCell *lc2;
|
||||||
|
|
||||||
|
/* Initialize dpns->rtable_columns to contain zeroed structs */
|
||||||
|
dpns->rtable_columns = NIL;
|
||||||
|
while (list_length(dpns->rtable_columns) < list_length(dpns->rtable))
|
||||||
|
dpns->rtable_columns = lappend(dpns->rtable_columns,
|
||||||
|
palloc0(sizeof(deparse_columns)));
|
||||||
|
|
||||||
|
/* Assign unique column aliases within each RTE */
|
||||||
|
forboth(lc, dpns->rtable, lc2, dpns->rtable_columns)
|
||||||
|
{
|
||||||
|
RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
|
||||||
|
deparse_columns *colinfo = (deparse_columns *) lfirst(lc2);
|
||||||
|
|
||||||
|
set_relation_column_names(dpns, rte, colinfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* get_opclass_name - fetch name of an index operator class
|
* get_opclass_name - fetch name of an index operator class
|
||||||
*
|
*
|
||||||
|
|
|
@ -522,7 +522,7 @@ ResolveRelationId(text *relationName, bool missingOk)
|
||||||
* DEFAULT clauses for columns getting their default values from a sequence.
|
* DEFAULT clauses for columns getting their default values from a sequence.
|
||||||
* These DDL commands are all palloced; and include the table's schema
|
* These DDL commands are all palloced; and include the table's schema
|
||||||
* definition, optional column storage and statistics definitions, and index
|
* definition, optional column storage and statistics definitions, and index
|
||||||
* and constraint definitions.
|
* constraint and trigger definitions.
|
||||||
*/
|
*/
|
||||||
List *
|
List *
|
||||||
GetTableDDLEvents(Oid relationId, bool includeSequenceDefaults)
|
GetTableDDLEvents(Oid relationId, bool includeSequenceDefaults)
|
||||||
|
@ -542,6 +542,9 @@ GetTableDDLEvents(Oid relationId, bool includeSequenceDefaults)
|
||||||
List *policyCommands = CreatePolicyCommands(relationId);
|
List *policyCommands = CreatePolicyCommands(relationId);
|
||||||
tableDDLEventList = list_concat(tableDDLEventList, policyCommands);
|
tableDDLEventList = list_concat(tableDDLEventList, policyCommands);
|
||||||
|
|
||||||
|
List *triggerCommands = GetExplicitTriggerCommandList(relationId);
|
||||||
|
tableDDLEventList = list_concat(tableDDLEventList, triggerCommands);
|
||||||
|
|
||||||
return tableDDLEventList;
|
return tableDDLEventList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@ extern void pg_get_query_def(Query *query, StringInfo buffer);
|
||||||
char * pg_get_rule_expr(Node *expression);
|
char * pg_get_rule_expr(Node *expression);
|
||||||
extern void deparse_shard_query(Query *query, Oid distrelid, int64 shardid,
|
extern void deparse_shard_query(Query *query, Oid distrelid, int64 shardid,
|
||||||
StringInfo buffer);
|
StringInfo buffer);
|
||||||
|
extern char * pg_get_triggerdef_command(Oid triggerId);
|
||||||
extern char * generate_relation_name(Oid relid, List *namespaces);
|
extern char * generate_relation_name(Oid relid, List *namespaces);
|
||||||
extern char * generate_qualified_relation_name(Oid relid);
|
extern char * generate_qualified_relation_name(Oid relid);
|
||||||
extern char * generate_operator_name(Oid operid, Oid arg1, Oid arg2);
|
extern char * generate_operator_name(Oid operid, Oid arg1, Oid arg2);
|
||||||
|
|
|
@ -44,6 +44,8 @@ typedef struct DistributeObjectOps
|
||||||
ObjectAddress (*address)(Node *, bool);
|
ObjectAddress (*address)(Node *, bool);
|
||||||
} DistributeObjectOps;
|
} DistributeObjectOps;
|
||||||
|
|
||||||
|
#define CITUS_TRUNCATE_TRIGGER_NAME "citus_truncate_trigger"
|
||||||
|
|
||||||
const DistributeObjectOps * GetDistributeObjectOps(Node *node);
|
const DistributeObjectOps * GetDistributeObjectOps(Node *node);
|
||||||
|
|
||||||
/* cluster.c - forward declarations */
|
/* cluster.c - forward declarations */
|
||||||
|
@ -274,6 +276,12 @@ extern ObjectWithArgs * ObjectWithArgsFromOid(Oid funcOid);
|
||||||
/* vacuum.c - forward declarations */
|
/* vacuum.c - forward declarations */
|
||||||
extern void PostprocessVacuumStmt(VacuumStmt *vacuumStmt, const char *vacuumCommand);
|
extern void PostprocessVacuumStmt(VacuumStmt *vacuumStmt, const char *vacuumCommand);
|
||||||
|
|
||||||
|
/* trigger.c - forward declarations */
|
||||||
|
extern List * GetExplicitTriggerCommandList(Oid relationId);
|
||||||
|
extern List * GetExplicitTriggerIdList(Oid relationId);
|
||||||
|
extern Oid get_relation_trigger_oid_compat(HeapTuple heapTuple);
|
||||||
|
extern void ErrorIfUnsupportedCreateTriggerCommand(CreateTrigStmt *createTriggerStmt);
|
||||||
|
|
||||||
extern bool ShouldPropagateSetCommand(VariableSetStmt *setStmt);
|
extern bool ShouldPropagateSetCommand(VariableSetStmt *setStmt);
|
||||||
extern void PostprocessVariableSetStmt(VariableSetStmt *setStmt, const char *setCommand);
|
extern void PostprocessVariableSetStmt(VariableSetStmt *setStmt, const char *setCommand);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
-- This test file includes tests to show that we do not allow triggers
|
||||||
|
-- on citus tables. Note that in other regression tests, we already test
|
||||||
|
-- the successfull citus table creation cases.
|
||||||
|
\set VERBOSITY terse
|
||||||
|
SET citus.next_shard_id TO 1505000;
|
||||||
|
CREATE SCHEMA table_triggers_schema;
|
||||||
|
SET search_path TO table_triggers_schema;
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
-- show that we do not allow trigger creation on citus tables
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
-- create a simple function to be invoked by triggers
|
||||||
|
CREATE FUNCTION update_value() RETURNS trigger AS $update_value$
|
||||||
|
BEGIN
|
||||||
|
NEW.value := value+1 ;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$update_value$ LANGUAGE plpgsql;
|
||||||
|
CREATE TABLE distributed_table (value int);
|
||||||
|
SELECT create_distributed_table('distributed_table', 'value');
|
||||||
|
create_distributed_table
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
CREATE TABLE reference_table (value int);
|
||||||
|
SELECT create_reference_table('reference_table');
|
||||||
|
create_reference_table
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
-- below two should fail
|
||||||
|
CREATE TRIGGER update_value_dist
|
||||||
|
AFTER INSERT ON distributed_table
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE update_value();
|
||||||
|
ERROR: cannot create trigger on relation "distributed_table" because it is either a distributed table or a reference table
|
||||||
|
CREATE TRIGGER update_value_ref
|
||||||
|
AFTER INSERT ON reference_table
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE update_value();
|
||||||
|
ERROR: cannot create trigger on relation "reference_table" because it is either a distributed table or a reference table
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
-- show that we do not allow creating citus tables if the
|
||||||
|
-- table has already triggers
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
CREATE TABLE distributed_table_1 (value int);
|
||||||
|
CREATE TRIGGER update_value_dist
|
||||||
|
AFTER INSERT ON distributed_table_1
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE update_value();
|
||||||
|
CREATE TABLE reference_table_1 (value int);
|
||||||
|
CREATE TRIGGER update_value_ref
|
||||||
|
AFTER INSERT ON reference_table_1
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE update_value();
|
||||||
|
-- below two should fail
|
||||||
|
SELECT create_distributed_table('distributed_table_1', 'value');
|
||||||
|
ERROR: cannot distribute relation "distributed_table_1" because it has triggers
|
||||||
|
SELECT create_reference_table('reference_table_1');
|
||||||
|
ERROR: cannot distribute relation "reference_table_1" because it has triggers
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
-- test deparse logic for CREATE TRIGGER commands
|
||||||
|
-- via master_get_table_ddl_events
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
CREATE TABLE test_table (
|
||||||
|
id int,
|
||||||
|
text_number text,
|
||||||
|
text_col text
|
||||||
|
);
|
||||||
|
CREATE FUNCTION test_table_trigger_function() RETURNS trigger AS $test_table_trigger_function$
|
||||||
|
BEGIN
|
||||||
|
RAISE EXCEPTION 'a meaningless exception';
|
||||||
|
END;
|
||||||
|
$test_table_trigger_function$ LANGUAGE plpgsql;
|
||||||
|
-- in below two, use constraint triggers to test DEFERRABLE | NOT DEFERRABLE syntax
|
||||||
|
CREATE CONSTRAINT TRIGGER test_table_update
|
||||||
|
AFTER UPDATE OF id ON test_table
|
||||||
|
NOT DEFERRABLE
|
||||||
|
FOR EACH ROW
|
||||||
|
WHEN (OLD.* IS NOT DISTINCT FROM NEW.* AND OLD.text_number IS NOT NULL)
|
||||||
|
EXECUTE FUNCTION test_table_trigger_function();
|
||||||
|
CREATE CONSTRAINT TRIGGER test_table_insert
|
||||||
|
AFTER INSERT ON test_table
|
||||||
|
DEFERRABLE INITIALLY IMMEDIATE
|
||||||
|
FOR EACH ROW
|
||||||
|
WHEN (NEW.id > 5 OR NEW.text_col IS NOT NULL AND NEW.id < to_number(NEW.text_number, '9999'))
|
||||||
|
EXECUTE FUNCTION test_table_trigger_function();
|
||||||
|
CREATE TRIGGER test_table_delete
|
||||||
|
AFTER DELETE ON test_table
|
||||||
|
FOR EACH STATEMENT
|
||||||
|
EXECUTE FUNCTION test_table_trigger_function();
|
||||||
|
SELECT master_get_table_ddl_events('test_table');
|
||||||
|
master_get_table_ddl_events
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
CREATE TABLE table_triggers_schema.test_table (id integer, text_number text, text_col text)
|
||||||
|
ALTER TABLE table_triggers_schema.test_table OWNER TO postgres
|
||||||
|
CREATE TRIGGER test_table_delete AFTER DELETE ON table_triggers_schema.test_table FOR EACH STATEMENT EXECUTE FUNCTION table_triggers_schema.test_table_trigger_function()
|
||||||
|
CREATE CONSTRAINT TRIGGER test_table_insert AFTER INSERT ON table_triggers_schema.test_table DEFERRABLE INITIALLY IMMEDIATE FOR EACH ROW WHEN (((new.id OPERATOR(pg_catalog.>) 5) OR ((new.text_col IS NOT NULL) AND ((new.id)::numeric OPERATOR(pg_catalog.<) to_number(new.text_number, '9999'::text))))) EXECUTE FUNCTION table_triggers_schema.test_table_trigger_function()
|
||||||
|
CREATE CONSTRAINT TRIGGER test_table_update AFTER UPDATE OF id ON table_triggers_schema.test_table NOT DEFERRABLE INITIALLY IMMEDIATE FOR EACH ROW WHEN (((NOT (old.* IS DISTINCT FROM new.*)) AND (old.text_number IS NOT NULL))) EXECUTE FUNCTION table_triggers_schema.test_table_trigger_function()
|
||||||
|
(5 rows)
|
||||||
|
|
||||||
|
-- cleanup at exit
|
||||||
|
DROP SCHEMA table_triggers_schema CASCADE;
|
||||||
|
NOTICE: drop cascades to 7 other objects
|
|
@ -0,0 +1,101 @@
|
||||||
|
-- This test file includes tests to show that we do not allow triggers
|
||||||
|
-- on citus tables. Note that in other regression tests, we already test
|
||||||
|
-- the successfull citus table creation cases.
|
||||||
|
\set VERBOSITY terse
|
||||||
|
SET citus.next_shard_id TO 1505000;
|
||||||
|
CREATE SCHEMA table_triggers_schema;
|
||||||
|
SET search_path TO table_triggers_schema;
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
-- show that we do not allow trigger creation on citus tables
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
-- create a simple function to be invoked by triggers
|
||||||
|
CREATE FUNCTION update_value() RETURNS trigger AS $update_value$
|
||||||
|
BEGIN
|
||||||
|
NEW.value := value+1 ;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$update_value$ LANGUAGE plpgsql;
|
||||||
|
CREATE TABLE distributed_table (value int);
|
||||||
|
SELECT create_distributed_table('distributed_table', 'value');
|
||||||
|
create_distributed_table
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
CREATE TABLE reference_table (value int);
|
||||||
|
SELECT create_reference_table('reference_table');
|
||||||
|
create_reference_table
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
-- below two should fail
|
||||||
|
CREATE TRIGGER update_value_dist
|
||||||
|
AFTER INSERT ON distributed_table
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE update_value();
|
||||||
|
ERROR: cannot create trigger on relation "distributed_table" because it is either a distributed table or a reference table
|
||||||
|
CREATE TRIGGER update_value_ref
|
||||||
|
AFTER INSERT ON reference_table
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE update_value();
|
||||||
|
ERROR: cannot create trigger on relation "reference_table" because it is either a distributed table or a reference table
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
-- show that we do not allow creating citus tables if the
|
||||||
|
-- table has already triggers
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
CREATE TABLE distributed_table_1 (value int);
|
||||||
|
CREATE TRIGGER update_value_dist
|
||||||
|
AFTER INSERT ON distributed_table_1
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE update_value();
|
||||||
|
CREATE TABLE reference_table_1 (value int);
|
||||||
|
CREATE TRIGGER update_value_ref
|
||||||
|
AFTER INSERT ON reference_table_1
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE update_value();
|
||||||
|
-- below two should fail
|
||||||
|
SELECT create_distributed_table('distributed_table_1', 'value');
|
||||||
|
ERROR: cannot distribute relation "distributed_table_1" because it has triggers
|
||||||
|
SELECT create_reference_table('reference_table_1');
|
||||||
|
ERROR: cannot distribute relation "reference_table_1" because it has triggers
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
-- test deparse logic for CREATE TRIGGER commands
|
||||||
|
-- via master_get_table_ddl_events
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
CREATE TABLE test_table (
|
||||||
|
id int,
|
||||||
|
text_number text,
|
||||||
|
text_col text
|
||||||
|
);
|
||||||
|
CREATE FUNCTION test_table_trigger_function() RETURNS trigger AS $test_table_trigger_function$
|
||||||
|
BEGIN
|
||||||
|
RAISE EXCEPTION 'a meaningless exception';
|
||||||
|
END;
|
||||||
|
$test_table_trigger_function$ LANGUAGE plpgsql;
|
||||||
|
-- in below two, use constraint triggers to test DEFERRABLE | NOT DEFERRABLE syntax
|
||||||
|
CREATE CONSTRAINT TRIGGER test_table_update
|
||||||
|
AFTER UPDATE OF id ON test_table
|
||||||
|
NOT DEFERRABLE
|
||||||
|
FOR EACH ROW
|
||||||
|
WHEN (OLD.* IS NOT DISTINCT FROM NEW.* AND OLD.text_number IS NOT NULL)
|
||||||
|
EXECUTE FUNCTION test_table_trigger_function();
|
||||||
|
CREATE CONSTRAINT TRIGGER test_table_insert
|
||||||
|
AFTER INSERT ON test_table
|
||||||
|
DEFERRABLE INITIALLY IMMEDIATE
|
||||||
|
FOR EACH ROW
|
||||||
|
WHEN (NEW.id > 5 OR NEW.text_col IS NOT NULL AND NEW.id < to_number(NEW.text_number, '9999'))
|
||||||
|
EXECUTE FUNCTION test_table_trigger_function();
|
||||||
|
CREATE TRIGGER test_table_delete
|
||||||
|
AFTER DELETE ON test_table
|
||||||
|
FOR EACH STATEMENT
|
||||||
|
EXECUTE FUNCTION test_table_trigger_function();
|
||||||
|
SELECT master_get_table_ddl_events('test_table');
|
||||||
|
master_get_table_ddl_events
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
CREATE TABLE table_triggers_schema.test_table (id integer, text_number text, text_col text)
|
||||||
|
ALTER TABLE table_triggers_schema.test_table OWNER TO postgres
|
||||||
|
CREATE TRIGGER test_table_delete AFTER DELETE ON table_triggers_schema.test_table FOR EACH STATEMENT EXECUTE PROCEDURE table_triggers_schema.test_table_trigger_function()
|
||||||
|
CREATE CONSTRAINT TRIGGER test_table_insert AFTER INSERT ON table_triggers_schema.test_table DEFERRABLE INITIALLY IMMEDIATE FOR EACH ROW WHEN (((new.id OPERATOR(pg_catalog.>) 5) OR ((new.text_col IS NOT NULL) AND ((new.id)::numeric OPERATOR(pg_catalog.<) to_number(new.text_number, '9999'::text))))) EXECUTE PROCEDURE table_triggers_schema.test_table_trigger_function()
|
||||||
|
CREATE CONSTRAINT TRIGGER test_table_update AFTER UPDATE OF id ON table_triggers_schema.test_table NOT DEFERRABLE INITIALLY IMMEDIATE FOR EACH ROW WHEN (((NOT (old.* IS DISTINCT FROM new.*)) AND (old.text_number IS NOT NULL))) EXECUTE PROCEDURE table_triggers_schema.test_table_trigger_function()
|
||||||
|
(5 rows)
|
||||||
|
|
||||||
|
-- cleanup at exit
|
||||||
|
DROP SCHEMA table_triggers_schema CASCADE;
|
||||||
|
NOTICE: drop cascades to 7 other objects
|
|
@ -45,7 +45,7 @@ test: multi_create_table_constraints multi_master_protocol multi_load_data multi
|
||||||
test: multi_behavioral_analytics_basics multi_behavioral_analytics_single_shard_queries multi_insert_select_non_pushable_queries multi_insert_select multi_behavioral_analytics_create_table_superuser
|
test: multi_behavioral_analytics_basics multi_behavioral_analytics_single_shard_queries multi_insert_select_non_pushable_queries multi_insert_select multi_behavioral_analytics_create_table_superuser
|
||||||
test: multi_shard_update_delete recursive_dml_with_different_planners_executors
|
test: multi_shard_update_delete recursive_dml_with_different_planners_executors
|
||||||
test: insert_select_repartition window_functions dml_recursive multi_insert_select_window
|
test: insert_select_repartition window_functions dml_recursive multi_insert_select_window
|
||||||
test: multi_insert_select_conflict
|
test: multi_insert_select_conflict create_table_triggers
|
||||||
test: multi_row_insert
|
test: multi_row_insert
|
||||||
|
|
||||||
# following should not run in parallel because it relies on connection counts to workers
|
# following should not run in parallel because it relies on connection counts to workers
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
-- This test file includes tests to show that we do not allow triggers
|
||||||
|
-- on citus tables. Note that in other regression tests, we already test
|
||||||
|
-- the successfull citus table creation cases.
|
||||||
|
|
||||||
|
\set VERBOSITY terse
|
||||||
|
|
||||||
|
SET citus.next_shard_id TO 1505000;
|
||||||
|
|
||||||
|
CREATE SCHEMA table_triggers_schema;
|
||||||
|
SET search_path TO table_triggers_schema;
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- show that we do not allow trigger creation on citus tables
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
-- create a simple function to be invoked by triggers
|
||||||
|
CREATE FUNCTION update_value() RETURNS trigger AS $update_value$
|
||||||
|
BEGIN
|
||||||
|
NEW.value := value+1 ;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$update_value$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE TABLE distributed_table (value int);
|
||||||
|
SELECT create_distributed_table('distributed_table', 'value');
|
||||||
|
|
||||||
|
CREATE TABLE reference_table (value int);
|
||||||
|
SELECT create_reference_table('reference_table');
|
||||||
|
|
||||||
|
-- below two should fail
|
||||||
|
CREATE TRIGGER update_value_dist
|
||||||
|
AFTER INSERT ON distributed_table
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE update_value();
|
||||||
|
|
||||||
|
CREATE TRIGGER update_value_ref
|
||||||
|
AFTER INSERT ON reference_table
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE update_value();
|
||||||
|
|
||||||
|
---------------------------------------------------------
|
||||||
|
-- show that we do not allow creating citus tables if the
|
||||||
|
-- table has already triggers
|
||||||
|
---------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE distributed_table_1 (value int);
|
||||||
|
|
||||||
|
CREATE TRIGGER update_value_dist
|
||||||
|
AFTER INSERT ON distributed_table_1
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE update_value();
|
||||||
|
|
||||||
|
CREATE TABLE reference_table_1 (value int);
|
||||||
|
|
||||||
|
CREATE TRIGGER update_value_ref
|
||||||
|
AFTER INSERT ON reference_table_1
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE update_value();
|
||||||
|
|
||||||
|
-- below two should fail
|
||||||
|
SELECT create_distributed_table('distributed_table_1', 'value');
|
||||||
|
SELECT create_reference_table('reference_table_1');
|
||||||
|
|
||||||
|
-------------------------------------------------
|
||||||
|
-- test deparse logic for CREATE TRIGGER commands
|
||||||
|
-- via master_get_table_ddl_events
|
||||||
|
-------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE test_table (
|
||||||
|
id int,
|
||||||
|
text_number text,
|
||||||
|
text_col text
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE FUNCTION test_table_trigger_function() RETURNS trigger AS $test_table_trigger_function$
|
||||||
|
BEGIN
|
||||||
|
RAISE EXCEPTION 'a meaningless exception';
|
||||||
|
END;
|
||||||
|
$test_table_trigger_function$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- in below two, use constraint triggers to test DEFERRABLE | NOT DEFERRABLE syntax
|
||||||
|
CREATE CONSTRAINT TRIGGER test_table_update
|
||||||
|
AFTER UPDATE OF id ON test_table
|
||||||
|
NOT DEFERRABLE
|
||||||
|
FOR EACH ROW
|
||||||
|
WHEN (OLD.* IS NOT DISTINCT FROM NEW.* AND OLD.text_number IS NOT NULL)
|
||||||
|
EXECUTE FUNCTION test_table_trigger_function();
|
||||||
|
|
||||||
|
CREATE CONSTRAINT TRIGGER test_table_insert
|
||||||
|
AFTER INSERT ON test_table
|
||||||
|
DEFERRABLE INITIALLY IMMEDIATE
|
||||||
|
FOR EACH ROW
|
||||||
|
WHEN (NEW.id > 5 OR NEW.text_col IS NOT NULL AND NEW.id < to_number(NEW.text_number, '9999'))
|
||||||
|
EXECUTE FUNCTION test_table_trigger_function();
|
||||||
|
|
||||||
|
CREATE TRIGGER test_table_delete
|
||||||
|
AFTER DELETE ON test_table
|
||||||
|
FOR EACH STATEMENT
|
||||||
|
EXECUTE FUNCTION test_table_trigger_function();
|
||||||
|
|
||||||
|
SELECT master_get_table_ddl_events('test_table');
|
||||||
|
|
||||||
|
-- cleanup at exit
|
||||||
|
DROP SCHEMA table_triggers_schema CASCADE;
|
Loading…
Reference in New Issue