Commit 501ed02c authored by Andrew Gierth's avatar Andrew Gierth

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
parent 99255d73
...@@ -458,6 +458,20 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</ ...@@ -458,6 +458,20 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</
rows. rows.
</para> </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> <para>
In <productname>PostgreSQL</productname> versions before 7.3, it was In <productname>PostgreSQL</productname> versions before 7.3, it was
necessary to declare trigger functions as returning the placeholder necessary to declare trigger functions as returning the placeholder
......
...@@ -273,6 +273,30 @@ has_subclass(Oid relationId) ...@@ -273,6 +273,30 @@ has_subclass(Oid relationId)
return result; 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 * Given two type OIDs, determine whether the first is a complex type
......
...@@ -171,6 +171,8 @@ typedef struct CopyStateData ...@@ -171,6 +171,8 @@ typedef struct CopyStateData
ResultRelInfo *partitions; /* Per partition result relation */ ResultRelInfo *partitions; /* Per partition result relation */
TupleConversionMap **partition_tupconv_maps; TupleConversionMap **partition_tupconv_maps;
TupleTableSlot *partition_tuple_slot; TupleTableSlot *partition_tuple_slot;
TransitionCaptureState *transition_capture;
TupleConversionMap **transition_tupconv_maps;
/* /*
* These variables are used to reduce overhead in textual COPY FROM. * These variables are used to reduce overhead in textual COPY FROM.
...@@ -1436,6 +1438,36 @@ BeginCopy(ParseState *pstate, ...@@ -1436,6 +1438,36 @@ BeginCopy(ParseState *pstate,
cstate->num_partitions = num_partitions; cstate->num_partitions = num_partitions;
cstate->partition_tupconv_maps = partition_tupconv_maps; cstate->partition_tupconv_maps = partition_tupconv_maps;
cstate->partition_tuple_slot = partition_tuple_slot; 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 else
...@@ -2591,6 +2623,35 @@ CopyFrom(CopyState cstate) ...@@ -2591,6 +2623,35 @@ CopyFrom(CopyState cstate)
*/ */
estate->es_result_relation_info = resultRelInfo; 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 * We might need to convert from the parent rowtype to the
* partition rowtype. * partition rowtype.
...@@ -2703,7 +2764,7 @@ CopyFrom(CopyState cstate) ...@@ -2703,7 +2764,7 @@ CopyFrom(CopyState cstate)
/* AFTER ROW INSERT Triggers */ /* AFTER ROW INSERT Triggers */
ExecARInsertTriggers(estate, resultRelInfo, tuple, ExecARInsertTriggers(estate, resultRelInfo, tuple,
recheckIndexes); recheckIndexes, cstate->transition_capture);
list_free(recheckIndexes); list_free(recheckIndexes);
} }
...@@ -2856,7 +2917,7 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid, ...@@ -2856,7 +2917,7 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
estate, false, NULL, NIL); estate, false, NULL, NIL);
ExecARInsertTriggers(estate, resultRelInfo, ExecARInsertTriggers(estate, resultRelInfo,
bufferedTuples[i], bufferedTuples[i],
recheckIndexes); recheckIndexes, NULL);
list_free(recheckIndexes); list_free(recheckIndexes);
} }
} }
...@@ -2866,14 +2927,15 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid, ...@@ -2866,14 +2927,15 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
* anyway. * anyway.
*/ */
else if (resultRelInfo->ri_TrigDesc != NULL && 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++) for (i = 0; i < nBufferedTuples; i++)
{ {
cstate->cur_lineno = firstBufferedLineNo + i; cstate->cur_lineno = firstBufferedLineNo + i;
ExecARInsertTriggers(estate, resultRelInfo, ExecARInsertTriggers(estate, resultRelInfo,
bufferedTuples[i], bufferedTuples[i],
NIL); NIL, NULL);
} }
} }
......
...@@ -10933,6 +10933,7 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode) ...@@ -10933,6 +10933,7 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
Relation parent_rel; Relation parent_rel;
List *children; List *children;
ObjectAddress address; ObjectAddress address;
const char *trigger_name;
/* /*
* A self-exclusive lock is needed here. See the similar case in * A self-exclusive lock is needed here. See the similar case in
...@@ -11014,6 +11015,19 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode) ...@@ -11014,6 +11015,19 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
RelationGetRelationName(child_rel), RelationGetRelationName(child_rel),
RelationGetRelationName(parent_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 */ /* OK to create inheritance */
CreateInheritance(child_rel, parent_rel); CreateInheritance(child_rel, parent_rel);
...@@ -13418,6 +13432,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd) ...@@ -13418,6 +13432,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
TupleDesc tupleDesc; TupleDesc tupleDesc;
bool skip_validate = false; bool skip_validate = false;
ObjectAddress address; ObjectAddress address;
const char *trigger_name;
attachRel = heap_openrv(cmd->name, AccessExclusiveLock); attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
...@@ -13547,6 +13562,19 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd) ...@@ -13547,6 +13562,19 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
errdetail("New partition should contain only the columns present in parent."))); 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 */ /* OK to create inheritance. Rest of the checks performed there */
CreateInheritance(attachRel, rel); CreateInheritance(attachRel, rel);
......
This diff is collapsed.
...@@ -3198,7 +3198,7 @@ EvalPlanQualEnd(EPQState *epqstate) ...@@ -3198,7 +3198,7 @@ EvalPlanQualEnd(EPQState *epqstate)
* 'tup_conv_maps' receives an array of TupleConversionMap objects with one * 'tup_conv_maps' receives an array of TupleConversionMap objects with one
* entry for every leaf partition (required to convert input tuple based * 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 * 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 * 'partition_tuple_slot' receives a standalone TupleTableSlot to be used
* to manipulate any given leaf partition's rowtype after that partition * to manipulate any given leaf partition's rowtype after that partition
* is chosen by tuple-routing. * is chosen by tuple-routing.
......
...@@ -417,7 +417,7 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot) ...@@ -417,7 +417,7 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
/* AFTER ROW INSERT Triggers */ /* AFTER ROW INSERT Triggers */
ExecARInsertTriggers(estate, resultRelInfo, tuple, ExecARInsertTriggers(estate, resultRelInfo, tuple,
recheckIndexes); recheckIndexes, NULL);
list_free(recheckIndexes); list_free(recheckIndexes);
} }
...@@ -479,7 +479,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, ...@@ -479,7 +479,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
/* AFTER ROW UPDATE Triggers */ /* AFTER ROW UPDATE Triggers */
ExecARUpdateTriggers(estate, resultRelInfo, ExecARUpdateTriggers(estate, resultRelInfo,
&searchslot->tts_tuple->t_self, &searchslot->tts_tuple->t_self,
NULL, tuple, recheckIndexes); NULL, tuple, recheckIndexes, NULL);
list_free(recheckIndexes); list_free(recheckIndexes);
} }
...@@ -522,7 +522,7 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate, ...@@ -522,7 +522,7 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate,
/* AFTER ROW DELETE Triggers */ /* AFTER ROW DELETE Triggers */
ExecARDeleteTriggers(estate, resultRelInfo, ExecARDeleteTriggers(estate, resultRelInfo,
&searchslot->tts_tuple->t_self, NULL); &searchslot->tts_tuple->t_self, NULL, NULL);
list_free(recheckIndexes); list_free(recheckIndexes);
} }
......
...@@ -313,6 +313,36 @@ ExecInsert(ModifyTableState *mtstate, ...@@ -313,6 +313,36 @@ ExecInsert(ModifyTableState *mtstate,
/* For ExecInsertIndexTuples() to work on the partition's indexes */ /* For ExecInsertIndexTuples() to work on the partition's indexes */
estate->es_result_relation_info = resultRelInfo; 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 * We might need to convert from the parent rowtype to the partition
* rowtype. * rowtype.
...@@ -588,7 +618,8 @@ ExecInsert(ModifyTableState *mtstate, ...@@ -588,7 +618,8 @@ ExecInsert(ModifyTableState *mtstate,
} }
/* AFTER ROW INSERT Triggers */ /* AFTER ROW INSERT Triggers */
ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes); ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes,
mtstate->mt_transition_capture);
list_free(recheckIndexes); list_free(recheckIndexes);
...@@ -636,7 +667,8 @@ ExecInsert(ModifyTableState *mtstate, ...@@ -636,7 +667,8 @@ ExecInsert(ModifyTableState *mtstate,
* ---------------------------------------------------------------- * ----------------------------------------------------------------
*/ */
static TupleTableSlot * static TupleTableSlot *
ExecDelete(ItemPointer tupleid, ExecDelete(ModifyTableState *mtstate,
ItemPointer tupleid,
HeapTuple oldtuple, HeapTuple oldtuple,
TupleTableSlot *planSlot, TupleTableSlot *planSlot,
EPQState *epqstate, EPQState *epqstate,
...@@ -813,7 +845,8 @@ ldelete:; ...@@ -813,7 +845,8 @@ ldelete:;
(estate->es_processed)++; (estate->es_processed)++;
/* AFTER ROW DELETE Triggers */ /* AFTER ROW DELETE Triggers */
ExecARDeleteTriggers(estate, resultRelInfo, tupleid, oldtuple); ExecARDeleteTriggers(estate, resultRelInfo, tupleid, oldtuple,
mtstate->mt_transition_capture);
/* Process RETURNING if present */ /* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning) if (resultRelInfo->ri_projectReturning)
...@@ -894,7 +927,8 @@ ldelete:; ...@@ -894,7 +927,8 @@ ldelete:;
* ---------------------------------------------------------------- * ----------------------------------------------------------------
*/ */
static TupleTableSlot * static TupleTableSlot *
ExecUpdate(ItemPointer tupleid, ExecUpdate(ModifyTableState *mtstate,
ItemPointer tupleid,
HeapTuple oldtuple, HeapTuple oldtuple,
TupleTableSlot *slot, TupleTableSlot *slot,
TupleTableSlot *planSlot, TupleTableSlot *planSlot,
...@@ -1122,7 +1156,8 @@ lreplace:; ...@@ -1122,7 +1156,8 @@ lreplace:;
/* AFTER ROW UPDATE Triggers */ /* AFTER ROW UPDATE Triggers */
ExecARUpdateTriggers(estate, resultRelInfo, tupleid, oldtuple, tuple, ExecARUpdateTriggers(estate, resultRelInfo, tupleid, oldtuple, tuple,
recheckIndexes); recheckIndexes,
mtstate->mt_transition_capture);
list_free(recheckIndexes); list_free(recheckIndexes);
...@@ -1329,7 +1364,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, ...@@ -1329,7 +1364,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
*/ */
/* Execute UPDATE with projection */ /* Execute UPDATE with projection */
*returning = ExecUpdate(&tuple.t_self, NULL, *returning = ExecUpdate(mtstate, &tuple.t_self, NULL,
mtstate->mt_conflproj, planSlot, mtstate->mt_conflproj, planSlot,
&mtstate->mt_epqstate, mtstate->ps.state, &mtstate->mt_epqstate, mtstate->ps.state,
canSetTag); canSetTag);
...@@ -1376,20 +1411,31 @@ fireBSTriggers(ModifyTableState *node) ...@@ -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 static ResultRelInfo *
fireASTriggers(ModifyTableState *node) getASTriggerResultRelInfo(ModifyTableState *node)
{ {
ResultRelInfo *resultRelInfo = node->resultRelInfo;
/* /*
* If the node modifies a partitioned table, we must fire its triggers. * If the node modifies a partitioned table, we must fire its triggers.
* Note that in that case, node->resultRelInfo points to the first leaf * Note that in that case, node->resultRelInfo points to the first leaf
* partition, not the root table. * partition, not the root table.
*/ */
if (node->rootResultRelInfo != NULL) 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) switch (node->operation)
{ {
...@@ -1411,6 +1457,72 @@ fireASTriggers(ModifyTableState *node) ...@@ -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 * ExecModifyTable
...@@ -1509,6 +1621,13 @@ ExecModifyTable(ModifyTableState *node) ...@@ -1509,6 +1621,13 @@ ExecModifyTable(ModifyTableState *node)
estate->es_result_relation_info = resultRelInfo; estate->es_result_relation_info = resultRelInfo;
EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan, EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
node->mt_arowmarks[node->mt_whichplan]); 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; continue;
} }
else else
...@@ -1618,11 +1737,11 @@ ExecModifyTable(ModifyTableState *node) ...@@ -1618,11 +1737,11 @@ ExecModifyTable(ModifyTableState *node)
estate, node->canSetTag); estate, node->canSetTag);
break; break;
case CMD_UPDATE: case CMD_UPDATE:
slot = ExecUpdate(tupleid, oldtuple, slot, planSlot, slot = ExecUpdate(node, tupleid, oldtuple, slot, planSlot,
&node->mt_epqstate, estate, node->canSetTag); &node->mt_epqstate, estate, node->canSetTag);
break; break;
case CMD_DELETE: case CMD_DELETE:
slot = ExecDelete(tupleid, oldtuple, planSlot, slot = ExecDelete(node, tupleid, oldtuple, planSlot,
&node->mt_epqstate, estate, node->canSetTag); &node->mt_epqstate, estate, node->canSetTag);
break; break;
default: default:
...@@ -1804,6 +1923,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) ...@@ -1804,6 +1923,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_partition_tuple_slot = partition_tuple_slot; 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. * Initialize any WITH CHECK OPTION constraints if needed.
*/ */
......
...@@ -21,6 +21,7 @@ extern List *find_inheritance_children(Oid parentrelId, LOCKMODE lockmode); ...@@ -21,6 +21,7 @@ extern List *find_inheritance_children(Oid parentrelId, LOCKMODE lockmode);
extern List *find_all_inheritors(Oid parentrelId, LOCKMODE lockmode, extern List *find_all_inheritors(Oid parentrelId, LOCKMODE lockmode,
List **parents); List **parents);
extern bool has_subclass(Oid relationId); extern bool has_subclass(Oid relationId);
extern bool has_superclass(Oid relationId);
extern bool typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId); extern bool typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId);
#endif /* PG_INHERITS_FN_H */ #endif /* PG_INHERITS_FN_H */
...@@ -41,6 +41,39 @@ typedef struct TriggerData ...@@ -41,6 +41,39 @@ typedef struct TriggerData
Tuplestorestate *tg_newtable; Tuplestorestate *tg_newtable;
} TriggerData; } 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 * TriggerEvent bit flags
* *
...@@ -127,6 +160,9 @@ extern void RelationBuildTriggers(Relation relation); ...@@ -127,6 +160,9 @@ extern void RelationBuildTriggers(Relation relation);
extern TriggerDesc *CopyTriggerDesc(TriggerDesc *trigdesc); extern TriggerDesc *CopyTriggerDesc(TriggerDesc *trigdesc);
extern const char *FindTriggerIncompatibleWithInheritance(TriggerDesc *trigdesc);
extern TransitionCaptureState *MakeTransitionCaptureState(TriggerDesc *trigdesc);
extern void FreeTriggerDesc(TriggerDesc *trigdesc); extern void FreeTriggerDesc(TriggerDesc *trigdesc);
extern void ExecBSInsertTriggers(EState *estate, extern void ExecBSInsertTriggers(EState *estate,
...@@ -139,7 +175,8 @@ extern TupleTableSlot *ExecBRInsertTriggers(EState *estate, ...@@ -139,7 +175,8 @@ extern TupleTableSlot *ExecBRInsertTriggers(EState *estate,
extern void ExecARInsertTriggers(EState *estate, extern void ExecARInsertTriggers(EState *estate,
ResultRelInfo *relinfo, ResultRelInfo *relinfo,
HeapTuple trigtuple, HeapTuple trigtuple,
List *recheckIndexes); List *recheckIndexes,
TransitionCaptureState *transition_capture);
extern TupleTableSlot *ExecIRInsertTriggers(EState *estate, extern TupleTableSlot *ExecIRInsertTriggers(EState *estate,
ResultRelInfo *relinfo, ResultRelInfo *relinfo,
TupleTableSlot *slot); TupleTableSlot *slot);
...@@ -155,7 +192,8 @@ extern bool ExecBRDeleteTriggers(EState *estate, ...@@ -155,7 +192,8 @@ extern bool ExecBRDeleteTriggers(EState *estate,
extern void ExecARDeleteTriggers(EState *estate, extern void ExecARDeleteTriggers(EState *estate,
ResultRelInfo *relinfo, ResultRelInfo *relinfo,
ItemPointer tupleid, ItemPointer tupleid,
HeapTuple fdw_trigtuple); HeapTuple fdw_trigtuple,
TransitionCaptureState *transition_capture);
extern bool ExecIRDeleteTriggers(EState *estate, extern bool ExecIRDeleteTriggers(EState *estate,
ResultRelInfo *relinfo, ResultRelInfo *relinfo,
HeapTuple trigtuple); HeapTuple trigtuple);
...@@ -174,7 +212,8 @@ extern void ExecARUpdateTriggers(EState *estate, ...@@ -174,7 +212,8 @@ extern void ExecARUpdateTriggers(EState *estate,
ItemPointer tupleid, ItemPointer tupleid,
HeapTuple fdw_trigtuple, HeapTuple fdw_trigtuple,
HeapTuple newtuple, HeapTuple newtuple,
List *recheckIndexes); List *recheckIndexes,
TransitionCaptureState *transition_capture);
extern TupleTableSlot *ExecIRUpdateTriggers(EState *estate, extern TupleTableSlot *ExecIRUpdateTriggers(EState *estate,
ResultRelInfo *relinfo, ResultRelInfo *relinfo,
HeapTuple trigtuple, HeapTuple trigtuple,
......
...@@ -963,6 +963,10 @@ typedef struct ModifyTableState ...@@ -963,6 +963,10 @@ typedef struct ModifyTableState
TupleConversionMap **mt_partition_tupconv_maps; TupleConversionMap **mt_partition_tupconv_maps;
/* Per partition tuple conversion map */ /* Per partition tuple conversion map */
TupleTableSlot *mt_partition_tuple_slot; 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; } ModifyTableState;
/* ---------------- /* ----------------
......
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment