diff options
Diffstat (limited to 'src/backend/executor')
-rw-r--r-- | src/backend/executor/execMain.c | 86 | ||||
-rw-r--r-- | src/backend/executor/execReplication.c | 5 | ||||
-rw-r--r-- | src/backend/executor/nodeModifyTable.c | 151 |
3 files changed, 225 insertions, 17 deletions
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 549d9eb6963..473d2e00a2f 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -44,6 +44,7 @@ #include "access/transam.h" #include "access/xact.h" #include "catalog/namespace.h" +#include "catalog/partition.h" #include "catalog/pg_publication.h" #include "commands/matview.h" #include "commands/trigger.h" @@ -1279,7 +1280,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo, * in es_trig_target_relations. */ ResultRelInfo * -ExecGetTriggerResultRel(EState *estate, Oid relid) +ExecGetTriggerResultRel(EState *estate, Oid relid, + ResultRelInfo *rootRelInfo) { ResultRelInfo *rInfo; ListCell *l; @@ -1330,7 +1332,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid) InitResultRelInfo(rInfo, rel, 0, /* dummy rangetable index */ - NULL, + rootRelInfo, estate->es_instrument); estate->es_trig_target_relations = lappend(estate->es_trig_target_relations, rInfo); @@ -1344,6 +1346,69 @@ ExecGetTriggerResultRel(EState *estate, Oid relid) return rInfo; } +/* + * Return the ancestor relations of a given leaf partition result relation + * up to and including the query's root target relation. + * + * These work much like the ones opened by ExecGetTriggerResultRel, except + * that we need to keep them in a separate list. + * + * These are closed by ExecCloseResultRelations. + */ +List * +ExecGetAncestorResultRels(EState *estate, ResultRelInfo *resultRelInfo) +{ + ResultRelInfo *rootRelInfo = resultRelInfo->ri_RootResultRelInfo; + Relation partRel = resultRelInfo->ri_RelationDesc; + Oid rootRelOid; + + if (!partRel->rd_rel->relispartition) + elog(ERROR, "cannot find ancestors of a non-partition result relation"); + Assert(rootRelInfo != NULL); + rootRelOid = RelationGetRelid(rootRelInfo->ri_RelationDesc); + if (resultRelInfo->ri_ancestorResultRels == NIL) + { + ListCell *lc; + List *oids = get_partition_ancestors(RelationGetRelid(partRel)); + List *ancResultRels = NIL; + + foreach(lc, oids) + { + Oid ancOid = lfirst_oid(lc); + Relation ancRel; + ResultRelInfo *rInfo; + + /* + * Ignore the root ancestor here, and use ri_RootResultRelInfo + * (below) for it instead. Also, we stop climbing up the + * hierarchy when we find the table that was mentioned in the + * query. + */ + if (ancOid == rootRelOid) + break; + + /* + * All ancestors up to the root target relation must have been + * locked by the planner or AcquireExecutorLocks(). + */ + ancRel = table_open(ancOid, NoLock); + rInfo = makeNode(ResultRelInfo); + + /* dummy rangetable index */ + InitResultRelInfo(rInfo, ancRel, 0, NULL, + estate->es_instrument); + ancResultRels = lappend(ancResultRels, rInfo); + } + ancResultRels = lappend(ancResultRels, rootRelInfo); + resultRelInfo->ri_ancestorResultRels = ancResultRels; + } + + /* We must have found some ancestor */ + Assert(resultRelInfo->ri_ancestorResultRels != NIL); + + return resultRelInfo->ri_ancestorResultRels; +} + /* ---------------------------------------------------------------- * ExecPostprocessPlan * @@ -1443,12 +1508,29 @@ ExecCloseResultRelations(EState *estate) /* * close indexes of result relation(s) if any. (Rels themselves are * closed in ExecCloseRangeTableRelations()) + * + * In addition, close the stub RTs that may be in each resultrel's + * ri_ancestorResultRels. */ foreach(l, estate->es_opened_result_relations) { ResultRelInfo *resultRelInfo = lfirst(l); + ListCell *lc; ExecCloseIndices(resultRelInfo); + foreach(lc, resultRelInfo->ri_ancestorResultRels) + { + ResultRelInfo *rInfo = lfirst(lc); + + /* + * Ancestors with RTI > 0 (should only be the root ancestor) are + * closed by ExecCloseRangeTableRelations. + */ + if (rInfo->ri_RangeTableIndex > 0) + continue; + + table_close(rInfo->ri_RelationDesc, NoLock); + } } /* Close any relations that have been opened by ExecGetTriggerResultRel(). */ diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c index 09f78f22441..13328141e23 100644 --- a/src/backend/executor/execReplication.c +++ b/src/backend/executor/execReplication.c @@ -517,8 +517,9 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo, /* AFTER ROW UPDATE Triggers */ ExecARUpdateTriggers(estate, resultRelInfo, + NULL, NULL, tid, NULL, slot, - recheckIndexes, NULL); + recheckIndexes, NULL, false); list_free(recheckIndexes); } @@ -557,7 +558,7 @@ ExecSimpleRelationDelete(ResultRelInfo *resultRelInfo, /* AFTER ROW DELETE Triggers */ ExecARDeleteTriggers(estate, resultRelInfo, - tid, NULL, NULL); + tid, NULL, NULL, false); } } diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index babf26810b7..5e4226abe26 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -122,6 +122,12 @@ static void ExecBatchInsert(ModifyTableState *mtstate, int numSlots, EState *estate, bool canSetTag); +static void ExecCrossPartitionUpdateForeignKey(ModifyTableContext *context, + ResultRelInfo *sourcePartInfo, + ResultRelInfo *destPartInfo, + ItemPointer tupleid, + TupleTableSlot *oldslot, + TupleTableSlot *newslot); static bool ExecOnConflictUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer conflictTid, @@ -635,6 +641,9 @@ ExecGetUpdateNewTuple(ResultRelInfo *relinfo, * slot contains the new tuple value to be stored. * * Returns RETURNING result if any, otherwise NULL. + * *inserted_tuple is the tuple that's effectively inserted; + * *inserted_destrel is the relation where it was inserted. + * These are only set on success. * * This may change the currently active tuple conversion map in * mtstate->mt_transition_capture, so the callers must take care to @@ -645,7 +654,9 @@ static TupleTableSlot * ExecInsert(ModifyTableContext *context, ResultRelInfo *resultRelInfo, TupleTableSlot *slot, - bool canSetTag) + bool canSetTag, + TupleTableSlot **inserted_tuple, + ResultRelInfo **insert_destrel) { ModifyTableState *mtstate = context->mtstate; EState *estate = context->estate; @@ -1008,11 +1019,14 @@ ExecInsert(ModifyTableContext *context, if (mtstate->operation == CMD_UPDATE && mtstate->mt_transition_capture && mtstate->mt_transition_capture->tcs_update_new_table) { - ExecARUpdateTriggers(estate, resultRelInfo, NULL, + ExecARUpdateTriggers(estate, resultRelInfo, + NULL, NULL, + NULL, NULL, slot, NULL, - mtstate->mt_transition_capture); + mtstate->mt_transition_capture, + false); /* * We've already captured the NEW TABLE row, so make sure any AR @@ -1046,6 +1060,11 @@ ExecInsert(ModifyTableContext *context, if (resultRelInfo->ri_projectReturning) result = ExecProcessReturning(resultRelInfo, slot, planSlot); + if (inserted_tuple) + *inserted_tuple = slot; + if (insert_destrel) + *insert_destrel = resultRelInfo; + return result; } @@ -1160,7 +1179,7 @@ ExecDeleteAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo, */ static void ExecDeleteEpilogue(ModifyTableContext *context, ResultRelInfo *resultRelInfo, - ItemPointer tupleid, HeapTuple oldtuple) + ItemPointer tupleid, HeapTuple oldtuple, bool changingPart) { ModifyTableState *mtstate = context->mtstate; EState *estate = context->estate; @@ -1176,8 +1195,11 @@ ExecDeleteEpilogue(ModifyTableContext *context, ResultRelInfo *resultRelInfo, if (mtstate->operation == CMD_UPDATE && mtstate->mt_transition_capture && mtstate->mt_transition_capture->tcs_update_old_table) { - ExecARUpdateTriggers(estate, resultRelInfo, tupleid, oldtuple, - NULL, NULL, mtstate->mt_transition_capture); + ExecARUpdateTriggers(estate, resultRelInfo, + NULL, NULL, + tupleid, oldtuple, + NULL, NULL, mtstate->mt_transition_capture, + false); /* * We've already captured the NEW TABLE row, so make sure any AR @@ -1188,7 +1210,7 @@ ExecDeleteEpilogue(ModifyTableContext *context, ResultRelInfo *resultRelInfo, /* AFTER ROW DELETE Triggers */ ExecARDeleteTriggers(estate, resultRelInfo, tupleid, oldtuple, - ar_delete_trig_tcs); + ar_delete_trig_tcs, changingPart); } /* ---------------------------------------------------------------- @@ -1457,7 +1479,7 @@ ldelete:; if (tupleDeleted) *tupleDeleted = true; - ExecDeleteEpilogue(context, resultRelInfo, tupleid, oldtuple); + ExecDeleteEpilogue(context, resultRelInfo, tupleid, oldtuple, changingPart); /* Process RETURNING if present and if requested */ if (processReturning && resultRelInfo->ri_projectReturning) @@ -1526,7 +1548,10 @@ ExecCrossPartitionUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot, - bool canSetTag, UpdateContext *updateCxt) + bool canSetTag, + UpdateContext *updateCxt, + TupleTableSlot **inserted_tuple, + ResultRelInfo **insert_destrel) { ModifyTableState *mtstate = context->mtstate; EState *estate = mtstate->ps.state; @@ -1652,7 +1677,8 @@ ExecCrossPartitionUpdate(ModifyTableContext *context, /* Tuple routing starts from the root table. */ context->cpUpdateReturningSlot = - ExecInsert(context, mtstate->rootResultRelInfo, slot, canSetTag); + ExecInsert(context, mtstate->rootResultRelInfo, slot, canSetTag, + inserted_tuple, insert_destrel); /* * Reset the transition state that may possibly have been written by @@ -1793,6 +1819,9 @@ lreplace:; */ if (partition_constraint_failed) { + TupleTableSlot *inserted_tuple; + ResultRelInfo *insert_destrel; + /* * ExecCrossPartitionUpdate will first DELETE the row from the * partition it's currently in and then insert it back into the root @@ -1801,11 +1830,37 @@ lreplace:; */ if (ExecCrossPartitionUpdate(context, resultRelInfo, tupleid, oldtuple, slot, - canSetTag, updateCxt)) + canSetTag, updateCxt, + &inserted_tuple, + &insert_destrel)) { /* success! */ updateCxt->updated = true; updateCxt->crossPartUpdate = true; + + /* + * If the partitioned table being updated is referenced in foreign + * keys, queue up trigger events to check that none of them were + * violated. No special treatment is needed in + * non-cross-partition update situations, because the leaf + * partition's AR update triggers will take care of that. During + * cross-partition updates implemented as delete on the source + * partition followed by insert on the destination partition, + * AR-UPDATE triggers of the root table (that is, the table + * mentioned in the query) must be fired. + * + * NULL insert_destrel means that the move failed to occur, that + * is, the update failed, so no need to anything in that case. + */ + if (insert_destrel && + resultRelInfo->ri_TrigDesc && + resultRelInfo->ri_TrigDesc->trig_update_after_row) + ExecCrossPartitionUpdateForeignKey(context, + resultRelInfo, + insert_destrel, + tupleid, slot, + inserted_tuple); + return TM_Ok; } @@ -1871,11 +1926,13 @@ ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt, /* AFTER ROW UPDATE Triggers */ ExecARUpdateTriggers(context->estate, resultRelInfo, + NULL, NULL, tupleid, oldtuple, slot, recheckIndexes, mtstate->operation == CMD_INSERT ? mtstate->mt_oc_transition_capture : - mtstate->mt_transition_capture); + mtstate->mt_transition_capture, + false); /* * Check any WITH CHECK OPTION constraints from parent views. We are @@ -1891,6 +1948,74 @@ ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt, slot, context->estate); } +/* + * Queues up an update event using the target root partitioned table's + * trigger to check that a cross-partition update hasn't broken any foreign + * keys pointing into it. + */ +static void +ExecCrossPartitionUpdateForeignKey(ModifyTableContext *context, + ResultRelInfo *sourcePartInfo, + ResultRelInfo *destPartInfo, + ItemPointer tupleid, + TupleTableSlot *oldslot, + TupleTableSlot *newslot) +{ + ListCell *lc; + ResultRelInfo *rootRelInfo; + List *ancestorRels; + + rootRelInfo = sourcePartInfo->ri_RootResultRelInfo; + ancestorRels = ExecGetAncestorResultRels(context->estate, sourcePartInfo); + + /* + * For any foreign keys that point directly into a non-root ancestors of + * the source partition, we can in theory fire an update event to enforce + * those constraints using their triggers, if we could tell that both the + * source and the destination partitions are under the same ancestor. But + * for now, we simply report an error that those cannot be enforced. + */ + foreach(lc, ancestorRels) + { + ResultRelInfo *rInfo = lfirst(lc); + TriggerDesc *trigdesc = rInfo->ri_TrigDesc; + bool has_noncloned_fkey = false; + + /* Root ancestor's triggers will be processed. */ + if (rInfo == rootRelInfo) + continue; + + if (trigdesc && trigdesc->trig_update_after_row) + { + for (int i = 0; i < trigdesc->numtriggers; i++) + { + Trigger *trig = &trigdesc->triggers[i]; + + if (!trig->tgisclone && + RI_FKey_trigger_type(trig->tgfoid) == RI_TRIGGER_PK) + { + has_noncloned_fkey = true; + break; + } + } + } + + if (has_noncloned_fkey) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot move tuple across partitions when a non-root ancestor of the source partition is directly referenced in a foreign key"), + errdetail("A foreign key points to ancestor \"%s\", but not the root ancestor \"%s\".", + RelationGetRelationName(rInfo->ri_RelationDesc), + RelationGetRelationName(rootRelInfo->ri_RelationDesc)), + errhint("Consider defining the foreign key on \"%s\".", + RelationGetRelationName(rootRelInfo->ri_RelationDesc)))); + } + + /* Perform the root table's triggers. */ + ExecARUpdateTriggers(context->estate, + rootRelInfo, sourcePartInfo, destPartInfo, + tupleid, NULL, newslot, NIL, NULL, true); +} /* ---------------------------------------------------------------- * ExecUpdate @@ -2745,7 +2870,7 @@ ExecModifyTable(PlanState *pstate) ExecInitInsertProjection(node, resultRelInfo); slot = ExecGetInsertNewTuple(resultRelInfo, planSlot); slot = ExecInsert(&context, resultRelInfo, slot, - node->canSetTag); + node->canSetTag, NULL, NULL); break; case CMD_UPDATE: |