Fix transition tables for partition/inheritance.
We disallow row-level triggers with transition tables on child tables. Transition tables for triggers on the parent table contain only those columns present in the parent. (We can't mix tuple formats in a single transition table.) Patch by Thomas Munro Discussion: https://postgr.es/m/CA%2BTgmoZzTBBAsEUh4MazAN7ga%3D8SsMC-Knp-6cetts9yNZUCcg%40mail.gmail.com
This commit is contained in:
parent
99255d73c0
commit
501ed02cf6
@ -458,6 +458,20 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</
|
||||
rows.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Modifying a partitioned table or a table with inheritance children fires
|
||||
statement-level triggers directly attached to that table, but not
|
||||
statement-level triggers for its partitions or child tables. In contrast,
|
||||
row-level triggers are fired for all affected partitions or child tables.
|
||||
If a statement-level trigger has been defined with transition relations
|
||||
named by a <literal>REFERENCING</literal> clause, then before and after
|
||||
images of rows are visible from all affected partitions or child tables.
|
||||
In the case of inheritance children, the row images include only columns
|
||||
that are present in the table that the trigger is attached to. Currently,
|
||||
row-level triggers with transition relations cannot be defined on
|
||||
partitions or inheritance child tables.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
In <productname>PostgreSQL</productname> versions before 7.3, it was
|
||||
necessary to declare trigger functions as returning the placeholder
|
||||
|
@ -273,6 +273,30 @@ has_subclass(Oid relationId)
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* has_superclass - does this relation inherit from another? The caller
|
||||
* should hold a lock on the given relation so that it can't be concurrently
|
||||
* added to or removed from an inheritance hierarchy.
|
||||
*/
|
||||
bool
|
||||
has_superclass(Oid relationId)
|
||||
{
|
||||
Relation catalog;
|
||||
SysScanDesc scan;
|
||||
ScanKeyData skey;
|
||||
bool result;
|
||||
|
||||
catalog = heap_open(InheritsRelationId, AccessShareLock);
|
||||
ScanKeyInit(&skey, Anum_pg_inherits_inhrelid, BTEqualStrategyNumber,
|
||||
F_OIDEQ, ObjectIdGetDatum(relationId));
|
||||
scan = systable_beginscan(catalog, InheritsRelidSeqnoIndexId, true,
|
||||
NULL, 1, &skey);
|
||||
result = HeapTupleIsValid(systable_getnext(scan));
|
||||
systable_endscan(scan);
|
||||
heap_close(catalog, AccessShareLock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Given two type OIDs, determine whether the first is a complex type
|
||||
|
@ -171,6 +171,8 @@ typedef struct CopyStateData
|
||||
ResultRelInfo *partitions; /* Per partition result relation */
|
||||
TupleConversionMap **partition_tupconv_maps;
|
||||
TupleTableSlot *partition_tuple_slot;
|
||||
TransitionCaptureState *transition_capture;
|
||||
TupleConversionMap **transition_tupconv_maps;
|
||||
|
||||
/*
|
||||
* These variables are used to reduce overhead in textual COPY FROM.
|
||||
@ -1436,6 +1438,36 @@ BeginCopy(ParseState *pstate,
|
||||
cstate->num_partitions = num_partitions;
|
||||
cstate->partition_tupconv_maps = partition_tupconv_maps;
|
||||
cstate->partition_tuple_slot = partition_tuple_slot;
|
||||
|
||||
/*
|
||||
* If there are any triggers with transition tables on the named
|
||||
* relation, we need to be prepared to capture transition tuples
|
||||
* from child relations too.
|
||||
*/
|
||||
cstate->transition_capture =
|
||||
MakeTransitionCaptureState(rel->trigdesc);
|
||||
|
||||
/*
|
||||
* If we are capturing transition tuples, they may need to be
|
||||
* converted from partition format back to partitioned table
|
||||
* format (this is only ever necessary if a BEFORE trigger
|
||||
* modifies the tuple).
|
||||
*/
|
||||
if (cstate->transition_capture != NULL)
|
||||
{
|
||||
int i;
|
||||
|
||||
cstate->transition_tupconv_maps = (TupleConversionMap **)
|
||||
palloc0(sizeof(TupleConversionMap *) *
|
||||
cstate->num_partitions);
|
||||
for (i = 0; i < cstate->num_partitions; ++i)
|
||||
{
|
||||
cstate->transition_tupconv_maps[i] =
|
||||
convert_tuples_by_name(RelationGetDescr(cstate->partitions[i].ri_RelationDesc),
|
||||
RelationGetDescr(rel),
|
||||
gettext_noop("could not convert row type"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -2591,6 +2623,35 @@ CopyFrom(CopyState cstate)
|
||||
*/
|
||||
estate->es_result_relation_info = resultRelInfo;
|
||||
|
||||
/*
|
||||
* If we're capturing transition tuples, we might need to convert
|
||||
* from the partition rowtype to parent rowtype.
|
||||
*/
|
||||
if (cstate->transition_capture != NULL)
|
||||
{
|
||||
if (resultRelInfo->ri_TrigDesc &&
|
||||
(resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
|
||||
resultRelInfo->ri_TrigDesc->trig_insert_instead_row))
|
||||
{
|
||||
/*
|
||||
* If there are any BEFORE or INSTEAD triggers on the
|
||||
* partition, we'll have to be ready to convert their
|
||||
* result back to tuplestore format.
|
||||
*/
|
||||
cstate->transition_capture->tcs_original_insert_tuple = NULL;
|
||||
cstate->transition_capture->tcs_map =
|
||||
cstate->transition_tupconv_maps[leaf_part_index];
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* Otherwise, just remember the original unconverted
|
||||
* tuple, to avoid a needless round trip conversion.
|
||||
*/
|
||||
cstate->transition_capture->tcs_original_insert_tuple = tuple;
|
||||
cstate->transition_capture->tcs_map = NULL;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* We might need to convert from the parent rowtype to the
|
||||
* partition rowtype.
|
||||
@ -2703,7 +2764,7 @@ CopyFrom(CopyState cstate)
|
||||
|
||||
/* AFTER ROW INSERT Triggers */
|
||||
ExecARInsertTriggers(estate, resultRelInfo, tuple,
|
||||
recheckIndexes);
|
||||
recheckIndexes, cstate->transition_capture);
|
||||
|
||||
list_free(recheckIndexes);
|
||||
}
|
||||
@ -2856,7 +2917,7 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
|
||||
estate, false, NULL, NIL);
|
||||
ExecARInsertTriggers(estate, resultRelInfo,
|
||||
bufferedTuples[i],
|
||||
recheckIndexes);
|
||||
recheckIndexes, NULL);
|
||||
list_free(recheckIndexes);
|
||||
}
|
||||
}
|
||||
@ -2866,14 +2927,15 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
|
||||
* anyway.
|
||||
*/
|
||||
else if (resultRelInfo->ri_TrigDesc != NULL &&
|
||||
resultRelInfo->ri_TrigDesc->trig_insert_after_row)
|
||||
(resultRelInfo->ri_TrigDesc->trig_insert_after_row ||
|
||||
resultRelInfo->ri_TrigDesc->trig_insert_new_table))
|
||||
{
|
||||
for (i = 0; i < nBufferedTuples; i++)
|
||||
{
|
||||
cstate->cur_lineno = firstBufferedLineNo + i;
|
||||
ExecARInsertTriggers(estate, resultRelInfo,
|
||||
bufferedTuples[i],
|
||||
NIL);
|
||||
NIL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10933,6 +10933,7 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
|
||||
Relation parent_rel;
|
||||
List *children;
|
||||
ObjectAddress address;
|
||||
const char *trigger_name;
|
||||
|
||||
/*
|
||||
* A self-exclusive lock is needed here. See the similar case in
|
||||
@ -11014,6 +11015,19 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
|
||||
RelationGetRelationName(child_rel),
|
||||
RelationGetRelationName(parent_rel))));
|
||||
|
||||
/*
|
||||
* If child_rel has row-level triggers with transition tables, we
|
||||
* currently don't allow it to become an inheritance child. See also
|
||||
* prohibitions in ATExecAttachPartition() and CreateTrigger().
|
||||
*/
|
||||
trigger_name = FindTriggerIncompatibleWithInheritance(child_rel->trigdesc);
|
||||
if (trigger_name != NULL)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("trigger \"%s\" prevents table \"%s\" from becoming an inheritance child",
|
||||
trigger_name, RelationGetRelationName(child_rel)),
|
||||
errdetail("ROW triggers with transition tables are not supported in inheritance hierarchies")));
|
||||
|
||||
/* OK to create inheritance */
|
||||
CreateInheritance(child_rel, parent_rel);
|
||||
|
||||
@ -13418,6 +13432,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
|
||||
TupleDesc tupleDesc;
|
||||
bool skip_validate = false;
|
||||
ObjectAddress address;
|
||||
const char *trigger_name;
|
||||
|
||||
attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
|
||||
|
||||
@ -13547,6 +13562,19 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
|
||||
errdetail("New partition should contain only the columns present in parent.")));
|
||||
}
|
||||
|
||||
/*
|
||||
* If child_rel has row-level triggers with transition tables, we
|
||||
* currently don't allow it to become a partition. See also prohibitions
|
||||
* in ATExecAddInherit() and CreateTrigger().
|
||||
*/
|
||||
trigger_name = FindTriggerIncompatibleWithInheritance(attachRel->trigdesc);
|
||||
if (trigger_name != NULL)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("trigger \"%s\" prevents table \"%s\" from becoming a partition",
|
||||
trigger_name, RelationGetRelationName(attachRel)),
|
||||
errdetail("ROW triggers with transition tables are not supported on partitions")));
|
||||
|
||||
/* OK to create inheritance. Rest of the checks performed there */
|
||||
CreateInheritance(attachRel, rel);
|
||||
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "catalog/objectaccess.h"
|
||||
#include "catalog/pg_constraint.h"
|
||||
#include "catalog/pg_constraint_fn.h"
|
||||
#include "catalog/pg_inherits_fn.h"
|
||||
#include "catalog/pg_proc.h"
|
||||
#include "catalog/pg_trigger.h"
|
||||
#include "catalog/pg_type.h"
|
||||
@ -96,7 +97,8 @@ static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata,
|
||||
static void AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
|
||||
int event, bool row_trigger,
|
||||
HeapTuple oldtup, HeapTuple newtup,
|
||||
List *recheckIndexes, Bitmapset *modifiedCols);
|
||||
List *recheckIndexes, Bitmapset *modifiedCols,
|
||||
TransitionCaptureState *transition_capture);
|
||||
static void AfterTriggerEnlargeQueryState(void);
|
||||
|
||||
|
||||
@ -354,13 +356,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
|
||||
* adjustments will be needed below.
|
||||
*/
|
||||
|
||||
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("\"%s\" is a partitioned table",
|
||||
RelationGetRelationName(rel)),
|
||||
errdetail("Triggers on partitioned tables cannot have transition tables.")));
|
||||
|
||||
if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
@ -375,6 +370,27 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
|
||||
RelationGetRelationName(rel)),
|
||||
errdetail("Triggers on views cannot have transition tables.")));
|
||||
|
||||
/*
|
||||
* We currently don't allow row-level triggers with transition
|
||||
* tables on partition or inheritance children. Such triggers
|
||||
* would somehow need to see tuples converted to the format of the
|
||||
* table they're attached to, and it's not clear which subset of
|
||||
* tuples each child should see. See also the prohibitions in
|
||||
* ATExecAttachPartition() and ATExecAddInherit().
|
||||
*/
|
||||
if (TRIGGER_FOR_ROW(tgtype) && has_superclass(rel->rd_id))
|
||||
{
|
||||
/* Use appropriate error message. */
|
||||
if (rel->rd_rel->relispartition)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("ROW triggers with transition tables are not supported on partitions")));
|
||||
else
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("ROW triggers with transition tables are not supported on inheritance children")));
|
||||
}
|
||||
|
||||
if (stmt->timing != TRIGGER_TYPE_AFTER)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
@ -2028,6 +2044,64 @@ equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2)
|
||||
}
|
||||
#endif /* NOT_USED */
|
||||
|
||||
/*
|
||||
* Check if there is a row-level trigger with transition tables that prevents
|
||||
* a table from becoming an inheritance child or partition. Return the name
|
||||
* of the first such incompatible trigger, or NULL if there is none.
|
||||
*/
|
||||
const char *
|
||||
FindTriggerIncompatibleWithInheritance(TriggerDesc *trigdesc)
|
||||
{
|
||||
if (trigdesc != NULL)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < trigdesc->numtriggers; ++i)
|
||||
{
|
||||
Trigger *trigger = &trigdesc->triggers[i];
|
||||
|
||||
if (trigger->tgoldtable != NULL || trigger->tgnewtable != NULL)
|
||||
return trigger->tgname;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Make a TransitionCaptureState object from a given TriggerDesc. The
|
||||
* resulting object holds the flags which control whether transition tuples
|
||||
* are collected when tables are modified. This allows us to use the flags
|
||||
* from a parent table to control the collection of transition tuples from
|
||||
* child tables.
|
||||
*
|
||||
* If there are no triggers with transition tables configured for 'trigdesc',
|
||||
* then return NULL.
|
||||
*
|
||||
* The resulting object can be passed to the ExecAR* functions. The caller
|
||||
* should set tcs_map or tcs_original_insert_tuple as appropriate when dealing
|
||||
* with child tables.
|
||||
*/
|
||||
TransitionCaptureState *
|
||||
MakeTransitionCaptureState(TriggerDesc *trigdesc)
|
||||
{
|
||||
TransitionCaptureState *state = NULL;
|
||||
|
||||
if (trigdesc != NULL &&
|
||||
(trigdesc->trig_delete_old_table || trigdesc->trig_update_old_table ||
|
||||
trigdesc->trig_update_new_table || trigdesc->trig_insert_new_table))
|
||||
{
|
||||
state = (TransitionCaptureState *)
|
||||
palloc0(sizeof(TransitionCaptureState));
|
||||
state->tcs_delete_old_table = trigdesc->trig_delete_old_table;
|
||||
state->tcs_update_old_table = trigdesc->trig_update_old_table;
|
||||
state->tcs_update_new_table = trigdesc->trig_update_new_table;
|
||||
state->tcs_insert_new_table = trigdesc->trig_insert_new_table;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/*
|
||||
* Call a trigger function.
|
||||
*
|
||||
@ -2192,7 +2266,7 @@ ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo)
|
||||
|
||||
if (trigdesc && trigdesc->trig_insert_after_statement)
|
||||
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT,
|
||||
false, NULL, NULL, NIL, NULL);
|
||||
false, NULL, NULL, NIL, NULL, NULL);
|
||||
}
|
||||
|
||||
TupleTableSlot *
|
||||
@ -2263,14 +2337,18 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
|
||||
|
||||
void
|
||||
ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
|
||||
HeapTuple trigtuple, List *recheckIndexes)
|
||||
HeapTuple trigtuple, List *recheckIndexes,
|
||||
TransitionCaptureState *transition_capture)
|
||||
{
|
||||
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
|
||||
|
||||
if (trigdesc &&
|
||||
(trigdesc->trig_insert_after_row || trigdesc->trig_insert_new_table))
|
||||
if ((trigdesc && trigdesc->trig_insert_after_row) ||
|
||||
(trigdesc && !transition_capture && trigdesc->trig_insert_new_table) ||
|
||||
(transition_capture && transition_capture->tcs_insert_new_table))
|
||||
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT,
|
||||
true, NULL, trigtuple, recheckIndexes, NULL);
|
||||
true, NULL, trigtuple,
|
||||
recheckIndexes, NULL,
|
||||
transition_capture);
|
||||
}
|
||||
|
||||
TupleTableSlot *
|
||||
@ -2398,7 +2476,7 @@ ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
|
||||
|
||||
if (trigdesc && trigdesc->trig_delete_after_statement)
|
||||
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_DELETE,
|
||||
false, NULL, NULL, NIL, NULL);
|
||||
false, NULL, NULL, NIL, NULL, NULL);
|
||||
}
|
||||
|
||||
bool
|
||||
@ -2473,12 +2551,14 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
|
||||
void
|
||||
ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
|
||||
ItemPointer tupleid,
|
||||
HeapTuple fdw_trigtuple)
|
||||
HeapTuple fdw_trigtuple,
|
||||
TransitionCaptureState *transition_capture)
|
||||
{
|
||||
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
|
||||
|
||||
if (trigdesc &&
|
||||
(trigdesc->trig_delete_after_row || trigdesc->trig_delete_old_table))
|
||||
if ((trigdesc && trigdesc->trig_delete_after_row) ||
|
||||
(trigdesc && !transition_capture && trigdesc->trig_delete_old_table) ||
|
||||
(transition_capture && transition_capture->tcs_delete_old_table))
|
||||
{
|
||||
HeapTuple trigtuple;
|
||||
|
||||
@ -2494,7 +2574,8 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
|
||||
trigtuple = fdw_trigtuple;
|
||||
|
||||
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_DELETE,
|
||||
true, trigtuple, NULL, NIL, NULL);
|
||||
true, trigtuple, NULL, NIL, NULL,
|
||||
transition_capture);
|
||||
if (trigtuple != fdw_trigtuple)
|
||||
heap_freetuple(trigtuple);
|
||||
}
|
||||
@ -2610,7 +2691,8 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
|
||||
if (trigdesc && trigdesc->trig_update_after_statement)
|
||||
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE,
|
||||
false, NULL, NULL, NIL,
|
||||
GetUpdatedColumns(relinfo, estate));
|
||||
GetUpdatedColumns(relinfo, estate),
|
||||
NULL);
|
||||
}
|
||||
|
||||
TupleTableSlot *
|
||||
@ -2735,12 +2817,18 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
|
||||
ItemPointer tupleid,
|
||||
HeapTuple fdw_trigtuple,
|
||||
HeapTuple newtuple,
|
||||
List *recheckIndexes)
|
||||
List *recheckIndexes,
|
||||
TransitionCaptureState *transition_capture)
|
||||
{
|
||||
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
|
||||
|
||||
if (trigdesc && (trigdesc->trig_update_after_row ||
|
||||
trigdesc->trig_update_old_table || trigdesc->trig_update_new_table))
|
||||
if ((trigdesc && trigdesc->trig_update_after_row) ||
|
||||
(trigdesc && !transition_capture &&
|
||||
(trigdesc->trig_update_old_table ||
|
||||
trigdesc->trig_update_new_table)) ||
|
||||
(transition_capture &&
|
||||
(transition_capture->tcs_update_old_table ||
|
||||
transition_capture->tcs_update_new_table)))
|
||||
{
|
||||
HeapTuple trigtuple;
|
||||
|
||||
@ -2757,7 +2845,8 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
|
||||
|
||||
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE,
|
||||
true, trigtuple, newtuple, recheckIndexes,
|
||||
GetUpdatedColumns(relinfo, estate));
|
||||
GetUpdatedColumns(relinfo, estate),
|
||||
transition_capture);
|
||||
if (trigtuple != fdw_trigtuple)
|
||||
heap_freetuple(trigtuple);
|
||||
}
|
||||
@ -2888,7 +2977,7 @@ ExecASTruncateTriggers(EState *estate, ResultRelInfo *relinfo)
|
||||
|
||||
if (trigdesc && trigdesc->trig_truncate_after_statement)
|
||||
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_TRUNCATE,
|
||||
false, NULL, NULL, NIL, NULL);
|
||||
false, NULL, NULL, NIL, NULL, NULL);
|
||||
}
|
||||
|
||||
|
||||
@ -5090,7 +5179,8 @@ static void
|
||||
AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
|
||||
int event, bool row_trigger,
|
||||
HeapTuple oldtup, HeapTuple newtup,
|
||||
List *recheckIndexes, Bitmapset *modifiedCols)
|
||||
List *recheckIndexes, Bitmapset *modifiedCols,
|
||||
TransitionCaptureState *transition_capture)
|
||||
{
|
||||
Relation rel = relinfo->ri_RelationDesc;
|
||||
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
|
||||
@ -5120,10 +5210,49 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
|
||||
*/
|
||||
if (row_trigger)
|
||||
{
|
||||
if ((event == TRIGGER_EVENT_DELETE &&
|
||||
trigdesc->trig_delete_old_table) ||
|
||||
(event == TRIGGER_EVENT_UPDATE &&
|
||||
trigdesc->trig_update_old_table))
|
||||
HeapTuple original_insert_tuple = NULL;
|
||||
TupleConversionMap *map = NULL;
|
||||
bool delete_old_table = false;
|
||||
bool update_old_table = false;
|
||||
bool update_new_table = false;
|
||||
bool insert_new_table = false;
|
||||
|
||||
if (transition_capture != NULL)
|
||||
{
|
||||
/*
|
||||
* A TransitionCaptureState object was provided to tell us which
|
||||
* tuples to capture based on a parent table named in a DML
|
||||
* statement. We may be dealing with a child table with an
|
||||
* incompatible TupleDescriptor, in which case we'll need a map to
|
||||
* convert them. As a small optimization, we may receive the
|
||||
* original tuple from an insertion into a partitioned table to
|
||||
* avoid a wasteful parent->child->parent round trip.
|
||||
*/
|
||||
delete_old_table = transition_capture->tcs_delete_old_table;
|
||||
update_old_table = transition_capture->tcs_update_old_table;
|
||||
update_new_table = transition_capture->tcs_update_new_table;
|
||||
insert_new_table = transition_capture->tcs_insert_new_table;
|
||||
map = transition_capture->tcs_map;
|
||||
original_insert_tuple =
|
||||
transition_capture->tcs_original_insert_tuple;
|
||||
}
|
||||
else if (trigdesc != NULL)
|
||||
{
|
||||
/*
|
||||
* Check if we need to capture transition tuples for triggers
|
||||
* defined on this relation directly. This case is useful for
|
||||
* cases like execReplication.c which don't set up a
|
||||
* TriggerCaptureState because they don't know how to work with
|
||||
* partitions.
|
||||
*/
|
||||
delete_old_table = trigdesc->trig_delete_old_table;
|
||||
update_old_table = trigdesc->trig_update_old_table;
|
||||
update_new_table = trigdesc->trig_update_new_table;
|
||||
insert_new_table = trigdesc->trig_insert_new_table;
|
||||
}
|
||||
|
||||
if ((event == TRIGGER_EVENT_DELETE && delete_old_table) ||
|
||||
(event == TRIGGER_EVENT_UPDATE && update_old_table))
|
||||
{
|
||||
Tuplestorestate *old_tuplestore;
|
||||
|
||||
@ -5131,12 +5260,18 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
|
||||
old_tuplestore =
|
||||
GetTriggerTransitionTuplestore
|
||||
(afterTriggers.old_tuplestores);
|
||||
tuplestore_puttuple(old_tuplestore, oldtup);
|
||||
if (map != NULL)
|
||||
{
|
||||
HeapTuple converted = do_convert_tuple(oldtup, map);
|
||||
|
||||
tuplestore_puttuple(old_tuplestore, converted);
|
||||
pfree(converted);
|
||||
}
|
||||
else
|
||||
tuplestore_puttuple(old_tuplestore, oldtup);
|
||||
}
|
||||
if ((event == TRIGGER_EVENT_INSERT &&
|
||||
trigdesc->trig_insert_new_table) ||
|
||||
(event == TRIGGER_EVENT_UPDATE &&
|
||||
trigdesc->trig_update_new_table))
|
||||
if ((event == TRIGGER_EVENT_INSERT && insert_new_table) ||
|
||||
(event == TRIGGER_EVENT_UPDATE && update_new_table))
|
||||
{
|
||||
Tuplestorestate *new_tuplestore;
|
||||
|
||||
@ -5144,11 +5279,22 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
|
||||
new_tuplestore =
|
||||
GetTriggerTransitionTuplestore
|
||||
(afterTriggers.new_tuplestores);
|
||||
tuplestore_puttuple(new_tuplestore, newtup);
|
||||
if (original_insert_tuple != NULL)
|
||||
tuplestore_puttuple(new_tuplestore, original_insert_tuple);
|
||||
else if (map != NULL)
|
||||
{
|
||||
HeapTuple converted = do_convert_tuple(newtup, map);
|
||||
|
||||
tuplestore_puttuple(new_tuplestore, converted);
|
||||
pfree(converted);
|
||||
}
|
||||
else
|
||||
tuplestore_puttuple(new_tuplestore, newtup);
|
||||
}
|
||||
|
||||
/* If transition tables are the only reason we're here, return. */
|
||||
if ((event == TRIGGER_EVENT_DELETE && !trigdesc->trig_delete_after_row) ||
|
||||
if (trigdesc == NULL ||
|
||||
(event == TRIGGER_EVENT_DELETE && !trigdesc->trig_delete_after_row) ||
|
||||
(event == TRIGGER_EVENT_INSERT && !trigdesc->trig_insert_after_row) ||
|
||||
(event == TRIGGER_EVENT_UPDATE && !trigdesc->trig_update_after_row))
|
||||
return;
|
||||
|
@ -3198,7 +3198,7 @@ EvalPlanQualEnd(EPQState *epqstate)
|
||||
* 'tup_conv_maps' receives an array of TupleConversionMap objects with one
|
||||
* entry for every leaf partition (required to convert input tuple based
|
||||
* on the root table's rowtype to a leaf partition's rowtype after tuple
|
||||
* routing is done
|
||||
* routing is done)
|
||||
* 'partition_tuple_slot' receives a standalone TupleTableSlot to be used
|
||||
* to manipulate any given leaf partition's rowtype after that partition
|
||||
* is chosen by tuple-routing.
|
||||
|
@ -417,7 +417,7 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
|
||||
|
||||
/* AFTER ROW INSERT Triggers */
|
||||
ExecARInsertTriggers(estate, resultRelInfo, tuple,
|
||||
recheckIndexes);
|
||||
recheckIndexes, NULL);
|
||||
|
||||
list_free(recheckIndexes);
|
||||
}
|
||||
@ -479,7 +479,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
|
||||
/* AFTER ROW UPDATE Triggers */
|
||||
ExecARUpdateTriggers(estate, resultRelInfo,
|
||||
&searchslot->tts_tuple->t_self,
|
||||
NULL, tuple, recheckIndexes);
|
||||
NULL, tuple, recheckIndexes, NULL);
|
||||
|
||||
list_free(recheckIndexes);
|
||||
}
|
||||
@ -522,7 +522,7 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate,
|
||||
|
||||
/* AFTER ROW DELETE Triggers */
|
||||
ExecARDeleteTriggers(estate, resultRelInfo,
|
||||
&searchslot->tts_tuple->t_self, NULL);
|
||||
&searchslot->tts_tuple->t_self, NULL, NULL);
|
||||
|
||||
list_free(recheckIndexes);
|
||||
}
|
||||
|
@ -313,6 +313,36 @@ ExecInsert(ModifyTableState *mtstate,
|
||||
/* For ExecInsertIndexTuples() to work on the partition's indexes */
|
||||
estate->es_result_relation_info = resultRelInfo;
|
||||
|
||||
/*
|
||||
* If we're capturing transition tuples, we might need to convert from
|
||||
* the partition rowtype to parent rowtype.
|
||||
*/
|
||||
if (mtstate->mt_transition_capture != NULL)
|
||||
{
|
||||
if (resultRelInfo->ri_TrigDesc &&
|
||||
(resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
|
||||
resultRelInfo->ri_TrigDesc->trig_insert_instead_row))
|
||||
{
|
||||
/*
|
||||
* If there are any BEFORE or INSTEAD triggers on the
|
||||
* partition, we'll have to be ready to convert their result
|
||||
* back to tuplestore format.
|
||||
*/
|
||||
mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL;
|
||||
mtstate->mt_transition_capture->tcs_map =
|
||||
mtstate->mt_transition_tupconv_maps[leaf_part_index];
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* Otherwise, just remember the original unconverted tuple, to
|
||||
* avoid a needless round trip conversion.
|
||||
*/
|
||||
mtstate->mt_transition_capture->tcs_original_insert_tuple = tuple;
|
||||
mtstate->mt_transition_capture->tcs_map = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We might need to convert from the parent rowtype to the partition
|
||||
* rowtype.
|
||||
@ -588,7 +618,8 @@ ExecInsert(ModifyTableState *mtstate,
|
||||
}
|
||||
|
||||
/* AFTER ROW INSERT Triggers */
|
||||
ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes);
|
||||
ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes,
|
||||
mtstate->mt_transition_capture);
|
||||
|
||||
list_free(recheckIndexes);
|
||||
|
||||
@ -636,7 +667,8 @@ ExecInsert(ModifyTableState *mtstate,
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
static TupleTableSlot *
|
||||
ExecDelete(ItemPointer tupleid,
|
||||
ExecDelete(ModifyTableState *mtstate,
|
||||
ItemPointer tupleid,
|
||||
HeapTuple oldtuple,
|
||||
TupleTableSlot *planSlot,
|
||||
EPQState *epqstate,
|
||||
@ -813,7 +845,8 @@ ldelete:;
|
||||
(estate->es_processed)++;
|
||||
|
||||
/* AFTER ROW DELETE Triggers */
|
||||
ExecARDeleteTriggers(estate, resultRelInfo, tupleid, oldtuple);
|
||||
ExecARDeleteTriggers(estate, resultRelInfo, tupleid, oldtuple,
|
||||
mtstate->mt_transition_capture);
|
||||
|
||||
/* Process RETURNING if present */
|
||||
if (resultRelInfo->ri_projectReturning)
|
||||
@ -894,7 +927,8 @@ ldelete:;
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
static TupleTableSlot *
|
||||
ExecUpdate(ItemPointer tupleid,
|
||||
ExecUpdate(ModifyTableState *mtstate,
|
||||
ItemPointer tupleid,
|
||||
HeapTuple oldtuple,
|
||||
TupleTableSlot *slot,
|
||||
TupleTableSlot *planSlot,
|
||||
@ -1122,7 +1156,8 @@ lreplace:;
|
||||
|
||||
/* AFTER ROW UPDATE Triggers */
|
||||
ExecARUpdateTriggers(estate, resultRelInfo, tupleid, oldtuple, tuple,
|
||||
recheckIndexes);
|
||||
recheckIndexes,
|
||||
mtstate->mt_transition_capture);
|
||||
|
||||
list_free(recheckIndexes);
|
||||
|
||||
@ -1329,7 +1364,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
|
||||
*/
|
||||
|
||||
/* Execute UPDATE with projection */
|
||||
*returning = ExecUpdate(&tuple.t_self, NULL,
|
||||
*returning = ExecUpdate(mtstate, &tuple.t_self, NULL,
|
||||
mtstate->mt_conflproj, planSlot,
|
||||
&mtstate->mt_epqstate, mtstate->ps.state,
|
||||
canSetTag);
|
||||
@ -1376,20 +1411,31 @@ fireBSTriggers(ModifyTableState *node)
|
||||
}
|
||||
|
||||
/*
|
||||
* Process AFTER EACH STATEMENT triggers
|
||||
* Return the ResultRelInfo for which we will fire AFTER STATEMENT triggers.
|
||||
* This is also the relation into whose tuple format all captured transition
|
||||
* tuples must be converted.
|
||||
*/
|
||||
static void
|
||||
fireASTriggers(ModifyTableState *node)
|
||||
static ResultRelInfo *
|
||||
getASTriggerResultRelInfo(ModifyTableState *node)
|
||||
{
|
||||
ResultRelInfo *resultRelInfo = node->resultRelInfo;
|
||||
|
||||
/*
|
||||
* If the node modifies a partitioned table, we must fire its triggers.
|
||||
* Note that in that case, node->resultRelInfo points to the first leaf
|
||||
* partition, not the root table.
|
||||
*/
|
||||
if (node->rootResultRelInfo != NULL)
|
||||
resultRelInfo = node->rootResultRelInfo;
|
||||
return node->rootResultRelInfo;
|
||||
else
|
||||
return node->resultRelInfo;
|
||||
}
|
||||
|
||||
/*
|
||||
* Process AFTER EACH STATEMENT triggers
|
||||
*/
|
||||
static void
|
||||
fireASTriggers(ModifyTableState *node)
|
||||
{
|
||||
ResultRelInfo *resultRelInfo = getASTriggerResultRelInfo(node);
|
||||
|
||||
switch (node->operation)
|
||||
{
|
||||
@ -1411,6 +1457,72 @@ fireASTriggers(ModifyTableState *node)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Set up the state needed for collecting transition tuples for AFTER
|
||||
* triggers.
|
||||
*/
|
||||
static void
|
||||
ExecSetupTransitionCaptureState(ModifyTableState *mtstate, EState *estate)
|
||||
{
|
||||
ResultRelInfo *targetRelInfo = getASTriggerResultRelInfo(mtstate);
|
||||
int i;
|
||||
|
||||
/* Check for transition tables on the directly targeted relation. */
|
||||
mtstate->mt_transition_capture =
|
||||
MakeTransitionCaptureState(targetRelInfo->ri_TrigDesc);
|
||||
|
||||
/*
|
||||
* If we found that we need to collect transition tuples then we may also
|
||||
* need tuple conversion maps for any children that have TupleDescs that
|
||||
* aren't compatible with the tuplestores.
|
||||
*/
|
||||
if (mtstate->mt_transition_capture != NULL)
|
||||
{
|
||||
ResultRelInfo *resultRelInfos;
|
||||
int numResultRelInfos;
|
||||
|
||||
/* Find the set of partitions so that we can find their TupleDescs. */
|
||||
if (mtstate->mt_partition_dispatch_info != NULL)
|
||||
{
|
||||
/*
|
||||
* For INSERT via partitioned table, so we need TupleDescs based
|
||||
* on the partition routing table.
|
||||
*/
|
||||
resultRelInfos = mtstate->mt_partitions;
|
||||
numResultRelInfos = mtstate->mt_num_partitions;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Otherwise we need the ResultRelInfo for each subplan. */
|
||||
resultRelInfos = mtstate->resultRelInfo;
|
||||
numResultRelInfos = mtstate->mt_nplans;
|
||||
}
|
||||
|
||||
/*
|
||||
* Build array of conversion maps from each child's TupleDesc to the
|
||||
* one used in the tuplestore. The map pointers may be NULL when no
|
||||
* conversion is necessary, which is hopefully a common case for
|
||||
* partitions.
|
||||
*/
|
||||
mtstate->mt_transition_tupconv_maps = (TupleConversionMap **)
|
||||
palloc0(sizeof(TupleConversionMap *) * numResultRelInfos);
|
||||
for (i = 0; i < numResultRelInfos; ++i)
|
||||
{
|
||||
mtstate->mt_transition_tupconv_maps[i] =
|
||||
convert_tuples_by_name(RelationGetDescr(resultRelInfos[i].ri_RelationDesc),
|
||||
RelationGetDescr(targetRelInfo->ri_RelationDesc),
|
||||
gettext_noop("could not convert row type"));
|
||||
}
|
||||
|
||||
/*
|
||||
* Install the conversion map for the first plan for UPDATE and DELETE
|
||||
* operations. It will be advanced each time we switch to the next
|
||||
* plan. (INSERT operations set it every time.)
|
||||
*/
|
||||
mtstate->mt_transition_capture->tcs_map =
|
||||
mtstate->mt_transition_tupconv_maps[0];
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* ExecModifyTable
|
||||
@ -1509,6 +1621,13 @@ ExecModifyTable(ModifyTableState *node)
|
||||
estate->es_result_relation_info = resultRelInfo;
|
||||
EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
|
||||
node->mt_arowmarks[node->mt_whichplan]);
|
||||
if (node->mt_transition_capture != NULL)
|
||||
{
|
||||
/* Prepare to convert transition tuples from this child. */
|
||||
Assert(node->mt_transition_tupconv_maps != NULL);
|
||||
node->mt_transition_capture->tcs_map =
|
||||
node->mt_transition_tupconv_maps[node->mt_whichplan];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else
|
||||
@ -1618,11 +1737,11 @@ ExecModifyTable(ModifyTableState *node)
|
||||
estate, node->canSetTag);
|
||||
break;
|
||||
case CMD_UPDATE:
|
||||
slot = ExecUpdate(tupleid, oldtuple, slot, planSlot,
|
||||
slot = ExecUpdate(node, tupleid, oldtuple, slot, planSlot,
|
||||
&node->mt_epqstate, estate, node->canSetTag);
|
||||
break;
|
||||
case CMD_DELETE:
|
||||
slot = ExecDelete(tupleid, oldtuple, planSlot,
|
||||
slot = ExecDelete(node, tupleid, oldtuple, planSlot,
|
||||
&node->mt_epqstate, estate, node->canSetTag);
|
||||
break;
|
||||
default:
|
||||
@ -1804,6 +1923,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
|
||||
mtstate->mt_partition_tuple_slot = partition_tuple_slot;
|
||||
}
|
||||
|
||||
/* Build state for collecting transition tuples */
|
||||
ExecSetupTransitionCaptureState(mtstate, estate);
|
||||
|
||||
/*
|
||||
* Initialize any WITH CHECK OPTION constraints if needed.
|
||||
*/
|
||||
|
@ -21,6 +21,7 @@ extern List *find_inheritance_children(Oid parentrelId, LOCKMODE lockmode);
|
||||
extern List *find_all_inheritors(Oid parentrelId, LOCKMODE lockmode,
|
||||
List **parents);
|
||||
extern bool has_subclass(Oid relationId);
|
||||
extern bool has_superclass(Oid relationId);
|
||||
extern bool typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId);
|
||||
|
||||
#endif /* PG_INHERITS_FN_H */
|
||||
|
@ -41,6 +41,39 @@ typedef struct TriggerData
|
||||
Tuplestorestate *tg_newtable;
|
||||
} TriggerData;
|
||||
|
||||
/*
|
||||
* Meta-data to control the capture of old and new tuples into transition
|
||||
* tables from child tables.
|
||||
*/
|
||||
typedef struct TransitionCaptureState
|
||||
{
|
||||
/*
|
||||
* Is there at least one trigger specifying each transition relation on
|
||||
* the relation explicitly named in the DML statement or COPY command?
|
||||
*/
|
||||
bool tcs_delete_old_table;
|
||||
bool tcs_update_old_table;
|
||||
bool tcs_update_new_table;
|
||||
bool tcs_insert_new_table;
|
||||
|
||||
/*
|
||||
* For UPDATE and DELETE, AfterTriggerSaveEvent may need to convert the
|
||||
* new and old tuples from a child table's format to the format of the
|
||||
* relation named in a query so that it is compatible with the transition
|
||||
* tuplestores.
|
||||
*/
|
||||
TupleConversionMap *tcs_map;
|
||||
|
||||
/*
|
||||
* For INSERT and COPY, it would be wasteful to convert tuples from child
|
||||
* format to parent format after they have already been converted in the
|
||||
* opposite direction during routing. In that case we bypass conversion
|
||||
* and allow the inserting code (copy.c and nodeModifyTable.c) to provide
|
||||
* the original tuple directly.
|
||||
*/
|
||||
HeapTuple tcs_original_insert_tuple;
|
||||
} TransitionCaptureState;
|
||||
|
||||
/*
|
||||
* TriggerEvent bit flags
|
||||
*
|
||||
@ -127,6 +160,9 @@ extern void RelationBuildTriggers(Relation relation);
|
||||
|
||||
extern TriggerDesc *CopyTriggerDesc(TriggerDesc *trigdesc);
|
||||
|
||||
extern const char *FindTriggerIncompatibleWithInheritance(TriggerDesc *trigdesc);
|
||||
extern TransitionCaptureState *MakeTransitionCaptureState(TriggerDesc *trigdesc);
|
||||
|
||||
extern void FreeTriggerDesc(TriggerDesc *trigdesc);
|
||||
|
||||
extern void ExecBSInsertTriggers(EState *estate,
|
||||
@ -139,7 +175,8 @@ extern TupleTableSlot *ExecBRInsertTriggers(EState *estate,
|
||||
extern void ExecARInsertTriggers(EState *estate,
|
||||
ResultRelInfo *relinfo,
|
||||
HeapTuple trigtuple,
|
||||
List *recheckIndexes);
|
||||
List *recheckIndexes,
|
||||
TransitionCaptureState *transition_capture);
|
||||
extern TupleTableSlot *ExecIRInsertTriggers(EState *estate,
|
||||
ResultRelInfo *relinfo,
|
||||
TupleTableSlot *slot);
|
||||
@ -155,7 +192,8 @@ extern bool ExecBRDeleteTriggers(EState *estate,
|
||||
extern void ExecARDeleteTriggers(EState *estate,
|
||||
ResultRelInfo *relinfo,
|
||||
ItemPointer tupleid,
|
||||
HeapTuple fdw_trigtuple);
|
||||
HeapTuple fdw_trigtuple,
|
||||
TransitionCaptureState *transition_capture);
|
||||
extern bool ExecIRDeleteTriggers(EState *estate,
|
||||
ResultRelInfo *relinfo,
|
||||
HeapTuple trigtuple);
|
||||
@ -174,7 +212,8 @@ extern void ExecARUpdateTriggers(EState *estate,
|
||||
ItemPointer tupleid,
|
||||
HeapTuple fdw_trigtuple,
|
||||
HeapTuple newtuple,
|
||||
List *recheckIndexes);
|
||||
List *recheckIndexes,
|
||||
TransitionCaptureState *transition_capture);
|
||||
extern TupleTableSlot *ExecIRUpdateTriggers(EState *estate,
|
||||
ResultRelInfo *relinfo,
|
||||
HeapTuple trigtuple,
|
||||
|
@ -963,6 +963,10 @@ typedef struct ModifyTableState
|
||||
TupleConversionMap **mt_partition_tupconv_maps;
|
||||
/* Per partition tuple conversion map */
|
||||
TupleTableSlot *mt_partition_tuple_slot;
|
||||
struct TransitionCaptureState *mt_transition_capture;
|
||||
/* controls transition table population */
|
||||
TupleConversionMap **mt_transition_tupconv_maps;
|
||||
/* Per plan/partition tuple conversion */
|
||||
} ModifyTableState;
|
||||
|
||||
/* ----------------
|
||||
|
@ -1793,31 +1793,6 @@ drop table upsert;
|
||||
drop function upsert_before_func();
|
||||
drop function upsert_after_func();
|
||||
--
|
||||
-- Verify that triggers are prevented on partitioned tables if they would
|
||||
-- access row data (ROW and STATEMENT-with-transition-table)
|
||||
--
|
||||
create table my_table (i int) partition by list (i);
|
||||
create table my_table_42 partition of my_table for values in (42);
|
||||
create function my_trigger_function() returns trigger as $$ begin end; $$ language plpgsql;
|
||||
create trigger my_trigger before update on my_table for each row execute procedure my_trigger_function();
|
||||
ERROR: "my_table" is a partitioned table
|
||||
DETAIL: Partitioned tables cannot have ROW triggers.
|
||||
create trigger my_trigger after update on my_table referencing old table as old_table
|
||||
for each statement execute procedure my_trigger_function();
|
||||
ERROR: "my_table" is a partitioned table
|
||||
DETAIL: Triggers on partitioned tables cannot have transition tables.
|
||||
--
|
||||
-- Verify that triggers are allowed on partitions
|
||||
--
|
||||
create trigger my_trigger before update on my_table_42 for each row execute procedure my_trigger_function();
|
||||
drop trigger my_trigger on my_table_42;
|
||||
create trigger my_trigger after update on my_table_42 referencing old table as old_table
|
||||
for each statement execute procedure my_trigger_function();
|
||||
drop trigger my_trigger on my_table_42;
|
||||
drop function my_trigger_function();
|
||||
drop table my_table_42;
|
||||
drop table my_table;
|
||||
--
|
||||
-- Verify that triggers with transition tables are not allowed on
|
||||
-- views
|
||||
--
|
||||
@ -1922,3 +1897,304 @@ copy parted_stmt_trig1(a) from stdin;
|
||||
NOTICE: trigger on parted_stmt_trig1 BEFORE INSERT for ROW
|
||||
NOTICE: trigger on parted_stmt_trig1 AFTER INSERT for ROW
|
||||
drop table parted_stmt_trig, parted2_stmt_trig;
|
||||
--
|
||||
-- Test the interaction between transition tables and both kinds of
|
||||
-- inheritance. We'll dump the contents of the transition tables in a
|
||||
-- format that shows the attribute order, so that we can distinguish
|
||||
-- tuple formats (though not dropped attributes).
|
||||
--
|
||||
create or replace function dump_insert() returns trigger language plpgsql as
|
||||
$$
|
||||
begin
|
||||
raise notice 'trigger = %, new table = %',
|
||||
TG_NAME,
|
||||
(select string_agg(new_table::text, ', ' order by a) from new_table);
|
||||
return null;
|
||||
end;
|
||||
$$;
|
||||
create or replace function dump_update() returns trigger language plpgsql as
|
||||
$$
|
||||
begin
|
||||
raise notice 'trigger = %, old table = %, new table = %',
|
||||
TG_NAME,
|
||||
(select string_agg(old_table::text, ', ' order by a) from old_table),
|
||||
(select string_agg(new_table::text, ', ' order by a) from new_table);
|
||||
return null;
|
||||
end;
|
||||
$$;
|
||||
create or replace function dump_delete() returns trigger language plpgsql as
|
||||
$$
|
||||
begin
|
||||
raise notice 'trigger = %, old table = %',
|
||||
TG_NAME,
|
||||
(select string_agg(old_table::text, ', ' order by a) from old_table);
|
||||
return null;
|
||||
end;
|
||||
$$;
|
||||
--
|
||||
-- Verify behavior of statement triggers on partition hierarchy with
|
||||
-- transition tables. Tuples should appear to each trigger in the
|
||||
-- format of the the relation the trigger is attached to.
|
||||
--
|
||||
-- set up a partition hierarchy with some different TupleDescriptors
|
||||
create table parent (a text, b int) partition by list (a);
|
||||
-- a child matching parent
|
||||
create table child1 partition of parent for values in ('AAA');
|
||||
-- a child with a dropped column
|
||||
create table child2 (x int, a text, b int);
|
||||
alter table child2 drop column x;
|
||||
alter table parent attach partition child2 for values in ('BBB');
|
||||
-- a child with a different column order
|
||||
create table child3 (b int, a text);
|
||||
alter table parent attach partition child3 for values in ('CCC');
|
||||
create trigger parent_insert_trig
|
||||
after insert on parent referencing new table as new_table
|
||||
for each statement execute procedure dump_insert();
|
||||
create trigger parent_update_trig
|
||||
after update on parent referencing old table as old_table new table as new_table
|
||||
for each statement execute procedure dump_update();
|
||||
create trigger parent_delete_trig
|
||||
after delete on parent referencing old table as old_table
|
||||
for each statement execute procedure dump_delete();
|
||||
create trigger child1_insert_trig
|
||||
after insert on child1 referencing new table as new_table
|
||||
for each statement execute procedure dump_insert();
|
||||
create trigger child1_update_trig
|
||||
after update on child1 referencing old table as old_table new table as new_table
|
||||
for each statement execute procedure dump_update();
|
||||
create trigger child1_delete_trig
|
||||
after delete on child1 referencing old table as old_table
|
||||
for each statement execute procedure dump_delete();
|
||||
create trigger child2_insert_trig
|
||||
after insert on child2 referencing new table as new_table
|
||||
for each statement execute procedure dump_insert();
|
||||
create trigger child2_update_trig
|
||||
after update on child2 referencing old table as old_table new table as new_table
|
||||
for each statement execute procedure dump_update();
|
||||
create trigger child2_delete_trig
|
||||
after delete on child2 referencing old table as old_table
|
||||
for each statement execute procedure dump_delete();
|
||||
create trigger child3_insert_trig
|
||||
after insert on child3 referencing new table as new_table
|
||||
for each statement execute procedure dump_insert();
|
||||
create trigger child3_update_trig
|
||||
after update on child3 referencing old table as old_table new table as new_table
|
||||
for each statement execute procedure dump_update();
|
||||
create trigger child3_delete_trig
|
||||
after delete on child3 referencing old table as old_table
|
||||
for each statement execute procedure dump_delete();
|
||||
-- insert directly into children sees respective child-format tuples
|
||||
insert into child1 values ('AAA', 42);
|
||||
NOTICE: trigger = child1_insert_trig, new table = (AAA,42)
|
||||
insert into child2 values ('BBB', 42);
|
||||
NOTICE: trigger = child2_insert_trig, new table = (BBB,42)
|
||||
insert into child3 values (42, 'CCC');
|
||||
NOTICE: trigger = child3_insert_trig, new table = (42,CCC)
|
||||
-- update via parent sees parent-format tuples
|
||||
update parent set b = b + 1;
|
||||
NOTICE: trigger = parent_update_trig, old table = (AAA,42), (BBB,42), (CCC,42), new table = (AAA,43), (BBB,43), (CCC,43)
|
||||
-- delete via parent sees parent-format tuples
|
||||
delete from parent;
|
||||
NOTICE: trigger = parent_delete_trig, old table = (AAA,43), (BBB,43), (CCC,43)
|
||||
-- insert into parent sees parent-format tuples
|
||||
insert into parent values ('AAA', 42);
|
||||
NOTICE: trigger = parent_insert_trig, new table = (AAA,42)
|
||||
insert into parent values ('BBB', 42);
|
||||
NOTICE: trigger = parent_insert_trig, new table = (BBB,42)
|
||||
insert into parent values ('CCC', 42);
|
||||
NOTICE: trigger = parent_insert_trig, new table = (CCC,42)
|
||||
-- delete from children sees respective child-format tuples
|
||||
delete from child1;
|
||||
NOTICE: trigger = child1_delete_trig, old table = (AAA,42)
|
||||
delete from child2;
|
||||
NOTICE: trigger = child2_delete_trig, old table = (BBB,42)
|
||||
delete from child3;
|
||||
NOTICE: trigger = child3_delete_trig, old table = (42,CCC)
|
||||
-- copy into parent sees parent-format tuples
|
||||
copy parent (a, b) from stdin;
|
||||
NOTICE: trigger = parent_insert_trig, new table = (AAA,42), (BBB,42), (CCC,42)
|
||||
-- DML affecting parent sees tuples collected from children even if
|
||||
-- there is no transition table trigger on the children
|
||||
drop trigger child1_insert_trig on child1;
|
||||
drop trigger child1_update_trig on child1;
|
||||
drop trigger child1_delete_trig on child1;
|
||||
drop trigger child2_insert_trig on child2;
|
||||
drop trigger child2_update_trig on child2;
|
||||
drop trigger child2_delete_trig on child2;
|
||||
drop trigger child3_insert_trig on child3;
|
||||
drop trigger child3_update_trig on child3;
|
||||
drop trigger child3_delete_trig on child3;
|
||||
delete from parent;
|
||||
NOTICE: trigger = parent_delete_trig, old table = (AAA,42), (BBB,42), (CCC,42)
|
||||
-- copy into parent sees tuples collected from children even if there
|
||||
-- is no transition-table trigger on the children
|
||||
copy parent (a, b) from stdin;
|
||||
NOTICE: trigger = parent_insert_trig, new table = (AAA,42), (BBB,42), (CCC,42)
|
||||
-- insert into parent with a before trigger on a child tuple before
|
||||
-- insertion, and we capture the newly modified row in parent format
|
||||
create or replace function intercept_insert() returns trigger language plpgsql as
|
||||
$$
|
||||
begin
|
||||
new.b = new.b + 1000;
|
||||
return new;
|
||||
end;
|
||||
$$;
|
||||
create trigger intercept_insert_child3
|
||||
before insert on child3
|
||||
for each row execute procedure intercept_insert();
|
||||
-- insert, parent trigger sees post-modification parent-format tuple
|
||||
insert into parent values ('AAA', 42), ('BBB', 42), ('CCC', 66);
|
||||
NOTICE: trigger = parent_insert_trig, new table = (AAA,42), (BBB,42), (CCC,1066)
|
||||
-- copy, parent trigger sees post-modification parent-format tuple
|
||||
copy parent (a, b) from stdin;
|
||||
NOTICE: trigger = parent_insert_trig, new table = (AAA,42), (BBB,42), (CCC,1234)
|
||||
drop table child1, child2, child3, parent;
|
||||
drop function intercept_insert();
|
||||
--
|
||||
-- Verify prohibition of row triggers with transition triggers on
|
||||
-- partitions
|
||||
--
|
||||
create table parent (a text, b int) partition by list (a);
|
||||
create table child partition of parent for values in ('AAA');
|
||||
-- adding row trigger with transition table fails
|
||||
create trigger child_row_trig
|
||||
after insert on child referencing new table as new_table
|
||||
for each row execute procedure dump_insert();
|
||||
ERROR: ROW triggers with transition tables are not supported on partitions
|
||||
-- detaching it first works
|
||||
alter table parent detach partition child;
|
||||
create trigger child_row_trig
|
||||
after insert on child referencing new table as new_table
|
||||
for each row execute procedure dump_insert();
|
||||
-- but now we're not allowed to reattach it
|
||||
alter table parent attach partition child for values in ('AAA');
|
||||
ERROR: trigger "child_row_trig" prevents table "child" from becoming a partition
|
||||
DETAIL: ROW triggers with transition tables are not supported on partitions
|
||||
-- drop the trigger, and now we're allowed to attach it again
|
||||
drop trigger child_row_trig on child;
|
||||
alter table parent attach partition child for values in ('AAA');
|
||||
drop table child, parent;
|
||||
--
|
||||
-- Verify behavior of statement triggers on (non-partition)
|
||||
-- inheritance hierarchy with transition tables; similar to the
|
||||
-- partition case, except there is no rerouting on insertion and child
|
||||
-- tables can have extra columns
|
||||
--
|
||||
-- set up inheritance hierarchy with different TupleDescriptors
|
||||
create table parent (a text, b int);
|
||||
-- a child matching parent
|
||||
create table child1 () inherits (parent);
|
||||
-- a child with a different column order
|
||||
create table child2 (b int, a text);
|
||||
alter table child2 inherit parent;
|
||||
-- a child with an extra column
|
||||
create table child3 (c text) inherits (parent);
|
||||
create trigger parent_insert_trig
|
||||
after insert on parent referencing new table as new_table
|
||||
for each statement execute procedure dump_insert();
|
||||
create trigger parent_update_trig
|
||||
after update on parent referencing old table as old_table new table as new_table
|
||||
for each statement execute procedure dump_update();
|
||||
create trigger parent_delete_trig
|
||||
after delete on parent referencing old table as old_table
|
||||
for each statement execute procedure dump_delete();
|
||||
create trigger child1_insert_trig
|
||||
after insert on child1 referencing new table as new_table
|
||||
for each statement execute procedure dump_insert();
|
||||
create trigger child1_update_trig
|
||||
after update on child1 referencing old table as old_table new table as new_table
|
||||
for each statement execute procedure dump_update();
|
||||
create trigger child1_delete_trig
|
||||
after delete on child1 referencing old table as old_table
|
||||
for each statement execute procedure dump_delete();
|
||||
create trigger child2_insert_trig
|
||||
after insert on child2 referencing new table as new_table
|
||||
for each statement execute procedure dump_insert();
|
||||
create trigger child2_update_trig
|
||||
after update on child2 referencing old table as old_table new table as new_table
|
||||
for each statement execute procedure dump_update();
|
||||
create trigger child2_delete_trig
|
||||
after delete on child2 referencing old table as old_table
|
||||
for each statement execute procedure dump_delete();
|
||||
create trigger child3_insert_trig
|
||||
after insert on child3 referencing new table as new_table
|
||||
for each statement execute procedure dump_insert();
|
||||
create trigger child3_update_trig
|
||||
after update on child3 referencing old table as old_table new table as new_table
|
||||
for each statement execute procedure dump_update();
|
||||
create trigger child3_delete_trig
|
||||
after delete on child3 referencing old table as old_table
|
||||
for each statement execute procedure dump_delete();
|
||||
-- insert directly into children sees respective child-format tuples
|
||||
insert into child1 values ('AAA', 42);
|
||||
NOTICE: trigger = child1_insert_trig, new table = (AAA,42)
|
||||
insert into child2 values (42, 'BBB');
|
||||
NOTICE: trigger = child2_insert_trig, new table = (42,BBB)
|
||||
insert into child3 values ('CCC', 42, 'foo');
|
||||
NOTICE: trigger = child3_insert_trig, new table = (CCC,42,foo)
|
||||
-- update via parent sees parent-format tuples
|
||||
update parent set b = b + 1;
|
||||
NOTICE: trigger = parent_update_trig, old table = (AAA,42), (BBB,42), (CCC,42), new table = (AAA,43), (BBB,43), (CCC,43)
|
||||
-- delete via parent sees parent-format tuples
|
||||
delete from parent;
|
||||
NOTICE: trigger = parent_delete_trig, old table = (AAA,43), (BBB,43), (CCC,43)
|
||||
-- reinsert values into children for next test...
|
||||
insert into child1 values ('AAA', 42);
|
||||
NOTICE: trigger = child1_insert_trig, new table = (AAA,42)
|
||||
insert into child2 values (42, 'BBB');
|
||||
NOTICE: trigger = child2_insert_trig, new table = (42,BBB)
|
||||
insert into child3 values ('CCC', 42, 'foo');
|
||||
NOTICE: trigger = child3_insert_trig, new table = (CCC,42,foo)
|
||||
-- delete from children sees respective child-format tuples
|
||||
delete from child1;
|
||||
NOTICE: trigger = child1_delete_trig, old table = (AAA,42)
|
||||
delete from child2;
|
||||
NOTICE: trigger = child2_delete_trig, old table = (42,BBB)
|
||||
delete from child3;
|
||||
NOTICE: trigger = child3_delete_trig, old table = (CCC,42,foo)
|
||||
-- copy into parent sees parent-format tuples (no rerouting, so these
|
||||
-- are really inserted into the parent)
|
||||
copy parent (a, b) from stdin;
|
||||
NOTICE: trigger = parent_insert_trig, new table = (AAA,42), (BBB,42), (CCC,42)
|
||||
-- DML affecting parent sees tuples collected from children even if
|
||||
-- there is no transition table trigger on the children
|
||||
drop trigger child1_insert_trig on child1;
|
||||
drop trigger child1_update_trig on child1;
|
||||
drop trigger child1_delete_trig on child1;
|
||||
drop trigger child2_insert_trig on child2;
|
||||
drop trigger child2_update_trig on child2;
|
||||
drop trigger child2_delete_trig on child2;
|
||||
drop trigger child3_insert_trig on child3;
|
||||
drop trigger child3_update_trig on child3;
|
||||
drop trigger child3_delete_trig on child3;
|
||||
delete from parent;
|
||||
NOTICE: trigger = parent_delete_trig, old table = (AAA,42), (BBB,42), (CCC,42)
|
||||
drop table child1, child2, child3, parent;
|
||||
--
|
||||
-- Verify prohibition of row triggers with transition triggers on
|
||||
-- inheritance children
|
||||
--
|
||||
create table parent (a text, b int);
|
||||
create table child () inherits (parent);
|
||||
-- adding row trigger with transition table fails
|
||||
create trigger child_row_trig
|
||||
after insert on child referencing new table as new_table
|
||||
for each row execute procedure dump_insert();
|
||||
ERROR: ROW triggers with transition tables are not supported on inheritance children
|
||||
-- disinheriting it first works
|
||||
alter table child no inherit parent;
|
||||
create trigger child_row_trig
|
||||
after insert on child referencing new table as new_table
|
||||
for each row execute procedure dump_insert();
|
||||
-- but now we're not allowed to make it inherit anymore
|
||||
alter table child inherit parent;
|
||||
ERROR: trigger "child_row_trig" prevents table "child" from becoming an inheritance child
|
||||
DETAIL: ROW triggers with transition tables are not supported in inheritance hierarchies
|
||||
-- drop the trigger, and now we're allowed to make it inherit again
|
||||
drop trigger child_row_trig on child;
|
||||
alter table child inherit parent;
|
||||
drop table child, parent;
|
||||
-- cleanup
|
||||
drop function dump_insert();
|
||||
drop function dump_update();
|
||||
drop function dump_delete();
|
||||
|
@ -1272,30 +1272,6 @@ drop table upsert;
|
||||
drop function upsert_before_func();
|
||||
drop function upsert_after_func();
|
||||
|
||||
--
|
||||
-- Verify that triggers are prevented on partitioned tables if they would
|
||||
-- access row data (ROW and STATEMENT-with-transition-table)
|
||||
--
|
||||
|
||||
create table my_table (i int) partition by list (i);
|
||||
create table my_table_42 partition of my_table for values in (42);
|
||||
create function my_trigger_function() returns trigger as $$ begin end; $$ language plpgsql;
|
||||
create trigger my_trigger before update on my_table for each row execute procedure my_trigger_function();
|
||||
create trigger my_trigger after update on my_table referencing old table as old_table
|
||||
for each statement execute procedure my_trigger_function();
|
||||
|
||||
--
|
||||
-- Verify that triggers are allowed on partitions
|
||||
--
|
||||
create trigger my_trigger before update on my_table_42 for each row execute procedure my_trigger_function();
|
||||
drop trigger my_trigger on my_table_42;
|
||||
create trigger my_trigger after update on my_table_42 referencing old table as old_table
|
||||
for each statement execute procedure my_trigger_function();
|
||||
drop trigger my_trigger on my_table_42;
|
||||
drop function my_trigger_function();
|
||||
drop table my_table_42;
|
||||
drop table my_table;
|
||||
|
||||
--
|
||||
-- Verify that triggers with transition tables are not allowed on
|
||||
-- views
|
||||
@ -1391,3 +1367,344 @@ copy parted_stmt_trig1(a) from stdin;
|
||||
\.
|
||||
|
||||
drop table parted_stmt_trig, parted2_stmt_trig;
|
||||
|
||||
--
|
||||
-- Test the interaction between transition tables and both kinds of
|
||||
-- inheritance. We'll dump the contents of the transition tables in a
|
||||
-- format that shows the attribute order, so that we can distinguish
|
||||
-- tuple formats (though not dropped attributes).
|
||||
--
|
||||
|
||||
create or replace function dump_insert() returns trigger language plpgsql as
|
||||
$$
|
||||
begin
|
||||
raise notice 'trigger = %, new table = %',
|
||||
TG_NAME,
|
||||
(select string_agg(new_table::text, ', ' order by a) from new_table);
|
||||
return null;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create or replace function dump_update() returns trigger language plpgsql as
|
||||
$$
|
||||
begin
|
||||
raise notice 'trigger = %, old table = %, new table = %',
|
||||
TG_NAME,
|
||||
(select string_agg(old_table::text, ', ' order by a) from old_table),
|
||||
(select string_agg(new_table::text, ', ' order by a) from new_table);
|
||||
return null;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create or replace function dump_delete() returns trigger language plpgsql as
|
||||
$$
|
||||
begin
|
||||
raise notice 'trigger = %, old table = %',
|
||||
TG_NAME,
|
||||
(select string_agg(old_table::text, ', ' order by a) from old_table);
|
||||
return null;
|
||||
end;
|
||||
$$;
|
||||
|
||||
--
|
||||
-- Verify behavior of statement triggers on partition hierarchy with
|
||||
-- transition tables. Tuples should appear to each trigger in the
|
||||
-- format of the the relation the trigger is attached to.
|
||||
--
|
||||
|
||||
-- set up a partition hierarchy with some different TupleDescriptors
|
||||
create table parent (a text, b int) partition by list (a);
|
||||
|
||||
-- a child matching parent
|
||||
create table child1 partition of parent for values in ('AAA');
|
||||
|
||||
-- a child with a dropped column
|
||||
create table child2 (x int, a text, b int);
|
||||
alter table child2 drop column x;
|
||||
alter table parent attach partition child2 for values in ('BBB');
|
||||
|
||||
-- a child with a different column order
|
||||
create table child3 (b int, a text);
|
||||
alter table parent attach partition child3 for values in ('CCC');
|
||||
|
||||
create trigger parent_insert_trig
|
||||
after insert on parent referencing new table as new_table
|
||||
for each statement execute procedure dump_insert();
|
||||
create trigger parent_update_trig
|
||||
after update on parent referencing old table as old_table new table as new_table
|
||||
for each statement execute procedure dump_update();
|
||||
create trigger parent_delete_trig
|
||||
after delete on parent referencing old table as old_table
|
||||
for each statement execute procedure dump_delete();
|
||||
|
||||
create trigger child1_insert_trig
|
||||
after insert on child1 referencing new table as new_table
|
||||
for each statement execute procedure dump_insert();
|
||||
create trigger child1_update_trig
|
||||
after update on child1 referencing old table as old_table new table as new_table
|
||||
for each statement execute procedure dump_update();
|
||||
create trigger child1_delete_trig
|
||||
after delete on child1 referencing old table as old_table
|
||||
for each statement execute procedure dump_delete();
|
||||
|
||||
create trigger child2_insert_trig
|
||||
after insert on child2 referencing new table as new_table
|
||||
for each statement execute procedure dump_insert();
|
||||
create trigger child2_update_trig
|
||||
after update on child2 referencing old table as old_table new table as new_table
|
||||
for each statement execute procedure dump_update();
|
||||
create trigger child2_delete_trig
|
||||
after delete on child2 referencing old table as old_table
|
||||
for each statement execute procedure dump_delete();
|
||||
|
||||
create trigger child3_insert_trig
|
||||
after insert on child3 referencing new table as new_table
|
||||
for each statement execute procedure dump_insert();
|
||||
create trigger child3_update_trig
|
||||
after update on child3 referencing old table as old_table new table as new_table
|
||||
for each statement execute procedure dump_update();
|
||||
create trigger child3_delete_trig
|
||||
after delete on child3 referencing old table as old_table
|
||||
for each statement execute procedure dump_delete();
|
||||
|
||||
-- insert directly into children sees respective child-format tuples
|
||||
insert into child1 values ('AAA', 42);
|
||||
insert into child2 values ('BBB', 42);
|
||||
insert into child3 values (42, 'CCC');
|
||||
|
||||
-- update via parent sees parent-format tuples
|
||||
update parent set b = b + 1;
|
||||
|
||||
-- delete via parent sees parent-format tuples
|
||||
delete from parent;
|
||||
|
||||
-- insert into parent sees parent-format tuples
|
||||
insert into parent values ('AAA', 42);
|
||||
insert into parent values ('BBB', 42);
|
||||
insert into parent values ('CCC', 42);
|
||||
|
||||
-- delete from children sees respective child-format tuples
|
||||
delete from child1;
|
||||
delete from child2;
|
||||
delete from child3;
|
||||
|
||||
-- copy into parent sees parent-format tuples
|
||||
copy parent (a, b) from stdin;
|
||||
AAA 42
|
||||
BBB 42
|
||||
CCC 42
|
||||
\.
|
||||
|
||||
-- DML affecting parent sees tuples collected from children even if
|
||||
-- there is no transition table trigger on the children
|
||||
drop trigger child1_insert_trig on child1;
|
||||
drop trigger child1_update_trig on child1;
|
||||
drop trigger child1_delete_trig on child1;
|
||||
drop trigger child2_insert_trig on child2;
|
||||
drop trigger child2_update_trig on child2;
|
||||
drop trigger child2_delete_trig on child2;
|
||||
drop trigger child3_insert_trig on child3;
|
||||
drop trigger child3_update_trig on child3;
|
||||
drop trigger child3_delete_trig on child3;
|
||||
delete from parent;
|
||||
|
||||
-- copy into parent sees tuples collected from children even if there
|
||||
-- is no transition-table trigger on the children
|
||||
copy parent (a, b) from stdin;
|
||||
AAA 42
|
||||
BBB 42
|
||||
CCC 42
|
||||
\.
|
||||
|
||||
-- insert into parent with a before trigger on a child tuple before
|
||||
-- insertion, and we capture the newly modified row in parent format
|
||||
create or replace function intercept_insert() returns trigger language plpgsql as
|
||||
$$
|
||||
begin
|
||||
new.b = new.b + 1000;
|
||||
return new;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create trigger intercept_insert_child3
|
||||
before insert on child3
|
||||
for each row execute procedure intercept_insert();
|
||||
|
||||
|
||||
-- insert, parent trigger sees post-modification parent-format tuple
|
||||
insert into parent values ('AAA', 42), ('BBB', 42), ('CCC', 66);
|
||||
|
||||
-- copy, parent trigger sees post-modification parent-format tuple
|
||||
copy parent (a, b) from stdin;
|
||||
AAA 42
|
||||
BBB 42
|
||||
CCC 234
|
||||
\.
|
||||
|
||||
drop table child1, child2, child3, parent;
|
||||
drop function intercept_insert();
|
||||
|
||||
--
|
||||
-- Verify prohibition of row triggers with transition triggers on
|
||||
-- partitions
|
||||
--
|
||||
create table parent (a text, b int) partition by list (a);
|
||||
create table child partition of parent for values in ('AAA');
|
||||
|
||||
-- adding row trigger with transition table fails
|
||||
create trigger child_row_trig
|
||||
after insert on child referencing new table as new_table
|
||||
for each row execute procedure dump_insert();
|
||||
|
||||
-- detaching it first works
|
||||
alter table parent detach partition child;
|
||||
|
||||
create trigger child_row_trig
|
||||
after insert on child referencing new table as new_table
|
||||
for each row execute procedure dump_insert();
|
||||
|
||||
-- but now we're not allowed to reattach it
|
||||
alter table parent attach partition child for values in ('AAA');
|
||||
|
||||
-- drop the trigger, and now we're allowed to attach it again
|
||||
drop trigger child_row_trig on child;
|
||||
alter table parent attach partition child for values in ('AAA');
|
||||
|
||||
drop table child, parent;
|
||||
|
||||
--
|
||||
-- Verify behavior of statement triggers on (non-partition)
|
||||
-- inheritance hierarchy with transition tables; similar to the
|
||||
-- partition case, except there is no rerouting on insertion and child
|
||||
-- tables can have extra columns
|
||||
--
|
||||
|
||||
-- set up inheritance hierarchy with different TupleDescriptors
|
||||
create table parent (a text, b int);
|
||||
|
||||
-- a child matching parent
|
||||
create table child1 () inherits (parent);
|
||||
|
||||
-- a child with a different column order
|
||||
create table child2 (b int, a text);
|
||||
alter table child2 inherit parent;
|
||||
|
||||
-- a child with an extra column
|
||||
create table child3 (c text) inherits (parent);
|
||||
|
||||
create trigger parent_insert_trig
|
||||
after insert on parent referencing new table as new_table
|
||||
for each statement execute procedure dump_insert();
|
||||
create trigger parent_update_trig
|
||||
after update on parent referencing old table as old_table new table as new_table
|
||||
for each statement execute procedure dump_update();
|
||||
create trigger parent_delete_trig
|
||||
after delete on parent referencing old table as old_table
|
||||
for each statement execute procedure dump_delete();
|
||||
|
||||
create trigger child1_insert_trig
|
||||
after insert on child1 referencing new table as new_table
|
||||
for each statement execute procedure dump_insert();
|
||||
create trigger child1_update_trig
|
||||
after update on child1 referencing old table as old_table new table as new_table
|
||||
for each statement execute procedure dump_update();
|
||||
create trigger child1_delete_trig
|
||||
after delete on child1 referencing old table as old_table
|
||||
for each statement execute procedure dump_delete();
|
||||
|
||||
create trigger child2_insert_trig
|
||||
after insert on child2 referencing new table as new_table
|
||||
for each statement execute procedure dump_insert();
|
||||
create trigger child2_update_trig
|
||||
after update on child2 referencing old table as old_table new table as new_table
|
||||
for each statement execute procedure dump_update();
|
||||
create trigger child2_delete_trig
|
||||
after delete on child2 referencing old table as old_table
|
||||
for each statement execute procedure dump_delete();
|
||||
|
||||
create trigger child3_insert_trig
|
||||
after insert on child3 referencing new table as new_table
|
||||
for each statement execute procedure dump_insert();
|
||||
create trigger child3_update_trig
|
||||
after update on child3 referencing old table as old_table new table as new_table
|
||||
for each statement execute procedure dump_update();
|
||||
create trigger child3_delete_trig
|
||||
after delete on child3 referencing old table as old_table
|
||||
for each statement execute procedure dump_delete();
|
||||
|
||||
-- insert directly into children sees respective child-format tuples
|
||||
insert into child1 values ('AAA', 42);
|
||||
insert into child2 values (42, 'BBB');
|
||||
insert into child3 values ('CCC', 42, 'foo');
|
||||
|
||||
-- update via parent sees parent-format tuples
|
||||
update parent set b = b + 1;
|
||||
|
||||
-- delete via parent sees parent-format tuples
|
||||
delete from parent;
|
||||
|
||||
-- reinsert values into children for next test...
|
||||
insert into child1 values ('AAA', 42);
|
||||
insert into child2 values (42, 'BBB');
|
||||
insert into child3 values ('CCC', 42, 'foo');
|
||||
|
||||
-- delete from children sees respective child-format tuples
|
||||
delete from child1;
|
||||
delete from child2;
|
||||
delete from child3;
|
||||
|
||||
-- copy into parent sees parent-format tuples (no rerouting, so these
|
||||
-- are really inserted into the parent)
|
||||
copy parent (a, b) from stdin;
|
||||
AAA 42
|
||||
BBB 42
|
||||
CCC 42
|
||||
\.
|
||||
|
||||
-- DML affecting parent sees tuples collected from children even if
|
||||
-- there is no transition table trigger on the children
|
||||
drop trigger child1_insert_trig on child1;
|
||||
drop trigger child1_update_trig on child1;
|
||||
drop trigger child1_delete_trig on child1;
|
||||
drop trigger child2_insert_trig on child2;
|
||||
drop trigger child2_update_trig on child2;
|
||||
drop trigger child2_delete_trig on child2;
|
||||
drop trigger child3_insert_trig on child3;
|
||||
drop trigger child3_update_trig on child3;
|
||||
drop trigger child3_delete_trig on child3;
|
||||
delete from parent;
|
||||
|
||||
drop table child1, child2, child3, parent;
|
||||
|
||||
--
|
||||
-- Verify prohibition of row triggers with transition triggers on
|
||||
-- inheritance children
|
||||
--
|
||||
create table parent (a text, b int);
|
||||
create table child () inherits (parent);
|
||||
|
||||
-- adding row trigger with transition table fails
|
||||
create trigger child_row_trig
|
||||
after insert on child referencing new table as new_table
|
||||
for each row execute procedure dump_insert();
|
||||
|
||||
-- disinheriting it first works
|
||||
alter table child no inherit parent;
|
||||
|
||||
create trigger child_row_trig
|
||||
after insert on child referencing new table as new_table
|
||||
for each row execute procedure dump_insert();
|
||||
|
||||
-- but now we're not allowed to make it inherit anymore
|
||||
alter table child inherit parent;
|
||||
|
||||
-- drop the trigger, and now we're allowed to make it inherit again
|
||||
drop trigger child_row_trig on child;
|
||||
alter table child inherit parent;
|
||||
|
||||
drop table child, parent;
|
||||
|
||||
-- cleanup
|
||||
drop function dump_insert();
|
||||
drop function dump_update();
|
||||
drop function dump_delete();
|
||||
|
Loading…
x
Reference in New Issue
Block a user